这里主要讲Path的填充方式 FillType 和 他的一个辅助工具类 PathMeasure
前文我们已经讲过如何用Path画出各种图形,《Android:视图绘制(三) ——Path介绍》,不了解的朋友可以移步。
FillType 填充方式
前文讲Paint的时候,我们就讲到过填充方式,不记得的朋友请移步《Android:视图绘制(一) ——基本的绘图操作Paint和Canvas》,Path的填充方式和Paint的不同,他提供了四种可供选择的值。
· FillType.WINDING: 默认值,取Path的所有区域
· FillType.EVEN_ODD: 取path所在并不相交的区域
· FillType.INVERSE_WINDING: 取path的外部区域
· FillType.INVERSE_EVEN_ODD: 取path外部和相交区域
INVERSE 相反取逆的意思,所以下面的两种填充方式是上面两种的相反形式。
Path提供了 setFillType(FillType ft)
方法,来设置填充方式。下面看图:
值得注意的是,当我们用了INVERSE 属性,取相交外部区域,会填充整个Canvas。
FillType系列还有一些函数。
boolean isInverseFillType() 是否是INVERSE 系列函数。
void toggleInverseFillType() 切换到相反的函数。即 WINDING 切换到 INVERSE_WINDING,反之亦然。
PathMeasure Path的辅助工具类,用于Path的计算的。其可以获得Path的长度和其中任意点的坐标,基于此,我们多是实现一些沿特定图形运动的动画。
其提供了两个构造方法。
PathMeasure()
PathMeasure(Path path, boolean forceClosed)
一种是无参的,另一种提供了两个参数。
参数一 path:因为PathMeasure是用于Path的计算的,所以PathMeasure一定要和我们要操作的Path绑定到一起,其内部会有一个全局的变量用于保存我们的Path,这个构造函数就是一个赋值的过程。
参数二 forceClosed:强制关闭,是一个Boolean的变量。官方解释:If true, then the path will be considered as “closed” even if its contour was not explicitly closed. 就是说,如果我们设置为true的话,就相当于强行的把Path闭合了,也就是相当于调用了Path的close。
PathMeasure 还提供了一个方法setPath(Path path, boolean forceClosed)
可以看到其参数和构造方法中的一样,其实就是对应上面那个无参的构造方法,用来设置值的。
下面来看一下 PathMeasure 中的主要方法。
float getLength() 返回Path的总长度。
boolean getPosTan(float distance, float pos[], float tan[]) 获得距Path起点distance长度的点的坐标,并赋值给pos[]
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) 获得距Path起点startD到stopD长度的path,并赋值给dst
还记得前面讲的贝塞尔曲线吗,今天用这个贝塞尔曲线加PathMeasure ,给大家模拟一个小球落地的例子:
Gif效果不是很好,有兴趣的可以拷到自己程序中看效果。直接上代码,注释很详细,就不在赘述了。
/**
* 模拟小球抛物线落地 Demo
*
* @author adong
* @date 2016-9-24 17:11:39
*/
public class CustomPaintView extends View {
private Paint mPaint;
private Path mPath;
private PathMeasure pathMeasure;
// path长度
private int mLenght;
// 当前距离
private int mCurrentPath;
// 当前点坐标
private float[] currentPosition;
// 用于存放小球的Bitmap
private Bitmap bitmap;
public CustomPaintView(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mPaint = new Paint();
mPath = new Path();
currentPosition = new float[2];
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.leida_point_small);
mPaint.setColor(Color.parseColor("#ff0000"));
mPaint.setStyle(Paint.Style.STROKE);
mPath.moveTo(0, 100);
// 贝塞尔曲线,用于模拟抛物线
mPath.cubicTo(300, 50, 600, 150, 800, 500);
mPath.quadTo(950, 400, 1000, 500);
pathMeasure = new PathMeasure();
pathMeasure.setPath(mPath, false);
// 获得长度
mLenght = (int) pathMeasure.getLength();
}
public CustomPaintView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomPaintView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath, mPaint);
if (mCurrentPath == 0) {
// 第一次启动
startAnimator();
} else {
// 在每次ValueAnimator的回调中更新小球的位置
canvas.drawBitmap(bitmap, currentPosition[0] - bitmap.getWidth() / 2,
currentPosition[1] - bitmap.getHeight() / 2, mPaint);
}
}
/**
* 计算每次的位置
*/
private void startAnimator() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, mLenght);
// 持续时间
valueAnimator.setDuration(5000);
// 加速插值器
valueAnimator.setInterpolator(new AccelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 获得当前的长度
mCurrentPath = (int) animation.getAnimatedValue();
// 获得对应点坐标
pathMeasure.getPosTan(mCurrentPath, currentPosition, null);
// 重绘
invalidate();
}
});
valueAnimator.start();
}
}