学习资料:1、Canvas之translate、scale、rotate、skew方法讲解!
2、安卓自定义View教程
3、《android群英传》徐宜生
4、Android Canvas的save(),saveLayer()和restore()浅谈
5、Approximate a circle with cubic Bézier curves
canvas基础:
/***
* ①和②的顺序是不能改变的。
* 原因:translate(x,y)是将整个画布(整个屏幕)平移,
* 即:先将画布平移到指定位置,再进行绘制。
*
* @param canvas 画布
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initPaints();
canvas.drawColor(Color.BLUE);
canvas.translate(100, 100);//①
canvas.drawRect(0, 0, 200, 200, mPaint1);//②
}
/***
* 画空心矩形需要设置Paint.Style.STROKE,默认是FILL.
*/
mPaint1.setStyle(Paint.Style.STROKE);
关于translate:
关于scale:
scale共两个方法,源代码如下:
/**
* Preconcat the current matrix with the specified scale.
*
* @param sx The amount to scale in X
* @param sy The amount to scale in Y
*/
public void scale(float sx, float sy) {
native_scale(mNativeCanvasWrapper, sx, sy);
}
/**
* Preconcat the current matrix with the specified scale.
*
* @param sx The amount to scale in X
* @param sy The amount to scale in Y
* @param px The x-coord for the pivot point (unchanged by the scale)
* @param py The y-coord for the pivot point (unchanged by the scale)
*/
public final void scale(float sx, float sy, float px, float py) {
translate(px, py);
scale(sx, sy);
translate(-px, -py);
}
所以说,代码:
canvas.scale(a, b, 300, 300);//向左偏移300,向下平移300
canvas.drawRect(rect, mPaint1);
和
canvas.translate(300,300);
canvas.scale(a,b);
canvas.translate(-300,-300);
效果图如下:
上面的缩放比例都是正数,当缩放比例为负数的时候,会根据缩放中心轴(x,y轴)进行翻转,效果图如下:
关于rotate(旋转):
和scale差不多,自己也实现了一个clock的demo,代码如下
private void drawClock(Canvas canvas) {
mPaint1.setColor(Color.RED);
mPaint1.setStyle(Paint.Style.STROKE);
mPaint1.setStrokeWidth(10);
canvas.translate(100, 100);
canvas.drawOval(new RectF(0, 0, 600, 600), mPaint1);
float shortLength = 30;
float longLength = 50;
for (int i = 0; i < 60; i++) {
if (i % 5 == 0) {
mPaint1.setStrokeWidth(10);
canvas.drawLine(300, 0, 300, longLength, mPaint1);
} else {
mPaint1.setStrokeWidth(5);
canvas.drawLine(300, 0, 300, shortLength, mPaint1);
}
canvas.rotate(6, 300, 300);
}
}
效果图:
关于skew:
public void skew (float sx, float sy)
参数含义:
float sx:将画布在x方向上倾斜相应的角度,sx倾斜角度的tan值,
float sy:将画布在y轴方向上倾斜相应的角度,sy为倾斜角度的tan值.
下面的内容中用到的图片来自:安卓自定义View教程
android屏幕的坐标系:
MotionEvent中 get 和 getRaw 的区别
(摘自安卓自定义View教程)
角度问题:
图层的混合模式:
用Paint.setXfermode()可以指定不同的PorterDuff.Mode。
view的绘制流程:
自定义view的构造函数:
public Demo1(Context context) {//使用new Demo1()时会调用
super(context);
}
public Demo1(Context context, AttributeSet attrs) {//在xml文件中使用时会调用
super(context, attrs);
}
view的测量,onMeasure()方法:
测量的模式分为三种:
EXACTLY: 精确测量模式,将layout_width或layout_height指定为具体数值时,系统会使用此模式,也是系统的默认测量模式。
AT_MOST:最大值模式,当layout_width或layout_height为wrap_content时,系统会调用此模式。
UNSPECIFIED:不指定测量模式,一般在自定义view时会使用。
NOTE: 只有自定义的view需要支持wrap_content时,才需要重写onMeasure()方法。
下面是重写onMeasure()方法的模板写法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
/***
* 先从MeasureSpec对象中取出具体的测量模式和大小,
* 当specMode为EXACTLY时(指定了具体值),那就让result为指定的值,
* 如果不是,则给他一个默认值,
* 然后判断是不是采用了AT_MOST模式(用了wrap_content属性),
* 然后返回specSize和result的最小值。
*
* @param widthMeasureSpec 宽度MeasureSpec对象
* @return 测量的宽度
*/
private int measureWidth(int widthMeasureSpec) {
int result;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
private int measureHeight(int heightMeasureSpec) {
int result;
int specMode = MeasureSpec.getMode(heightMeasureSpec);
int specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
canvas常用操作速查表:
saveLayer()
Android Canvas的save(),saveLayer()和restore()浅谈
saveLayer
Canvas 在一般的情况下可以看作是一张画布,所有的绘图操作如drawBitmap, drawCircle都发生在这张画布上,这张画板还定义了一些属性比如Matrix,颜色等等。但是如果需要实现一些相对复杂的绘图操作,比如多层动 画,地图(地图可以有多个地图层叠加而成,比如:政区层,道路层,兴趣点层)。Canvas提供了图层(Layer)支持,缺省情况可以看作是只有一个图 层Layer。如果需要按层次来绘图,Android的Canvas可以使用SaveLayerXXX, Restore 来创建一些中间层,对于这些Layer是按照“栈结构“来管理的。
创建一个新的Layer到“栈”中,可以使用saveLayer, savaLayerAlpha, 从“栈”中推出一个Layer,可以使用restore,restoreToCount。但Layer入栈时,后续的DrawXXX操作都发生在这个 Layer上,而Layer退栈时,就会把本层绘制的图像“绘制”到上层或是Canvas上,在复制Layer到Canvas上时,可以指定Layer的 透明度(Layer),这是在创建Layer时指定的:public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags)本例Layers 介绍了图层的基本用法:Canvas可以看做是由两个图层(Layer)构成的,为了更好的说明问题,我们将代码稍微修改一下,缺省图层绘制一个红色的 圆,在新的图层画一个蓝色的圆,新图层的透明度为0×88。
drawPicture
picture:
A Picture records drawing calls (via the canvas returned by beginRecording) and can then play them back into Canvas (via draw(Canvas) or drawPicture(Picture)).For most content (e.g. text, lines, rectangles), drawing a sequence from a picture can be faster than the equivalent API calls, since the picture performs its playback without incurring any method-call overhead.
Picture记录绘画调用(通过开始录制后被返回的画布),然后把他们(绘画的一些调用)放回canvas(通过调用draw(canvas) 或 drawPicture(Picture)方法)。对于大部分内容来说,从picture绘制一个序列会比调用同样可以实现此效果的api要快,因为picture执行他的回放功能,不会导致任何因方法调用而产生的花费。
Picture的相关方法:
使用Picture前请关闭硬件加速(很重要,被坑了)!
在AndroidMenifest文件中application节点下添上android:hardwareAccelerated=”false”以关闭整个应用的硬件加速。
beginRecording 和 endRecording 是成对使用的,一个开始录制,一个是结束录制,两者之间的操作将会存储在Picture中。
绘制Bitmap
绘制文字
看官方API(比较简单)
Path之基本操作
请关闭硬件加速,以免引起不必要的问题
常用方法表
moveTo只改变下次操作的起点,在执行完第一次LineTo的时候,本来的默认点位置是A(200,200),但是moveTo将其改变成为了C(200,100),所以在第二次调用lineTo的时候就是连接C(200,100) 到 B(200,0) 之间的直线.
setLastPoint是重置上一次操作的最后一个点,在执行完第一次的lineTo的时候,最后一个点是A(200,200),而setLastPoint更改最后一个点为C(200,100),所以在实际执行的时候,第一次的lineTo就不是从原点O到A(200,200)的连线了,而变成了从原点O到C(200,100)之间的连线了。
注意: close的作用是封闭路径,与连接当前最后一个点和第一个点并不等价。如果连接了最后一个点和第一个点仍然无法形成封闭图形,则close什么 也不做。
关于Direction
addPath(…)
addArc与arcTo
offset(…)
Path之贝塞尔曲线
1、一阶曲线其实就是前面讲解过的lineTo。
2、二阶曲线对应的方法是quadTo(…)(ps:具体用法见API,作用是使画的曲线更平滑),是由两个数据点,一个控制点构成.
原理:(D、E两点是任取得)
3、三阶曲线对应的方法是cubicTo,由两个数据点和两个控制点来控制曲线状态。
NOTE:三阶曲线相比于二阶曲线可以制作更加复杂的形状,但是对于高阶的曲线,用低阶的曲线组合也可达到相同的效果,就是传说中的降阶。因此我们对贝塞尔曲线的封装方法一般最高只到三阶曲线。
贝塞尔曲线相关函数使用方法
效果图:(有点卡顿)
代码实现(两阶贝塞尔曲线):
public class Demo2 extends View {
private PointF mStartPoint = new PointF();
private PointF mEndPoint = new PointF();
private PointF mControlPoint = new PointF();
private float mCenterX, mCenterY;
private Path mPath = new Path();
public Demo2(Context context) {
super(context);
}
public Demo2(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Demo2(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(21)
public Demo2(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w / 2;
mCenterY = h / 2;
mStartPoint.x = mCenterX - 200;
mStartPoint.y = mCenterY;
mEndPoint.x = mCenterX + 200;
mEndPoint.y = mCenterY;
mControlPoint.x = mCenterX;
mControlPoint.y = mCenterY - 200;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
mPath.reset();
mPath.moveTo(mStartPoint.x, mStartPoint.y);
mPath.quadTo(mControlPoint.x, mControlPoint.y, mEndPoint.x, mEndPoint.y);
canvas.drawPath(mPath, paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
mControlPoint.set(x, y);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
break;
}
invalidate();
return true;
}
}
那什么时候会使用到贝塞尔曲线呢?下面是大神总结的三种情况:
贝塞尔曲线的主要优点是可以实时控制曲线状态,并可以通过改变控制点的状态实时让曲线进行平滑的状态变化。
path的rXxx方法
和不带r的方法的区别:rXxx方法的坐标使用的是相对位置(基于当前点的位移),而之前方法的坐标是绝对位置(基于当前坐标系的坐标)。
加深理解:若当前点的位置在 (100,100) , 使用了 rLineTo(100,200) 之后,下一个点的位置是在当前点的基础上加上偏移量得到的,即 (100+100, 100+200) 这个位置。
android中的填充模式:
布尔操作(API19)
布尔操作是两个Path之间的运算,主要作用是用一些简单的图形通过一些规则合成一些相对比较复杂,或难以直接得到的图形。
如太极中的阴阳鱼,如果用贝塞尔曲线制作的话,可能需要六段贝塞尔曲线才行,而在这里我们可以用四个Path通过布尔运算得到,而且会相对来说更容易理解一点:
布尔运算方法
重置路径
重置Path有两个方法,分别是reset和rewind,两者区别主要有一下两点:
选择权重: FillType > 数据结构
因为“FillType”影响的是显示效果,而“数据结构”影响的是重建速度。