先看下效果图:
绘制轨迹
绘制手指的轨迹主要是拦截View的onTouchEvent()方法,并根据手指的轨迹绘制path。path中有两种可以实现的方法
1、Path.lineTo(x,y)方法
public class MovePathView extends View {
private Path mPath;
private Paint mPaint;
//手指按下的位置
private float startX,startY;
public MovePathView(Context context) {
super(context);
init();
}
//初始化
private void init() {
mPaint = new Paint();
mPath = new Path();
mPaint.setColor(Color.BLUE);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(4);
}
public MovePathView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MovePathView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
//设置原点
mPath.moveTo(startX,startY);
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_MOVE:
float currX = event.getX();
float currY = event.getY();
//连线
mPath.lineTo(currX,currY);
//刷新view
invalidate();
break;
}
//返回true,消费事件
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath,mPaint);
}
//对外提供的方法,重新绘制
public void reset(){
mPath.reset();
invalidate();
}
}
这里面需要知道的应该就3个点:
- View的坐标系
- View的事件分发
- Path的moveTo(),lineTo()方法
2、使用Path.quadTo()绘制曲线
public class MoveQuatoView extends View {
private Paint mPaint;
private Path mPath;
//上个位置
private float mPreX,mPreY;
//结束位置
private float endY,endX;
public MoveQuatoView(Context context) {
super(context);
init();
}
public MoveQuatoView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MoveQuatoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//初始化
private void init() {
mPath = new Path();
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath,mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mPath.moveTo(event.getX(), event.getY());
mPreX = event.getX();
mPreY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
endX = (mPreX + event.getX()) / 2;
endY = (mPreY + event.getY()) / 2;
mPath.quadTo(mPreX, mPreY, endX, endY);
mPreX = event.getX();
mPreY = event.getY();
invalidate();
break;
}
return true;
}
}
上面一段代码为了取得平滑的效果,所以endX和endY都只取了直线的中间部分。
水波纹效果
水波纹主要用到了Path.rQuadTo()
方法。
rQuadTo()也是绘制曲线的一个方法。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(5);
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
Path path = new Path();
path.moveTo(100,300);
/**
rQuadTo(float dx1, float dy1, float dx2, float dy2)
dx1:控制点X坐标,表示相对上一个终点X坐标的位移坐标,可为负值,正值表示相加,负值表示相减;
dy1:控制点Y坐标,相对上一个终点Y坐标的位移坐标。同样可为负值,正值表示相加,负值表示相减;
dx2:终点X坐标,同样是一个相对坐标,相对上一个终点X坐标的位移值,可为负值,正值表示相加,负值表示相减;
dy2:终点Y坐标,同样是一个相对,相对上一个终点Y坐标的位移值。可为负值,正值表示相加,负值表示相减;
*/
path.rQuadTo(100,-100,200,0);
path.rQuadTo(100,100,200,0);
canvas.drawPath(path,paint);
}
上面代码总共有两个rQuadTo()方法。
第一个path.rQuadTo(100,-100,200,0); 起始点:(100,300) 控制点坐标:(200,200),X:200=100+100,Y: 200 = 300-100 终点坐标: (300,300), X :300=100+200,Y:300 = 300+0 效果是:![image2.png](http://upload-images.jianshu.io/upload_images/2729169-8a82e6e36cd5cf8b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 第二个
path.rQuadTo(100,100,200,0);`
此时的起始点坐标也就是第一个的终点坐标,所以
起始点坐标:(300,300)
控制点坐标:(400,400),X: 400 = 300+100,Y:400 = 300+100
终点坐标: (500,300),X: 500 = 300+200,Y:300 = 300+0
同理,如果有第三个path.rQuadTo,那么第三个的起始点也就是上一个的终点(500,300)
搞清楚了path.rQuadTo()
方法的用法就可以去实现水波纹的效果了。
public class RippleView extends View {
private Paint mPaint;
private Path mPath;
//波纹的宽度
private int mItemWaveLength = 1000;
//波纹每次移动的距离
private int dx;
public RippleView(Context context) {
super(context);
init();
}
public RippleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public RippleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//初始化
private void init(){
mPath = new Path();
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(5);
mPaint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//移动后,重置mPath,将之前路径清空
mPath.reset();
//距离顶部的高度
int originY = 600;
//波纹宽度的一般
int halfWaveLen = mItemWaveLength/2;
//随着刷新,每次移动dx距离
mPath.moveTo(-mItemWaveLength+dx,originY);
//for循环当前屏幕中所有的波纹
for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){
mPath.rQuadTo(halfWaveLen/2,-100,halfWaveLen,0);
mPath.rQuadTo(halfWaveLen/2,100,halfWaveLen,0);
}
mPath.lineTo(getWidth(),getHeight());
mPath.lineTo(0,getHeight());
mPath.close();
canvas.drawPath(mPath,mPaint);
}
/**
* 动画的目的是让波纹移动起来
* 利用调用在path.moveTo的时候,将起始点向右移动即可实现移动,
* 而且只要我们移动一个波长的长度,波纹就会重合,就可以实现无限循环了
*/
public void startAnim(){
//动画移动的距离 0~mItemWaveLength
ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength);
//时间
animator.setDuration(2000);
//重复次数,这里是无限次
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator());
//动画刷新监听
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//每次移动的距离
dx = (int)animation.getAnimatedValue();
//刷新View
postInvalidate();
}
});
animator.start();
}
}
这样就实现了一个水波纹的效果了。
本文参考:
自定义控件三部曲之绘图篇(六)——Path之贝赛尔曲线和手势轨迹、水波纹效果