1、寫這個demo主要是因爲一個同事給我看了一個ios的效果,因爲感覺好玩所以我就寫了android樣式的,具體的效果就如下圖展示(圖是ios的gif不過效果是一樣的),有需要的朋友在下面會給出下載地址
首先分析一下我的做法,我是將波浪的部分和頭像分開考慮,根據波浪的移動高度將頭像畫出
一、定義屬性
其實我在做的時候我是直接開始畫,畫完了纔去優化自定義屬性,然而現在這些過程已經不重要了,我就先介紹下定義的屬性分別都是什麼含義。
<mmf.com.bubblingdemo.CorrugateView
android:id="@+id/cv_waves"
android:layout_width="match_parent"
android:layout_marginTop="100dp"
app:imgSize="50dp"
app:waveHeight="20dp"
app:rollTime="20"
app:rollDistance="5"
android:layout_height="70dp" />
app:imgSize=”50dp”定義的是頭像的大小
app:waveHeight=”20dp”波浪的高度
app:rollTime=”20”移動一次的時間
app:rollDistance=”5”移動一次的距離,像素
二、開始畫CorrugateView這個控件
(1)獲取所有屬性的值和初始化所需要的畫筆
public void init(Context context, AttributeSet attrs) {
TypedArray attr = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CorrugateView, 0, 0);
try {
imgSize = (int) attr.getDimension(R.styleable.CorrugateView_imgSize, getResources().getDimensionPixelSize(
R.dimen.top_distance));
waveHeight = (int) attr.getDimension(R.styleable.CorrugateView_waveHeight, getResources().getDimensionPixelSize(
R.dimen.top_distance_20));
rollTime = attr.getInteger(R.styleable.CorrugateView_rollTime, 30);
rollDistance = attr.getInteger(R.styleable.CorrugateView_rollDistance, 5);
} finally {
attr.recycle();
}
length = rollDistance;
//保存上面一條曲線的數組
mPointsList = new ArrayList<Point>();
//保存下面一條曲線的數組
mPointsListBottom = new ArrayList<Point>();
//畫上面曲線的畫筆和線
mWavePath = new Path();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(getResources().getColor(R.color.white));
//畫下面曲線的畫筆和線
mWavePathBottom = new Path();
mPaintBottom = new Paint();
mPaintBottom.setAntiAlias(true);
mPaintBottom.setStyle(Paint.Style.FILL);
mPaintBottom.setColor(getResources().getColor(R.color.top_withe));
}
(2)獲取控件的寬高和初始化要畫的波浪的每個點
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = getMeasuredWidth();
//控件高度=圖片的高度加上波浪的高度
mHeight = waveHeight + imgSize;
//初始化每個點
initPoint();
invalidate();
//開啓一個計時器
if (timer == null)
start();
}
initPoint();這個方法就是畫二階貝塞爾曲線的每個點,具體看代碼,因爲有點長就不貼進來了
start();開啓一個計時器,主要作用是在一定時間按一定的距離將曲線向右移動
(3)畫曲線
@Override
protected void onDraw(Canvas canvas) {
//畫兩條曲線
mWavePath.reset();
mWavePathBottom.reset();
mWavePathBottom.moveTo(mPointsListBottom.get(0).x, mPointsListBottom.get(0).y);
mWavePathBottom.quadTo(mPointsListBottom.get(1).x, mPointsListBottom.get(1).y, mPointsListBottom.get(2).x, mPointsListBottom.get(2).y);
mWavePathBottom.quadTo(mPointsListBottom.get(3).x, mPointsListBottom.get(3).y, mPointsListBottom.get(4).x, mPointsListBottom.get(4).y);
mWavePathBottom.quadTo(mPointsListBottom.get(5).x, mPointsListBottom.get(5).y, mPointsListBottom.get(6).x, mPointsListBottom.get(6).y);
mWavePathBottom.quadTo(mPointsListBottom.get(7).x, mPointsListBottom.get(7).y, mPointsListBottom.get(8).x, mPointsListBottom.get(8).y);
mWavePathBottom.quadTo(mPointsListBottom.get(9).x, mPointsListBottom.get(9).y, mPointsListBottom.get(10).x, mPointsListBottom.get(10).y);
mWavePathBottom.lineTo(mPointsListBottom.get(10).x, mHeight);
mWavePathBottom.lineTo(mPointsListBottom.get(0).x, mHeight);
mWavePathBottom.lineTo(mPointsListBottom.get(0).x, mPointsListBottom.get(0).y);
mWavePathBottom.close();
canvas.drawPath(mWavePathBottom, mPaintBottom);
mWavePath.moveTo(mPointsList.get(0).x, mPointsList.get(0).y);
mWavePath.quadTo(mPointsList.get(1).x, mPointsList.get(1).y, mPointsList.get(2).x, mPointsList.get(2).y);
mWavePath.quadTo(mPointsList.get(3).x, mPointsList.get(3).y, mPointsList.get(4).x, mPointsList.get(4).y);
mWavePath.quadTo(mPointsList.get(5).x, mPointsList.get(5).y, mPointsList.get(6).x, mPointsList.get(6).y);
mWavePath.quadTo(mPointsList.get(7).x, mPointsList.get(7).y, mPointsList.get(8).x, mPointsList.get(8).y);
mWavePath.lineTo(mPointsList.get(8).x, mHeight);
mWavePath.lineTo(mPointsList.get(0).x, mHeight);
mWavePath.lineTo(mPointsList.get(0).x, mPointsList.get(0).y);
mWavePath.close();
canvas.drawPath(mWavePath, mPaint);
//畫頭像
Bitmap bitmap = BitmapFactory.decodeResource(this.getContext()
.getResources(), R.mipmap.icon_2017);
drawImage(canvas, bitmap, (mWidth - imgSize) / 2, (int) getHeigthIcon() - imgSize,
imgSize, imgSize, 0, 0, mPaint);
//當移動的長度大於等於屏幕寬度重置點的座標
if (allLength >= mWidth) {
resetPoints();
allLength = 0;
}
}
getHeigthIcon()這個方法比較重要,控制着頭像的上下移動,主要運用貝塞爾曲線的二階公式計算頭像的高度,下圖所示
/**
* 獲取頭像中心的x對應的曲線的y值
* @return
*/
private float getHeigthIcon() {
//移動的比率
float t = (float) allHeight * 2 / mWidth;
float y;
//ismHeight爲true表示向下移動 false表示向上移動
if (ismHeight) {
//二價的貝塞爾曲線公式計算下面的曲線的根據t變化的高度
y = mPointsList.get(2).y * (1 - t) * (1 - t)
+ 2 * mPointsList.get(3).y * t * (1 - t)
+ mPointsList.get(4).y * t * t;
} else {
//二價的貝塞爾曲線公式計算上面的曲線的根據t變化的高度
y = mPointsList.get(0).y * (1 - t) * (1 - t)
+ 2 * mPointsList.get(1).y * t * (1 - t)
+ mPointsList.get(2).y * t * t;
}
return y;
}
drawImage(Canvas canvas, Bitmap blt, int x, int y, int w,int h, int bx, int by, Paint paint)畫圖片的方法,具體看代碼,至此一個波浪的頭像就算完成啦!感興趣的下demo去看啦!
哦!差點忘了還有一個三階的愛心,demo的LoveLayout.java這個文件喲!感興趣的自己去看喲!
效果效果圖,我又忘了!如下所示,裏面使用了透明度的漸變,所以越高就越透明瞭,每個愛心的路徑都是一條隨機的三階貝塞爾曲線,demo中只要點界面就會拋出一個愛心,自己去欣賞吧!
demo下載地址:https://github.com/972242736/BubblingDemo.git