Android自定義View之使用Path繪製手勢軌跡和水波效果
先看下效果圖:
繪製軌跡
繪製手指的軌跡主要是攔截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);
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)
第二個
此時的起始點座標也就是第一個的終點座標,所以
起始點座標:(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();
}
}
這樣就實現了一個水波紋的效果了。