Paint
Paint类包含关于如何绘制几何图形,文本和位图的样式和颜色信息。
//设置抗锯齿,如果不设置,加载位图的时候可能会出现锯齿状的边界,如果设置,边界就会变的稍微有点模糊,锯齿就看不到了
mPaint.setAntiAlias(boolean aa);
设置绘画的风格,用于控制原始图形的几何解释(除了drawBitmap,它总是假定为Fill)。
//设置绘制的风格,用于控制原语的几何体是如何解释的(除了drawBitmap,它总是假定为Fill)。
mPaint.setStyle(Style style);
//设置描边的宽度。 在发线模式下将0传递给笔划。 细线总是绘制一个独立于Canva矩阵的像素。设置绘画的笔画宽度,每当绘画时使用风格是Stroke或StrokeAndFill
mPaint.setStrokeWidth(3);
//用这种风格绘制的几何和文本将被填充,忽略所有
中风相关的设置在油漆。
Paint.Style.FILL
//用这种风格绘制的几何和文本将描边,尊重油漆上的笔画相关的领域。
Paint.Style.STROKE
//用这种风格绘制的几何和文字将同时填充和描边,尊重油漆上的笔触相关的领域。 如果几何形状逆时针方向,此模式可能会产生意想不到的结果。 此限制不适用于FILL或STROKE。
Paint.Style.FILL_AND_STROKE
mPaint.setColor(mColorBg);
path
Path类封装了由直线段,二次曲线和三次曲线组成的复合(多个轮廓)几何路径。可以使用canvas.drawPath(path,paint)绘制,可以是填充或描边(基于绘制的样式) ,或者它可以用于剪辑或在路径上绘制文本。
//清除路径中的任何线条和曲线,使其为空。这不会改变填充类型的设置。
animPath.reset();
//从最后一个点到指定点(x,y)添加一条线。 如果此轮廓未进行moveTo()调用,则第一个点将自动设置为(0,0)。
x一行结尾的x坐标--y一行结尾的y坐标
animPath.lineTo(0, 0);
将下一个轮廓的起点设置为点(x,y)。
@参数x新轮廓起点的x坐标
@参数y新轮廓起点的y坐标
sPath.moveTo
向路径添加一个封闭的圆形轮廓
参数x要添加到路径的圆的中心的x坐标
参数y要添加到路径中的圆的中心的y坐标
radius要添加到路径的圆的半径
dir旋转圆的轮廓的方向
sPath.addCircle(50, 50, 60, Path.Direction.CW);
顺时针
Path.Direction.CW
逆时针
Path.Direction.CCW
创建一个空的PathMeasure对象。 要使用它来测量路径的长度,和/或沿着它找到位置和切线,请调用setPath。
请注意,一旦一个路径与度量对象相关联,如果路径随后被修改并且使用度量对象,那么它是未定义的。 如果修改路径,则必须使用路径调用setPath。
PathMeasure pathMeasure = new PathMeasure();
指定一个新的路径,或者为null,否则为空。
pathMeasure.setPath(sourcePath, false);
//返回当前轮廓的总长度,如果没有路径与此度量对象关联,则返回0。
pathMeasure.getLength()
移动到路径中的下一个轮廓。 如果存在则返回true,否则返回false。
pathMeasure.nextContour();
//经过上面这段计算duration代码的折腾 需要重新初始pathMeasure
pathMeasure.setPath(sourcePath, false);
//每段path走完后,要补一下 某些情况会出现 animPath不满的情况(当然也可以设置一段,就可以达到进度条的效果)
*给定起止距离,返回到中间段。 如果段是零长度,则返回false,否则返回true。 startD和stopD被固定为合法值(0..getLength())。 如果startD> = stopD,则返回false(并保持dst不变)。如果startWithMoveTo为true,则使用moveTo开始该段。
* 在{@link android.os.Build.VERSION_CODES#KITKAT}和更早的发行版上,得到的路径可能不会显示在硬件加速的画布上。 一个简单的解决方法是向这个路径添加一个单独的操作,比如 dst.rLineTo(0,0) code>。 p>
pathMeasure.getSegment(0, pathMeasure.getLength(), animPath, true);
给定一个起点和终点距离,返回中间段。 如果段是零长度,则返回false,否则返回true。 startD和stopD被固定为合法值(0..getLength())。
如果startD> = stopD,则返回false(并保持dst不变)。
如果startWithMoveTo为true,则使用moveTo开始片段。
在{@link android.os.Build.VERSION_CODES#KITKAT}和更早版本上,结果路径可能不会显示在硬件加速画布上。 一个简单的解决方法是向这个路径添加一个单独的操作,比如 dst.rLineTo(0,0) code>。 p>
//animPath替换成mStonePath
animPath.set(mStonePath);
view
当这个视图的大小改变时,这在layout期间被调用。 如果刚刚添加到视图层次结构中,则使用旧值0调用。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mPaddingLeft = getPaddingLeft();
mPaddingTop = getPaddingTop();
}
返回这个视图的左边填充。 如果有插入和启用滚动条,则此值可能还包括显示滚动条所需的空间。
mPaddingLeft = getPaddingLeft();
mPaddingTop = getPaddingTop();
invalidate
invalidate,请求重新draw,只会绘制调用者本身。
* 这必须从UI线程调用。 要从非UI线程调用,请调用{@link #postInvalidate()}。
public void invalidate() {
invalidate(true);
}
这是invalidate()工作实际发生的地方。 一个完整的invalidate()将导致绘图缓存失效,但是可以使用invalidateCache设置为false来调用此函数,以跳过不需要它的情况下的失效步骤(例如,保持与 相同的内容)。
*
* @param invalidateCache此视图的绘图缓存是否也应该失效。 对于完全无效,这通常是正确的,但是如果视图的内容或维度没有改变,则可以将其设置为假。
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
Canvas
Canvas类保存“draw”调用。 要绘制东西,你需要4个基本组件:一个位图来保存像素,一个Canvas来承载绘制调用(写入到位图),一个绘制基元(例如Rect,Path,文本,位图)和一个绘制 描述绘图的颜色和样式)。
//用指定的转换对当前矩阵进行预处理
canvas.translate(mPaddingLeft, mPaddingTop);
*使用指定的绘图绘制指定的路径。 该路径将根据画笔中的样式进行填充或框定。
*
* @param path要绘制的路径
* @param paint用于绘制路径的油漆
canvas.drawPath(mSourcePath, mPaint);
canvas.drawPath(mAnimPath, mPaint);
绘制前景,mAnimPath不断变化,不断重绘View的话,就会有动画效果。
PathAnimView
一个路径动画的View,利用源Path绘制“底”,利用动画Path 绘制 填充动画
一个SourcePath 内含多段Path,循环取出每段Path,并做一个动画
需要做动画的源Path
用于绘制动画的Path
背景色
前景色
PathAnimHelper
介绍:一个自定义View Path动画的工具类
一个SourcePath 内含多段(一段)Path,循环取出每段Path,并做一个动画,
/**
* 一个SourcePath 内含多段Path,循环取出每段Path,并做一个动画
* 自定义动画的总时间
* 和是否循环
*
* @param view 需要做动画的自定义View
* @param sourcePath 源Path
* @param animPath 自定义View用这个Path做动画
* @param totalDuaration 动画一共的时间
* @param isInfinite 是否无限循环
*/
protected void startAnim(View view, Path sourcePath, Path animPath, long totalDuaration, boolean isInfinite) {
if (view == null || sourcePath == null || animPath == null) {
return;
}
PathMeasure pathMeasure = new PathMeasure();
//pathMeasure.setPath(sourcePath, false);
//先重置一下需要显示动画的path
animPath.reset();
animPath.lineTo(0, 0);
pathMeasure.setPath(sourcePath, false);
//这里仅仅是为了 计算一下每一段的duration
int count = 0;
while (pathMeasure.getLength() != 0) {
pathMeasure.nextContour();
count++;
}
//经过上面这段计算duration代码的折腾 需要重新初始化pathMeasure
pathMeasure.setPath(sourcePath, false);
loopAnim(view, sourcePath, animPath, totalDuaration, pathMeasure, totalDuaration / count, isInfinite);
}
/**
* 循环取出每一段path ,并执行动画
*
* @param animPath 自定义View用这个Path做动画
* @param pathMeasure 用于测量的PathMeasure
*/
protected void loopAnim(final View view, final Path sourcePath, final Path animPath, final long totalDuaration, final PathMeasure pathMeasure, final long duration, final boolean isInfinite) {
//动画正在运行的话,先stop吧。万一有人要使用新动画呢,(正经用户不会这么用。)
stopAnim();
mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.setDuration(duration);
mAnimator.setRepeatCount(ValueAnimator.INFINITE);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//Log.i("TAG", "onAnimationUpdate");
//增加一个callback 便于子类重写搞事情
onPathAnimCallback(view, sourcePath, animPath, pathMeasure, animation);
//通知View刷新自己
view.invalidate();
}
});
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
//Log.w("TAG", "onAnimationRepeat: ");
//每段path走完后,要补一下 某些情况会出现 animPath不满的情况
pathMeasure.getSegment(0, pathMeasure.getLength(), animPath, true);
//绘制完一条Path之后,再绘制下一条
pathMeasure.nextContour();
//长度为0 说明一次循环结束
if (pathMeasure.getLength() == 0) {
if (isInfinite) {//如果需要循环动画
animPath.reset();
animPath.lineTo(0, 0);
pathMeasure.setPath(sourcePath, false);
} else {//不需要就停止(因为repeat是无限 需要手动停止)
animation.end();
}
}
}
});
mAnimator.start();
}
根据ArrayList path 解析
/**
* 根据ArrayList path 解析
*
* @param path
* @return
*/
public static Path getPathFromArrayFloatList(ArrayList path) {
Path sPath = new Path();
for (int i = 0; i < path.size(); i++) {
float[] floats = path.get(i);
sPath.moveTo(floats[0], floats[1]);
sPath.lineTo(floats[2], floats[3]);
}
return sPath;
}
/**
* 从R.array.xxx里取出点阵,
*
* @param context
* @param arrayId
* @param zoomSize
* @return
*/
public static Path getPathFromStringArray(Context context, int arrayId, float zoomSize) {
Path path = new Path();
String[] points = context.getResources().getStringArray(arrayId);
for (int i = 0; i < points.length; i++) {
String[] x = points[i].split(",");
for (int j = 0; j < x.length; j = j + 2) {
if (j == 0) {
path.moveTo(Float.parseFloat(x[j]) * zoomSize, Float.parseFloat(x[j + 1]) * zoomSize);
} else {
path.lineTo(Float.parseFloat(x[j]) * zoomSize, Float.parseFloat(x[j + 1]) * zoomSize);
}
}
}
return path;
}
float[][] LETTERS = new float[][]{
new float[]{
// A
24, 0, 1, 22,
1, 22, 1, 72,
24, 0, 47, 22,
47, 22, 47, 72,
1, 48, 47, 48
},
new float[]{
// B
0, 0, 0, 72,
0, 0, 37, 0,
37, 0, 47, 11,
47, 11, 47, 26,
47, 26, 38, 36,
38, 36, 0, 36,
38, 36, 47, 46,
47, 46, 47, 61,
47, 61, 38, 71,
37, 72, 0, 72,
},....
};
进度条效果
@Override
public void onPathAnimCallback(View view, Path sourcePath, Path animPath, PathMeasure pathMeasure, ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
//获取一个段落
float end = pathMeasure.getLength() * value;
float begin = (float) (end - ((0.5 - Math.abs(value - 0.5)) * pathMeasure.getLength()));
animPath.reset();
animPath.lineTo(0, 0);
pathMeasure.getSegment(begin, end, animPath, true);
}