一、写这个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