大概是這樣的效果.gif
網(wǎng)絡圖片已艰,如有侵權(quán)立即更換哩掺。
分析:
看起來有點炫酷疮丛,第一次做還有點分不清楚門道。連續(xù)幾天,我都在思考它的原理呢蔫,有空閑就兩只手來回片吊,模擬蕩漾得出幾點特征:
- 1.雙層波浪协屡,相反的方向運行肤晓,中間是靜態(tài)的不考慮。
- 2.看著像一層層波浪卷员,上下移動腾务,向x軸方向移動,偶爾還變個速導致忽快忽慢岩瘦,都是錯覺未巫。
- 3.波浪是不均勻的,非得貝塞爾曲線來做不可启昧。
解釋第2點叙凡,上下移動,向左移動箫津,忽快忽慢的誤導性很大狭姨,這是個巨坑苏遥。注意最下面那層波浪饼拍,把它的波浪想像成固定一個整體,不上下移動田炭,眼睛一直跟隨一個波段师抄,盯住它,你會發(fā)現(xiàn)教硫,它就是一個整體在勻速向左移動叨吮,曲線只是在x軸平移,不斷重復瞬矩,第2點的錯覺是波段的波峰茶鉴,波谷,波長不一致導致的變速感景用。沒有上下移動涵叮。
如何實現(xiàn):
1.path畫一條固定的貝塞爾曲線,曲線的數(shù)據(jù)找 ui小姐姐要大概是這樣子的:
image.png
為什么超出屏幕還有一部分呢伞插?因為它要連接起點割粮,像這樣:
image.png
2.一直向左平移到線的終點:
image.png
運動到曲線的最右點到快到達屏幕邊界內(nèi)時,下一幀復原最開始波浪,一個無限循環(huán)的不規(guī)則波浪就完成了媚污。
我把原型圖里面最下面的波浪拼起來:
all.png
這樣總明白了吧舀瓢!他就是一條完整的曲線。
上手
第一個問題:如何畫一條曲線耗美,可以首尾拼接起來保持平滑京髓?
- 畫一個多點相連平滑曲線航缀,android 上解決不了(最多兩個控制點,不能再多了)朵锣,但是數(shù)學可以谬盐。
安排:http://www.reibang.com/p/98088ff77607
結(jié)尾處,單獨處理诚些,用一條一個控制點的一階貝賽爾連接飞傀。
上圖:
無標題ss.png
最后效果:
效果.gif
因為ui 姐姐需要兩層不透明的,效果以最下面的曲線的為例實現(xiàn)了90%吧诬烹!但是為什么這么丑砸烦,我也不知道,哈哈绞吁。如果給一個好點的曲線數(shù)據(jù)幢痘,也許就不一樣了!比如我換一條均勻的貝賽爾曲線就好看多了:
均勻效果.gif
但是為什么要畫不均勻的波浪呢家破?
上代碼:
wave 類
package com.example.a14143.wave;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.Log;
/**
* @author DrChen
* @Date 2019/4/21.
* qq:1414355045
* 保存波浪的所有信息
* 和繪制
*/
public class Wave {
/**
* 單個波浪寬
*/
private float waveWith;
/**
* 繪制的方向
*/
boolean isLeft = true;
/**
* 單個循環(huán)一半長度占屏幕寬度的百分比(浪的長度等于一個循環(huán)的1/2)
*/
private float waveWithF;
/**
* 繪制的區(qū)域
*/
private RectF rectF;
/**
* 當前移動位置計次
*/
private int currentMoveX;
/**
* 每次移動的距離
*/
private int moveX;
/**
* 移動前的點位
*/
private float[][] wavePoints;
private boolean isDebug = false;
/**
* 切率
*/
private float lineSmoothness = 0.12f;
public Path path = new Path();
private int mHeight;
/**
*
* @param isLeft 向左向右
* @param rectF 繪制區(qū)域
* @param points
*/
public Wave( boolean isLeft, RectF rectF,float lineSmoothness,int mHeight,int moveX,float[]... points) {
this.isLeft = isLeft;
this.mHeight = mHeight;
if(lineSmoothness>0) this.lineSmoothness = lineSmoothness;
setRectF(rectF);
setPoints(points);
//每次移動的距離
if (isLeft) {
this.moveX = - moveX;
} else {
this. moveX = moveX;
}
measurePath();
}
private void setRectF(RectF rectF) {
this.rectF = rectF;
}
/**
* 傳入數(shù)據(jù)只有控制點颜说,沒有貝賽爾的起點,通過控制點計算各個點位
*/
private void setPoints(float[]... points) {
if (points.length < 4 ) {
throw new RuntimeException("所有點不能小于4個"); //直接手動拋出異常
}
if (rectF == null) {
throw new RuntimeException("沒有繪制區(qū)域就沒有繪制");
}
if (wavePoints == null) {
wavePoints = new float[points.length][6];
}
waveWithF = points[points.length-1][0];
waveWith = rectF.width()* waveWithF;
// 下面是利用各個點連成一條平滑的曲線
float prePreviousPointX = Float.NaN;
float prePreviousPointY = Float.NaN;
float previousPointX = Float.NaN;
float previousPointY = Float.NaN;
float currentPointX = Float.NaN;
float currentPointY = Float.NaN;
float nextPointX;
float nextPointY;
int lineSize = points.length;
for (int i = 0; i < points.length; i++) {
if (Float.isNaN(currentPointX)) {
currentPointX = points[i][0];
currentPointY = points[i][1];
}
if (Float.isNaN(previousPointX)) {
//是否是第一個點
if (i > 0) {
previousPointX = points[i - 1][0];
previousPointY = points[i - 1][1];
} else {
//是的話就用當前點表示上一個點
previousPointX = currentPointX;
previousPointY = currentPointY;
}
}
if (Float.isNaN(prePreviousPointX)) {
//是否是前兩個點
if (i > 1) {
prePreviousPointX = points[i][0];
prePreviousPointY = points[i][1];
} else {
//是的話就用當前點表示上上個點
prePreviousPointX = previousPointX;
prePreviousPointY = previousPointY;
}
}
// 判斷是不是最后一個點了
if (i < lineSize - 1) {
nextPointX = points[i + 1][0];
nextPointY = points[i + 1][1];
} else {
//是的話就用當前點表示下一個點
nextPointX = currentPointX;
nextPointY = currentPointY;
}
if (i == 0||i>=points.length-2) {//起點和最后面接頭的地方汰聋,為了保證平滑單獨處理
// 將Path移動到開始點
wavePoints[i][0] = currentPointX*rectF.width();
wavePoints[i][1] = currentPointY*rectF.height()+rectF.top;
} else {
// 求出控制點坐標
final float firstDiffX = (currentPointX - prePreviousPointX);
final float firstDiffY = (currentPointY - prePreviousPointY);
final float secondDiffX = (nextPointX - previousPointX);
final float secondDiffY = (nextPointY - previousPointY);
final float firstControlPointX = previousPointX + (lineSmoothness * firstDiffX);
final float firstControlPointY = previousPointY + (lineSmoothness * firstDiffY);
final float secondControlPointX = currentPointX - (lineSmoothness * secondDiffX);
final float secondControlPointY = currentPointY - (lineSmoothness * secondDiffY);
wavePoints[i][0] = firstControlPointX*rectF.width();
wavePoints[i][1] = firstControlPointY*rectF.height()+rectF.top;
wavePoints[i][2] = secondControlPointX*rectF.width();
wavePoints[i][3] = secondControlPointY*rectF.height()+rectF.top;
wavePoints[i][4] = currentPointX*rectF.width();
wavePoints[i][5] = currentPointY*rectF.height()+rectF.top;
}
// 更新值,
prePreviousPointX = previousPointX;
prePreviousPointY = previousPointY;
previousPointX = currentPointX;
previousPointY = currentPointY;
currentPointX = nextPointX;
currentPointY = nextPointY;
}
}
/**
* 測試時需要
*/
public void testMoveTo(float moveX) {
for (int i = 0; i < wavePoints.length; i++) {
wavePoints[i][0] += moveX * waveWith;
wavePoints[i][2] += moveX * waveWith;
wavePoints[i][4] += moveX * waveWith;
}
}
/**
* 刷新每次移動的點位
*/
public void measureMoveTo() {
if(isLeft){
if(waveWith + currentMoveX < Math.abs(moveX)){
currentMoveX = 0;
}else {
currentMoveX+=moveX;
}
}else {
if(waveWith - currentMoveX< moveX){
currentMoveX = 0;
}else {
currentMoveX+= moveX;
}
}
}
/**
*
* 繪制path
*/
private void measurePath() {
path.reset();
if(isLeft){
path.moveTo(wavePoints[0][0], mHeight);
path.lineTo(wavePoints[0][0], wavePoints[0][1]);
for (int i = 1; i < wavePoints.length-2 ; i ++) {
path.cubicTo(wavePoints[i][0], wavePoints[i][1],
wavePoints[i][2], wavePoints[i][3],
wavePoints[i][4], wavePoints[i ][5]);
}
//最后面接頭處理
path.quadTo(wavePoints[wavePoints.length-2][0],wavePoints[wavePoints.length-2][1],
wavePoints[wavePoints.length-1][0],wavePoints[wavePoints.length-1][1]
);
for (int i = 1; i < wavePoints.length-2 ; i ++) {
path.cubicTo(wavePoints[i][0]+waveWith, wavePoints[i][1],
wavePoints[i][2]+waveWith, wavePoints[i][3],
wavePoints[i][4]+waveWith, wavePoints[i ][5]);
}
path.lineTo(waveWith + wavePoints[wavePoints.length-1][0], mHeight);
path.close();
}else {
path.moveTo(wavePoints[0][0]-waveWith, mHeight);
path.lineTo(wavePoints[0][0]-waveWith, wavePoints[0][1]);
for (int i = 1; i < wavePoints.length-2 ; i ++) {
path.cubicTo(wavePoints[i][0]-waveWith, wavePoints[i][1],
wavePoints[i][2]-waveWith, wavePoints[i][3],
wavePoints[i][4]-waveWith, wavePoints[i ][5]);
}
//最后面接頭處理
path.quadTo(wavePoints[wavePoints.length-2][0]-waveWith,wavePoints[wavePoints.length-2][1],
wavePoints[wavePoints.length-1][0]-waveWith,wavePoints[wavePoints.length-1][1]
);
for (int i = 1; i < wavePoints.length-2 ; i ++) {
path.cubicTo(wavePoints[i][0], wavePoints[i][1],
wavePoints[i][2], wavePoints[i][3],
wavePoints[i][4], wavePoints[i ][5]);
}
path.lineTo( wavePoints[wavePoints.length-1][0], mHeight);
path.close();
}
}
public void draw(Canvas canvas, Paint paint) {
canvas.save();
canvas.translate(currentMoveX,0);
canvas.drawPath(path,paint);
measureMoveTo();
canvas.restore();
}
}
View 類
package com.example.a14143.wave;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Shader;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
* @author DrChen
* @Date 2019/4/16 0016.
* qq:1414355045
* 三段漸變波紋
*/
public class WaveView extends View {
/**
* 三段漸變的中點
*/
private final float gradient_height_mid = 0.28f;
private final float top_wave_top = 0.28f;
private final float top_wave_bottom = top_wave_top + 0.06f;
private final float bottom_wave_bottom = top_wave_bottom ;
private final float bottom_wave_top = top_wave_bottom - 0.05f;
/**
* 這個是背景的漸變色和波浪的顏色
*/
private int topColor, midColor, bottomColor, waveColor;
/**
* 控件的寬高
*/
private int mHeight, mWidth;
/**
* 漸變畫筆
*/
private Paint gradientPaint;
/**
* 波浪的畫筆
*/
private Paint wavePaint;
/**
* 繪制波浪式輔助的畫筆(可以去掉)
*/
private Paint testPaint;
/**
* 是否開啟輔助的畫筆
*/
private boolean isDebug = false;
/**
* view 是否被刪除
*/
private boolean onPause = false;
/**
* 整個視圖的背景是三種顏色的漸變门粪,藍到紫的區(qū)域
*/
private RectF gradient_bottom;
/**
* 紫到白的區(qū)域
*/
private RectF gradient_top;
/**
* 波浪繪制區(qū)域
*/
private RectF bottom_wave_RectF;
private RectF top_wave_RectF;
private LinearGradient oneGradient;
private LinearGradient twoGradient;
private Wave bottomWave;
private Path bottomPath = new Path();
private Wave topWave;
private Path topPath = new Path();
public WaveView(Context context) {
this(context, null);
}
public WaveView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, -1);
}
public WaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
topColor = Color.parseColor("#3851DF");
midColor = Color.parseColor("#937EF7");
bottomColor = Color.parseColor("#FFFFFF");
waveColor = Color.parseColor("#80FFFFFF");
if (gradientPaint == null) {
gradientPaint = new Paint();
}
gradientPaint.setStyle(Paint.Style.FILL);
if (gradient_bottom == null) gradient_bottom = new RectF();
if (gradient_top == null) gradient_top = new RectF();
if (bottom_wave_RectF == null) bottom_wave_RectF = new RectF();
if (top_wave_RectF == null) top_wave_RectF = new RectF();
if (wavePaint == null) wavePaint = new Paint();
wavePaint.setColor(waveColor);
wavePaint.setStyle(Paint.Style.FILL);
wavePaint.setAntiAlias(true);
if (testPaint == null) {
testPaint = new Paint();
}
testPaint.setColor(Color.BLACK);
testPaint.setStrokeWidth(2);
testPaint.setStyle(Paint.Style.STROKE);
}
public void onResume() {
onPause = false;
invalidate();
}
public void onPause() {
onPause = true;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
gradient_bottom.right = mWidth;
gradient_bottom.bottom = mHeight * gradient_height_mid;
gradient_top.top = mHeight * gradient_height_mid;
gradient_top.bottom = mHeight;
gradient_top.right = mWidth;
bottom_wave_RectF.top = mHeight * bottom_wave_top;
bottom_wave_RectF.right = mWidth;
bottom_wave_RectF.bottom = mHeight * bottom_wave_bottom;
top_wave_RectF.top = mHeight * top_wave_top;
top_wave_RectF.right = mWidth;
top_wave_RectF.bottom = mHeight * top_wave_bottom;
if (bottomWave == null) {
//我的數(shù)據(jù)屏幕寬度是750,浪高50, 每個點位后面要加f 不然算出來的數(shù)據(jù)會全錯烹困,因為會丟失小數(shù)點
bottomWave = new Wave( true, bottom_wave_RectF,0.12f,mHeight,dip2px(5,getContext()),
new float[]{0 / 750f, 35 / 50f},
new float[]{214 / 750f, 6 / 50f},
new float[]{362 / 750f, 28 / 50f},
new float[]{582 / 750f, 6 / 50f},
new float[]{903 / 750f, 41 / 50f},
new float[]{1149 / 750f, 25 / 50f},
new float[]{1262/750f,35/50f},
new float[]{1390/750f,60/50f},//最后這個點可以適當調(diào)整玄妈,保證平滑
new float[]{1522/750f,35/50f}
);
}
if(topWave==null){
//我的數(shù)據(jù)屏幕寬度是750,浪高50, 每個點位后面要加f 不然算出來的數(shù)據(jù)會全錯髓梅,因為會丟失小數(shù)點
topWave = new Wave(false, top_wave_RectF,0.1f,mHeight,dip2px(2,getContext()),
new float[]{0 / 750f, 28 / 78f},
new float[]{262 / 750f, 0 / 78f},
new float[]{657 / 750f, 37 / 78f},
new float[]{1052 / 750f, 0 / 78f},
new float[]{1314 / 750f, 28 / 78f},
new float[]{1520 / 750f, 60 / 78f},
new float[]{1709/750f,28/78f}
);
}
}
/**
* dp轉(zhuǎn)px
* @param dip dp
* @param context 上下文
* @return
*/
public static int dip2px(float dip, Context context) { float density = context.getResources().getDisplayMetrics().density;
int px = (int) (dip * density + 0.5f);// 4.9->4, 4.1->4, 四舍五入
return px;
}
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
//繪制背景三段漸變
drawBackdrop(canvas);
if (isDebug) {
canvas.drawRect(bottom_wave_RectF, testPaint);
}
// topWave.testMoveTo(0.4f);
// bottomWave.testMoveTo(0.6f);
topWave.draw(canvas,wavePaint);
bottomWave.draw(canvas,wavePaint);
if (onPause) {
return;
}
postInvalidateDelayed(10);
}
@Override
protected void onDetachedFromWindow() {
onPause = true;
super.onDetachedFromWindow();
}
/**
* 繪制三段漸變背景
*
* @param canvas
*/
private void drawBackdrop(Canvas canvas) {
if (oneGradient == null)
oneGradient = new LinearGradient(gradient_bottom.centerX(), gradient_bottom.top, gradient_bottom.centerX(), gradient_bottom.bottom, topColor, midColor, Shader.TileMode.MIRROR);
if (twoGradient == null)
twoGradient = new LinearGradient(gradient_top.centerX(), gradient_top.top, gradient_top.centerX(), gradient_top.bottom, midColor, bottomColor, Shader.TileMode.MIRROR);
gradientPaint.setShader(oneGradient);
canvas.drawRect(gradient_bottom, gradientPaint);
gradientPaint.setShader(twoGradient);
canvas.drawRect(gradient_top, gradientPaint);
}
}
最后調(diào)用:
package com.example.a14143.wave;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
WaveView waveView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
waveView = findViewById(R.id.waveView);
}
@Override
protected void onResume() {
super.onResume();
waveView.onResume();
}
@Override
protected void onPause() {
super.onPause();
waveView.onPause();
}
}
github的鏈接: https://github.com/drchengit/WaveView
我是drchen拟蜻,一個溫潤的男子,版權(quán)所有枯饿,未經(jīng)允許不得抄襲酝锅。