自定义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:

这里写图片描述

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

效果图:
jack

关于skew:

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

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

android屏幕的坐标系:

这里写图片描述

MotionEvent中 get 和 getRaw 的区别

(摘自安卓自定义View教程)
http://www.gcssloop.com/customview/CoordinateSystem/

角度问题:

这里写图片描述

图层的混合模式:

用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”影响的是显示效果,而“数据结构”影响的是重建速度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值