自定义View学习摘要笔记(一)

学习资料: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:

自定义View学习摘要笔记(一)_第1张图片

关于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);

效果图如下:
自定义View学习摘要笔记(一)_第2张图片
上面的缩放比例都是正数,当缩放比例为负数的时候,会根据缩放中心轴(x,y轴)进行翻转,效果图如下:
自定义View学习摘要笔记(一)_第3张图片

关于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);
        }
    }

效果图:
自定义View学习摘要笔记(一)_第4张图片

关于skew:

public void skew (float sx, float sy)
参数含义:
float sx:将画布在x方向上倾斜相应的角度,sx倾斜角度的tan值,
float sy:将画布在y轴方向上倾斜相应的角度,sy为倾斜角度的tan值.

下面的内容中用到的图片来自:安卓自定义View教程

android屏幕的坐标系:

自定义View学习摘要笔记(一)_第5张图片

MotionEvent中 get 和 getRaw 的区别

(摘自安卓自定义View教程)
自定义View学习摘要笔记(一)_第6张图片

角度问题:

自定义View学习摘要笔记(一)_第7张图片

图层的混合模式:

用Paint.setXfermode()可以指定不同的PorterDuff.Mode。
自定义View学习摘要笔记(一)_第8张图片

view的绘制流程:

自定义View学习摘要笔记(一)_第9张图片

自定义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常用操作速查表:

自定义View学习摘要笔记(一)_第10张图片

saveLayer()

自定义View学习摘要笔记(一)_第11张图片
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。
自定义View学习摘要笔记(一)_第12张图片

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”以关闭整个应用的硬件加速。
自定义View学习摘要笔记(一)_第13张图片
beginRecording 和 endRecording 是成对使用的,一个开始录制,一个是结束录制,两者之间的操作将会存储在Picture中。
自定义View学习摘要笔记(一)_第14张图片
自定义View学习摘要笔记(一)_第15张图片

绘制Bitmap

自定义View学习摘要笔记(一)_第16张图片

自定义View学习摘要笔记(一)_第17张图片

自定义View学习摘要笔记(一)_第18张图片
自定义View学习摘要笔记(一)_第19张图片
自定义View学习摘要笔记(一)_第20张图片
自定义View学习摘要笔记(一)_第21张图片

绘制文字

看官方API(比较简单)

Path之基本操作

请关闭硬件加速,以免引起不必要的问题
常用方法表
自定义View学习摘要笔记(一)_第22张图片
自定义View学习摘要笔记(一)_第23张图片
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

自定义View学习摘要笔记(一)_第24张图片

addPath(…)

自定义View学习摘要笔记(一)_第25张图片

addArc与arcTo

这里写图片描述
自定义View学习摘要笔记(一)_第26张图片
自定义View学习摘要笔记(一)_第27张图片

offset(…)

自定义View学习摘要笔记(一)_第28张图片

Path之贝塞尔曲线

自定义View学习摘要笔记(一)_第29张图片
1、一阶曲线其实就是前面讲解过的lineTo。
2、二阶曲线对应的方法是quadTo(…)(ps:具体用法见API,作用是使画的曲线更平滑),是由两个数据点,一个控制点构成.
原理:(D、E两点是任取得)
自定义View学习摘要笔记(一)_第30张图片
3、三阶曲线对应的方法是cubicTo,由两个数据点和两个控制点来控制曲线状态。
自定义View学习摘要笔记(一)_第31张图片

NOTE:三阶曲线相比于二阶曲线可以制作更加复杂的形状,但是对于高阶的曲线,用低阶的曲线组合也可达到相同的效果,就是传说中的降阶。因此我们对贝塞尔曲线的封装方法一般最高只到三阶曲线。

贝塞尔曲线相关函数使用方法

效果图:(有点卡顿)
自定义View学习摘要笔记(一)_第32张图片
代码实现(两阶贝塞尔曲线):

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;
    }
}

那什么时候会使用到贝塞尔曲线呢?下面是大神总结的三种情况:
自定义View学习摘要笔记(一)_第33张图片

贝塞尔曲线的主要优点是可以实时控制曲线状态,并可以通过改变控制点的状态实时让曲线进行平滑的状态变化。

path的rXxx方法

和不带r的方法的区别:rXxx方法的坐标使用的是相对位置(基于当前点的位移),而之前方法的坐标是绝对位置(基于当前坐标系的坐标)。
加深理解:若当前点的位置在 (100,100) , 使用了 rLineTo(100,200) 之后,下一个点的位置是在当前点的基础上加上偏移量得到的,即 (100+100, 100+200) 这个位置。

android中的填充模式:

自定义View学习摘要笔记(一)_第34张图片
自定义View学习摘要笔记(一)_第35张图片

布尔操作(API19)

布尔操作是两个Path之间的运算,主要作用是用一些简单的图形通过一些规则合成一些相对比较复杂,或难以直接得到的图形。
如太极中的阴阳鱼,如果用贝塞尔曲线制作的话,可能需要六段贝塞尔曲线才行,而在这里我们可以用四个Path通过布尔运算得到,而且会相对来说更容易理解一点:自定义View学习摘要笔记(一)_第36张图片
自定义View学习摘要笔记(一)_第37张图片

布尔运算方法

自定义View学习摘要笔记(一)_第38张图片

重置路径

重置Path有两个方法,分别是reset和rewind,两者区别主要有一下两点:
自定义View学习摘要笔记(一)_第39张图片
选择权重: FillType > 数据结构

因为“FillType”影响的是显示效果,而“数据结构”影响的是重建速度。

你可能感兴趣的:(Android)