自定义view进阶-贝塞尔曲线实现水波动画、粘性控件
2017-06-08 23:37
344 查看
开始之前
要说自定义控件里比较逼格高的,莫属贝塞尔曲线做出的特效了,例如最常见的QQ粘性气泡,以及水波加载动画,反弹的小球,等等,看起来特别的炫酷,当然了水波动画的实现方法有好几种,这里主要记录以贝塞尔曲线的方式如何实现,开始之前,了解下基础知识,以及几个关键API:1.快速熟悉贝塞尔
按控制点个数分为一阶贝塞尔曲线有0个控制点,二阶贝塞尔曲线有1个控制点,三阶2个控制点,四阶3个控制点,当然还有更多控制点的更多阶,不过在安卓开发中,用的3个已经是极限了,因为随着控制点增加,cpu要做的运算量呈指数增长,为了保证性能,最好将控制点使用保证在2个以下,3个不是不可以,如果是看效果还好,但是不建议在实际开发中应用,一般来说,3阶贝塞尔曲线已经够用了,足以满足绝大部分场景需求。 如果你了解PS,就很容易理解贝塞尔曲线是怎么画出的了,不了解PS也没关系,看下面几个图就明白了,比较简单: 一阶贝塞尔因为没有控制点,看起来其实就是一条直线。这里就不画图了。 二阶贝塞尔有一个控制点,在PS里用钢笔工具可以很形象的看到其结构,如图:
三阶贝塞尔曲线,2个控制点,一个在上,一个在下:
2.关键API
在自定义View中,绘制的坐标系是以屏幕左上角为原点(0,0),向右向下为正方向,如果我们想画一个2阶贝塞尔曲线,首先我们要一个画笔paint,一个路径path,然后可以画画了,path.moveTo(startX,startY);
path.quadTo(controlX,controlY,endX,endY);
然后在ondraw()方法中就可以画出来了:
示例代码如下:
path.moveTo(100,100); path.quadTo(300,20,500,100);
就这样,一个二阶贝塞尔曲线就画出来了:
三阶贝塞尔用的是:cubicTo(x1,y1,x2,y2,x3,y3);其中,x1,y1是第一个控制点,x2,y2是第二个控制点,x3,y3是终点。
path.moveTo(100,100); path.cubicTo(300,20,500,180,800,100);
效果图为:
如果没有moveTo()方法,系统默认起始点为原点(0,0),而每次quadTo()时,起始点都是上一次的终点,终点又会成下一次的起点,这个要尤其注意,并非还是一开始的起始点。
于是,这里引出另一个API来画贝塞尔曲线,
二阶:rQuadTo(controlX,controlY,endX,endY);
三阶:rCubicTo(control1X,control1Y,control2X,contrl2Y,endX,endY);
看源码可以知道,这个方法采用的坐标是相对于起始点的坐标值计算的,换句话说,x1,y1体现的是变化量,系统根据此变化量算出真实坐标。例如,实现上面二阶贝塞尔曲线可以这么写:
path.quadTo(300,20,500,100);
实现三阶可以这么写:
path.rCubicTo(200,-80,400,80,600,0);
效果是一样的。
安卓本身并没有针对4阶以上的贝塞尔做实现,至于4阶以上的实现方式,我随后整理出来,这里为了快速掌握并实战,直接进入第三个环节:
3学以致用
做出一个小特效,如图:代码如下:
/** * Created by HY on 2017/6/12. */ public class BazierDemo extends View implements View.OnClickListener { private int radius =dp2px(55); private Paint mPathPaint ; private Path mPath; private int mWaveLength = 280; //波纹数量 private int waveCount; private int mRadius; private int dx=0; private int mCenterY; private int mScreenHeight; private int mScreenWidth; private int mOffset; private Paint cirPaint 4000 ; private Bitmap bitmap; private Canvas bitmapCanvas; private ValueAnimator animator; public BazierDemo(Context context) { this(context,null); } public BazierDemo(Context context, AttributeSet attrs) { this(context, attrs,0); } public BazierDemo(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { cirPaint = new Paint(Paint.ANTI_ALIAS_FLAG); cirPaint.setAntiAlias(true); cirPaint.setDither(true); cirPaint.setColor(Color.LTGRAY); cirPaint.setStyle(Paint.Style.FILL_AND_STROKE); mPathPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPathPaint.setAntiAlias(true); mPathPaint.setDither(true); mPathPaint.setColor(Color.BLUE); mPathPaint.setStyle(Paint.Style.FILL_AND_STROKE); mPathPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); setOnClickListener(this); mPath = new Path(); } private int dp2px(int dp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mScreenWidth = w; mScreenHeight = h; if (bitmap==null){ bitmap = Bitmap.createBitmap(w,h, Bitmap.Config.ARGB_8888); } // mRadius = w/3; waveCount = (int) Math.round(mScreenWidth/mWaveLength+1.5); mCenterY= mScreenHeight/2+mRadius; if (bitmapCanvas==null){ bitmapCanvas = new Canvas(bitmap); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //绘制圆 bitmapCanvas.save(); bitmapCanvas.drawCircle(getWidth()/2,getWidth()/2, getWidth()/3, cirPaint); mPath.reset(); //移动到屏幕最左边 mPath.moveTo(-mWaveLength+mOffset,mCenterY+dx); for (int i = 0; i < waveCount; i++) { //正弦曲线 mPath.quadTo((-mWaveLength*3/4)+(i*mWaveLength)+mOffset,mCenterY+30+dx,(-mWaveLength/2)+(i*mWaveLength)+mOffset,mCenterY+dx); mPath.quadTo((-mWaveLength/4)+(i*mWaveLength)+mOffset,mCenterY-30+dx,i*mWaveLength+mOffset,mCenterY+dx); } //填充 mPath.lineTo(mScreenWidth,mScreenHeight); mPath.lineTo(0,mScreenHeight); mPath.close(); bitmapCanvas.drawPath(mPath,mPathPaint); bitmapCanvas.restore(); canvas.drawBitmap(bitmap,0,0,null); } @Override public void onClick(View v) { animator = ValueAnimator.ofInt(0,mWaveLength); animator.setDuration(1000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mOffset = (int) animation.getAnimatedValue(); postInvalidate(); } }); animator.start(); } public void setProgress(int dx){ this.dx = -(int) (dx*0.01f*2*mRadius); invalidate(); } }
MainActivity里的代码为:
bazierDemo = (BazierDemo) findViewById(R.id.demo); seekBar = (SeekBar) findViewById(R.id.seek); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { bazierDemo.setProgress(progress); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } });
相关文章推荐
- Android进阶——自定义View之继承系统控件实现自带删除按钮动画效果和软键盘自动悬浮于文本框下方
- Android进阶——自定义View之扩展系统控件的另一种思路实现渐变文字动画的TextView
- Android进阶——自定义View之重写ViewGroup组合系统控件实现自定义ToolBar模板
- 安卓通过自定义view实现水波进度条控件
- Android高手进阶教程(二十七)之---基于ViewFlipper实现的自定义新手指引控件.
- Android高手进阶教程(二十七)之---基于ViewFlipper实现的自定义新手指引控件.
- Android高手进阶教程(二十七)之---基于ViewFlipper实现的自定义新手指引控件.
- Android进阶——自定义View之组合系统控件实现水珠形状的ItemView
- Android高手进阶教程(二十七)之---基于ViewFlipper实现的自定义新手指引控件.
- Android高手进阶教程(二十七)之---基于ViewFlipper实现的自定义新手指引控件.
- Android进阶之自定义View实战(一)仿iOS UISwitch控件实现
- Android自定义View阻尼动画&贝塞尔曲线的实现
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
- ios中自定义alert view,并实现动画组合
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
- Android 自定义View修炼-自定义可动画展开收缩View的实现
- 自定义view实现水波荡漾的效果
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
- 基于ViewFlipper实现的自定义新手指引控件
- android高分段进阶攻略(9)——ViewPager补间动画实现京东广告Banner