一、寫這個demo主要是由於一個同事給我看了一個ios的效果,由於感受好玩因此我就寫了android樣式的,具體的效果就以下圖展現(圖是ios的gif不過效果是同樣的),有須要的朋友在下面會給出下載地址
java
首先分析一下個人作法,我是將波浪的部分和頭像分開考慮,根據波浪的移動高度將頭像畫出
android
1、定義屬性
其實我在作的時候我是直接開始畫,畫完了纔去優化自定義屬性,然而如今這些過程已經不重要了,我就先介紹下定義的屬性分別都是什麼含義。ios
<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」移動一次的距離,像素git
2、開始畫CorrugateView這個控件
(1)獲取全部屬性的值和初始化所須要的畫筆github
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)獲取控件的寬高和初始化要畫的波浪的每一個點canvas
@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()這個方法比較重要,控制着頭像的上下移動,主要運用貝塞爾曲線的二階公式計算頭像的高度,下圖所示
markdown
/** * 獲取頭像中心的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去看啦!app
哦!差點忘了還有一個三階的愛心,demo的LoveLayout.java這個文件喲!感興趣的本身去看喲!ide
效果效果圖,我又忘了!以下所示,裏面使用了透明度的漸變,因此越高就越透明瞭,每一個愛心的路徑都是一條隨機的三階貝塞爾曲線,demo中只要點界面就會拋出一個愛心,本身去欣賞吧!
demo下載地址:https://github.com/972242736/BubblingDemo.git