您的位置:首页 > 产品设计 > UI/UE

高级UI-PathMeasure 使用(路径绘制) 小船在波浪上运动 demo

2018-02-24 17:20 387 查看
目录

通过path画出矩形

通过path画出小船跟随波浪运动

使用DashPathEffect 进行路径绘制

基础介绍看这两个. 讲的都很不错

https://www.jianshu.com/p/9ee023755ce8

https://www.jianshu.com/p/3efa5341abcc

getSegment 的使用

先写个基本使用的demo

public class MyView extends View {

private static final String TAG = "MyView";
private int mViewWidth;
private int mViewHeight;
private Paint mDeafultPaint;

private Paint mPaint;

public MyView(Context context) {
super(context);
init();
}

private void init() {
mDeafultPaint = new Paint();
mDeafultPaint.setColor(Color.RED);
mDeafultPaint.setStrokeWidth(5);
mDeafultPaint.setStyle(Paint.Style.STROKE);

mPaint = new Paint();
mPaint.setColor(Color.DKGRAY);
mPaint.setStrokeWidth(2);
mPaint.setStyle(Paint.Style.STROKE);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 平移坐标系 让圆点到中间
canvas.translate(mViewWidth/2,mViewHeight/2);
// 画坐标线
canvas.drawLine(-canvas.getWidth(),0,canvas.getWidth(),0,mPaint);
canvas.drawLine(0,-canvas.getHeight(),0,canvas.getHeight(),mPaint);

//testForceClosed(canvas);

//testGetSegment(canvas);

//testGetSegmentMoveTo(canvas);

testNextContour(canvas);

}

private void testNextContour(Canvas canvas) {
Path path = new Path();
Path path1 = new Path();
Path path2 = new Path();
// 添加小矩形
path1.addRect(-100, -100, 100, 100, Path.Direction.CW);
// 添加大矩形
//path.addRect(-200, 200, 200, 600, Path.Direction.CW);
path2.addRect(-200, -200, 200, 200, Path.Direction.CW);
path.op(path1,path2, Path.Op.XOR);
canvas.drawPath(path,mDeafultPaint);

PathMeasure measure = new PathMeasure(path, false);

float len1 = measure.getLength();
// 跳转到下一条路径
measure.nextContour();

float len2 = measure.getLength();
Log.d(TAG,"len1 = "+len1);
Log.d(TAG,"len2 = "+len2);
}

private void testGetSegmentMoveTo(Canvas canvas) {
Path path = new Path();
// 创建Path并添加了一个矩形
path.addRect(-200, -200, 200, 200, Path.Direction.CW);

Path dst = new Path();
dst.lineTo(-300, -300);
// 将 Path 与 PathMeasure 关联
PathMeasure measure = new PathMeasure(path, false);

// 截取一部分存入dst中,并使用 moveTo 保持截取得到的 Path 第一个点的位置不变
//measure.getSegment(200, 600, dst, false);
measure.getSegment(0, 600, dst, true);

canvas.drawPath(path,mPaint);
// 绘制 dst
canvas.drawPath(dst, mDeafultPaint);
}

//使用getSegment
private void testGetSegment(Canvas canvas) {
Path path = new Path();
// 创建Path并添加了一个矩形
path.addRect(-200, -200, 200, 200, Path.Direction.CW);

Path dst = new Path();
// 将 Path 与 PathMeasure 关联
PathMeasure measure = new PathMeasure(path, false);

// 截取一部分存入dst中,并使用 moveTo 保持截取得到的 Path 第一个点的位置不变
measure.getSegment(200, 600, dst, false);

canvas.drawPath(path,mPaint);
// 绘制 dst
canvas.drawPath(dst, mDeafultPaint);
}

private void testForceClosed(Canvas canvas) {
Path path = new Path();

path.lineTo(0,200);
path.lineTo(200,200);
path.lineTo(200,0);

PathMeasure measure1 = new PathMeasure(path,false);
PathMeasure measure2 = new PathMeasure(path,true);

Log.e(TAG, "forceClosed=false length = "+measure1.getLength());
Log.e(TAG, "forceClosed=true length = "+measure2.getLength());

canvas.drawPath(path,mDeafultPaint);

}
}


01 通过path画出矩形

效果如下



代码如下:

这里我需要记住一个技巧 很多数据可以通过计算变成 0~1 或者-1~1区间 这样便于计算与理解

package android.mybzdemo.pathMeasure;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
* @author liuml
* @explain 利用getSegment 画
* @time 2018/2/6 16:55
*/

public class MyPathMeasureBase extends View {

private Path mPath;
private Paint mPaint;
private PathMeasure mPathMeasure;
private float mAnimatorValue;
private Path mDst;
private float mLength;

public MyPathMeasureBase(Context context) {
super(context);
}

public MyPathMeasureBase(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);

//---画笔
//ANTI_ALIAS_FLAG绘制时不允许使用反锯齿的标志。
mPaint =new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(5);

//---路径
mPath = new Path();
//画个矩形
RectF rect = new RectF(200, 200, 500, 500);
mPath.addRect(rect, Path.Direction.CW);

//---路径测量
mPathMeasure = new PathMeasure();
//和path关联 true getLength时候是包括闭合的
mPathMeasure.setPath(mPath,true);
mLength = mPathMeasure.getLength();

mDst = new Path();

//---动画
//这里有个技巧 把所有大数 或者小数 全部改造成0-1 或者-1 到1 这个区间 这样就好操作了
final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimatorValue = (float) animation.getAnimatedValue();
postInvalidate();
}
});

valueAnimator.setDuration(2000);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.start();
}

public MyPathMeasureBase(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDst.reset();
// 硬件加速的BUG
mDst.lineTo(0,0);
//通过不断添加结束点 来画出矩形
float stop = mLength * mAnimatorValue;
mPathMeasure.getSegment(0, stop, mDst, true);
canvas.drawPath(mDst, mPaint);

}

}


那么如何实现下面效果?



同样通过getSegment

//通过不断添加结束点 来画出矩形
float stop = mLength * mAnimatorValue;
float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * mLength));
mPathMeasure.getSegment(start, stop, mDst, true);


通过计算改变起始值 这里的原理是当画到一半的时候 不断改变起始值 最终让起始点和终点相等,即可实现.

02 通过path画出小船跟随波浪运动

效果



代码

package android.mybzdemo.pathMeasure;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.mybzdemo.R;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;

/**
* @author liuml
* @explain pathMeasure 使用  实现小船在波浪上面
* @time 2018/2/6 16:55
*/

public class BoatView2 extends View {
private static final String TAG = "MyBzView3";
ValueAnimator animator;
private Path mPath;
private Paint mPaint;
private static final int INT_WAVE_LENGTH = 1000;//波长
private int waveHeight = 60;//
private int mDeltax;//运动的值
private Bitmap boatBmp;//小船
private PathMeasure pathMeasure;

private float[] pos;
private float[] tan;
private Matrix mMatrix;
private float faction = 0;
private float length;
private float[] test;

public BoatView2(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);

mPaint = new Paint();
mPaint.setColor(Color.BLUE);
//用这种风格绘制的几何图形和文本将会被填充
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
//小船
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;//小船压缩一半
boatBmp = BitmapFactory.decodeResource(getResources(), R.drawable.timg, options);

pos = new float[2];
tan = new float[2];
test = new float[2];
mMatrix = new Matrix();

mPath = new Path();
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

//清除路径上的任何线条和曲线,使其为空。
mPath.reset();

int orgin = 800;
int halfLength = INT_WAVE_LENGTH / 2;
//起始点移动到左边屏幕的左边,根据不断改变起始点 动态的改变位置  mDeltax =0 让波浪不动
mPath.moveTo(-INT_WAVE_LENGTH + mDeltax * INT_WAVE_LENGTH, orgin);
//        Log.d(TAG, "onDraw: getWidth= " + getWidth());
//从起始点开始
int number = 0;
for (int i = -INT_WAVE_LENGTH; i < getWidth() + INT_WAVE_LENGTH;
i += INT_WAVE_LENGTH) {

//画一个圆点 这个圆点在交界处
//            Paint paint = new Paint();
//            paint.setColor(Color.RED);
//            canvas.drawCircle(-INT_WAVE_LENGTH + halfLength * number + (faction * INT_WAVE_LENGTH), orgin, 20, paint);
//            number++;

//使用rQuadTo 是相对位移 不用重新设置起始点
mPath.rQuadTo(halfLength / 2, waveHeight, halfLength, 0);
mPath.rQuadTo(halfLength / 2, -waveHeight, halfLength, 0);
}
//上面是画曲线

//下面是画出界面左边和右边的一条线 这样就可以闭合
mPath.lineTo(getWidth(), getHeight());
mPath.lineTo(0, getHeight());
mPath.close();//让线闭合

//把线画出来
canvas.drawPath(mPath, mPaint);

//下面画出小船
//先测量path
pathMeasure = new PathMeasure(mPath, false);
//先获取长度
length = pathMeasure.getLength();
//将距离标记为0 <= distance <= getLength(),然后进行计算  对应的位置和切线。如果没有路径,返回false,
//或者指定一条零长度的路径,在这种情况下,位置和切线变。  获取某一个点的tan
boolean posTan = pathMeasure.getPosTan(length * faction, pos, tan);

//方案一: 自己计算
//        Log.d(TAG,"pos[0] = " + pos[0] + "pos[1] = " +pos[1]);
//        Log.d(TAG,"tan[0] = " + tan[0] + "tan[1] = " +tan[1]);
if (posTan) {
// 方案一 :自己计算
// 将tan值通过反正切函数得到对应的弧度,在转化成对应的角度度数
/*  float degrees = (float) (Math.atan2(tan[1],tan[0])*180f / Math.PI);
mMatrix.postRotate(degrees, boatBmp.getWidth()/2, boatBmp.getHeight() / 2);//旋转
mMatrix.postTranslate(pos[0]- boatBmp.getWidth() / 2,pos[1] - boatBmp.getHeight());//平移
canvas.drawBitmap(boatBmp,mMatrix,mPaint);*/
}
//方案二:通过api获取matrix  获取指定长度的位置坐标及该点Matrix
//在这里做一些操作 如果这个点超过屏幕就让他返回来
float v = 1080 * faction;

float tmp;
if (faction > 0.6) {
tmp = 1 - faction;
} else {
tmp = faction;
}
//
pathMeasure.getMatrix(length * faction, mMatrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);

//需要减去小船本身的宽高 向上平移
mMatrix.preTranslate(boatBmp.getWidth() / 2, -boatBmp.getHeight());
canvas.drawBitmap(boatBmp, mMatrix, mPaint);

}

/**
* 开启动画
*/
public void startAnimator() {
/* animator = ValueAnimator.ofInt(0, INT_WAVE_LENGTH);
animator.setDuration(1000);
//设置为线性的
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);//无限循环
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDeltax = (int) animation.getAnimatedValue();
//根据不断改变起始点 动态的改变位置
postInvalidate();
}
});

animator.start();*/
//这里就之前说的 技巧  利用0到1的区间 转化数值
animator = ValueAnimator.ofFloat(0, 1);
animator.setDuration(10000);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
faction = (float) animation.getAnimatedValue();
Log.d(TAG, "onAnimationUpdate: =================faction =" + faction);
Log.d(TAG, "onAnimationUpdate: =================length =" + length);
postInvalidate();
}
});
animator.start();

}

public void stopanimator() {

animator.cancel();
}
}


在上一个 贝塞尔曲线 的波浪demo下继续写:

03 使用DashPathEffect 进行路径绘制

Effect效果

具体使用

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0907/3429.html

主要的原理:

PathEffect effect = new DashPathEffect(new float[] { length, length }, 0);


我们可以把DashPathEffect的第一个参数(float数组)只填入两个值,都是path的总长度length,那么按照上面对DashPathEffect的解释,第一次绘制一条实线就已经完全绘制完了,间隔的空白区间得不到绘制的机会。事实上这样绘制完全不能产生虚线效果,跟不设置PathEffect是一样的。

但是我们注意第三个参数即起始位置的偏移量现在是为0的。如果我们不为0呢?

比如为100,那么第一次绘制实线就会跳过100的距离,第一次的实线就只能绘制length-100的长度,那么空白区域就可以绘制100的长度,但是你看不见空白,所以我们只会感觉到绘制了一条length-100的路径。

如果你按照我们的思路去做实验,那么很快你就会想到,把这个偏移量也设置成length,那么第一次的实线区间将完全得不到绘制,而直接进入空白区间,而我们的空白区间总长度也是length,因此它占用了全部的绘制区间,所以此时什么也看不到。如果空白区间小于length的话,是可以看到一点实线的(因为空白区间完了紧接着就是实线了)。

所以,我们可以设置一个百分比,取名叫phase,phase的增长是从0 .0-1.0,如果我们利用属性动画来改变它,然后根据它动态的构造一个这样的DashPathEffect:

new DashPathEffect(new float[] { length, length }, length - phase * length);


这样就能产生跟踪绘制的效果。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  自定义veiw android