目录
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);
}
}
效果如下
代码如下:
这里我需要记住一个技巧 很多数据可以通过计算变成 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);
通过计算改变起始值 这里的原理是当画到一半的时候 不断改变起始值 最终让起始点和终点相等,即可实现.
效果
代码
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下继续写:
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);
这样就能产生跟踪绘制的效果。