一:自定义view概述

1、自定义view

自定义view的一般步奏是:

1、 继承view、
2、 重写构造方法、
3、 重写onMeasure方法、
4、 重写onDraw方法。
5、 也可以使用自定义属性:

使用的优先级
直接在XML中定义>style定义>由defStyleAttr和defStyleRes指定的默认值>直接在Theme中指定的值
1、通过<declare-styleable>为自定义View添加属性
2、在xml中为相应的属性指定属性值

  • 在layout布局文件中
  • 设置style并在style中设置属性
  • 在theme中指定在当前Application或Activity中属性的默认值

3、在运行时(一般为构造函数)获取属性值
我们要获取的属性值都是通过这个函数返回的TypedArray获得的

TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes){}

四个参数:

  • set:属性值的集合
  • attrs:属性资源的ID
  • defStyleAttr:当前Theme中的一个attribute,是一个指向style的一个引用,当在layout xml中和style中都没有为View指定属性时,会从Theme中这个attribute指向的Style中查找相应的属性值。如果这个参数传入0表示不在Theme中搜索默认值
  • defStyleRes:指向一个Style的资源,但是仅在defStyleAttr为0或defStyleAttr不为0但Theme中没有为defStyleAttr属性赋值时起作用。也是指定自定义view的默认属性。

4、将获取到的属性值应用到View

1、构造函数
  • 一个参数 (Context context):
    直接new一个view的时候调用

  • 两个参数 (Context context, AttributeSet attrs):
    在layout布局文件中使用的时候会调用,关于它的所有属性(包括自定义属性,还有在这个layout中通过style为view添加的属性)都会包含在attrs中传递进来。

  • 三个参数 (Context context, AttributeSet attrs, int defStyleAttr):
    不会自动调用,一般在两个参数的构造函数中手动调用,重写这个构造函数的意义是,为自定义view提供一些默认的属性。
    其中defStyleAttr,是默认的属性,它的取值是,是在Theme中style的值。

  • 四个参数 (Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes):
    minSdkVersion>=21才可以使用

一般来说,你只需实现前两个

3、测量view的大小(onMeasure)

当宽高设置为match_parent、wrap_content时,系统帮我们测量的宽高都是match_parent的长度,所以在设置了wrap_content时,需要手动进行测量(重写onMeasure())。

在重新onMeasure方法时,需要注意 MeasureSpec 。 MeasureSpec封装了父布局传递给子布局的布局要求。一个MeasureSpec由大小和模式组成。

它有三种模式:
EXACTLY(精确): 父控件决定子控件的确切大小,子控件将被限定在给定的边界里而忽略它本身大小
AT_MOST(最多): 子控件至多达到指定大小的值,
UNSPECIFIED(未指定): 父控件不对子控件施加任何束缚,子元素可以得到任意想要的大小

最简单的映射关系是:

  • wrap_parent -> MeasureSpec.AT_MOST
  • match_parent -> MeasureSpec.EXACTLY
  • 具体值 -> MeasureSpec.EXACTLY
4、基本实现

自定义属性 定义(res/values/attrs.xml 下)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SelfView">
        <attr name="innerColor" format="color"/>
    </declare-styleable>
    <attr name="defaultStyle" format="reference"/>
</resources>

使用,(res/values/style.xml 下)

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="defaultStyle">@style/defaultAttrStyle</item>
    </style>

    <style name="defaultAttrStyle">
        <item name="innerColor">@color/colorPrimary</item>
    </style>

    <style name="defaultResStyle">
        <item name="innerColor">@color/colorPrimary</item>
    </style>

</resources>

重写构造方法、onMeasure方法



public class SelfView extends View {
    private final int DEFAULT_COLOR= Color.RED;
    private final int DEFAULT_SIZE= 320;
    private int color;
    private int length;

    /**
     * 一般在直接New一个View的时候调用。
     */
    public SelfView(Context context) {
        this(context,null);
    }

    /**
     * 一般在layout文件中使用的时候会调用,关于它的所有属性(包括自定义属性)都会包含在attrs中传递进来。
     */
    public SelfView(Context context, AttributeSet attrs) {
        this(context, attrs,R.attr.defaultStyle);
    }

    public SelfView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //使用TypedArray 读取属性值
        TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.SelfView,defStyleAttr,R.style.defaultResStyle);
        color=typedArray.getColor(R.styleable.SelfView_innerColor,DEFAULT_COLOR);
        typedArray.recycle();
        init(context);
    }

/*    public SelfView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }*/
}

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getMeasureSpec(widthMeasureSpec),getMeasureSpec(heightMeasureSpec));
    }

    private int getMeasureSpec(int widthMeasureSpec) {
        int specMode=MeasureSpec.getMode(widthMeasureSpec);
        int specSize=MeasureSpec.getSize(widthMeasureSpec);
        int result=length;
        switch (specMode){
            case MeasureSpec.EXACTLY:
                result=specSize;
                break;
            case MeasureSpec.AT_MOST:
                result=Math.min(specSize,length);
                break;
            case MeasureSpec.UNSPECIFIED:
                break;
        }
        return result;
    }
}

参考:安卓自定义View进阶-分类与流程深入理解View的构造函数理解View的构造函数

2、绘图有关的类

1、Paint

Paint类用于定义绘图时的参数,主要包含颜色、文本、图形样式、位图模式、滤镜等几个方面。
1、图形样式包含绘制的图形是空心样式还是实心样式,同时还能指定落笔和收笔时的笔触效果。
Paint类与图形样式相关的方法有:

  • setStyle(Paint.Style style):设置绘制的图形是空心样式还是实心样式,默认为实心样式。style 的可选值有:

  • FILL 实心样式

  • FILL_AND_STROKE 同时使用实心样 式和空心样式

  • STROKE 空心样式,绘制时只有线条而无填充效果

  • setStrokeJoin(Paint.Join join):当绘图样式为 STROKE、FILL_AND_STROKE 时,该方法用于指定线条连接处的拐角样式,能使绘制的图形 更加平滑。默认值为 MITER。可选值如下:

  • MITER

  • BEVEL

  • ROUND

MITER

ROUND

BEVEL(FILL_AND_STROKE )

BEVEL(STROKE )

BEVEL(FILL)
  • setStrokeCap(Paint.Cap cap):该方法用于设置落笔时的样式,控制我们的画笔在离开画板时留下的最后一点图形,可选值如下:
    BUTT,ROUND, SQUARE

参考:Android自定义组件开发详解 pdf

2、弧度和角度

角度=(弧度/Math.PI)*180
弧度转化为角度:Math.toDegrees()
角度转化为弧度:Math.toRadians()
反正切函數:Math.atan(),返回值是弧度
正切函数:Math.tan(),參數是弧度。

3、Canvas 的操作

⑴位移(translate)

 translate(float dx, float dy)

translate是坐标系的移动,是基于当前位置的移动,而不是每次基于屏幕左上角的(0,0)点移动

⑵缩放(scale)

scale(float sx, float sy)
scale(float sx, float sy, float px, float py)

缩放的中心默认为坐标原点 ,当缩放比例为负数的时候会根据缩放中心轴进行翻转

⑶旋转(rotate)

rotate(float degrees)
rotate(float degrees, float px, float py)

默认的旋转中心依旧是坐标原点。

所有的画布操作都只影响后续的绘制,对之前已经绘制过的内容没有影响。

(4)、保存save、回滚restore

大多数情况下只需要记住下面的步骤就可以了:

   save();      //保存状态
   ...          //具体操作
   restore();   //回滚到之前的状态

画布的操作是不可逆的,而且很多画布操作会影响后续的步骤,所以会对画布的一些状态进行保存和回滚。

通过旋转画布,可以得到如下效果:

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.GRAY);
        canvas.save();
        canvas.translate(width/2,height/2);
        canvas.drawCircle(0,0,outerRadius,linePaint);
        if(desBitmap!=null && !desBitmap.isRecycled()){
            for (int i=0;i<6;i++){
                canvas.drawBitmap(desBitmap,-innerRadius,outerRadius-innerRadius,null);
                canvas.rotate(60);
            }
        }
        canvas.restore();
    }
旋转画布

参考:安卓自定义View进阶-Canvas之画布操作

4、图层

  • canvas默认就有一个layer,当我们平时调用canvas的各种drawXXX()方法时,其实是把所有的东西都绘制到canvas这个默认的layer上面。

  • 我们还可以通过canvas.saveLayer()新建一个layer,新建的layer放置在canvas默认layer的上部,当我们执行了canvas.saveLayer()之后,我们所有的绘制操作都绘制到了我们新建的layer上

  • 用canvas.saveLayer()方法产生的layer所有像素的ARGB值都是(0,0,0,0),即canvas.saveLayer()方法产生的layer初始时时完全透明的。

  • canvas.saveLayer()方法会返回一个int值,用于表示layer的ID,在我们对这个新layer绘制完成后可以通过调用canvas.restoreToCount(layer)或者canvas.restore()把这个layer绘制到canvas默认的layer上去,这样就完成了一个layer的绘制工作。

参考:Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解

</br></br>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容