跟着洪洋大神学自定义view

一直想学自定义view,《安卓开发艺术》上讲的非常清楚,但是对我来说显得有点过于深入,洪洋大神的博客则显得通俗易懂,步骤讲的非常清楚。

基本步骤:

1.创建自定义组件类继承view,然后在res/attrs.xml里定义组件需要的属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <attr name="titleText" format="string"/>
    <attr name="titleTextColor" format="color"/>
    <attr name="titleTextSize" format="dimension"/>
    <attr name="image" format="reference"/>
    <attr name="imageScaleType">
        <enum name="fillXY" value="0"/>
        <enum name="center" value="1"/>
    </attr>
    
    <declare-styleable name="MyView1">
        <attr name="titleText"/>
        <attr name="titleTextColor"/>
        <attr name="titleTextSize"/>
    </declare-styleable>
    <declare-styleable name="MyView2">
        <attr name="titleText"/>
        <attr name="titleTextColor"/>
        <attr name="titleTextSize"/>
        <attr name="image"/>
        <attr name="imageScaleType"/>
    </declare-styleable>
</resources>
这里每一个declare...标签对应一个自定义组件,name对应自定义组件的类名,这里面我写了两个,表示declare上面的属性是可以复用的。一个attr标签就是一个属性,format标明属性的值的类型,其中reference表示资源ID,如drawabale的id。


2.在构造方法中获取并处理属性:

在xml中直接放置组件调用的是两个参数的方法,但是一般我们在三个参数的构造方法里进行操作,所以让一个参数和两个参数的构造方法都通过this调用三个参数的构造方法。

先用TypedArray类取出包含所有属性的数组,这个数组里的元素都是属性的资源id,int类型,需要拿到这个id进一步取出里面的值。然后洪洋大神进行了switch判断而不是直接取值,应该是避免有些没有指定值的属性报空指针。

 //获取并处理自定义属性
        TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.MyView1,defStyleAttr,0);
        int n=array.getIndexCount();//获取到的属性数目.
        for (int i=0;i<n;i++){
            int attr=array.getIndex(i);//取出每一个属性的ID
            switch (attr){
                case R.styleable.MyView1_titleText:
                    text=array.getString(attr);//通过ID取属性的值
                    break;
                case R.styleable.MyView1_titleTextColor:
                    mTextColor=array.getColor(attr,defStyleAttr);//def是默认值
                    break;
                case R.styleable.MyView1_titleTextSize:
                    //设置默认值16sp
                    int def= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,16,getResources().getDisplayMetrics());
                    mTextSize=array.getDimensionPixelSize(attr,def);
                    break;
            }
        }
        array.recycle();//资源回收
把这些值赋给开始private的变量,(注意这里的mTextSize和mTextColor都是int类型,参数里的def是默认值也就是找不到时候的值。对于一些设置值的方法大多是设置的像素px,比如画笔的setTextSize,应该把sp或者dp先转为px的值传入),然后就可以在measure,draw里进一步使用。调用顺序是构造方法---onMeasure----onDraw。还要及时做资源回收

补充:这些变量应该有初始值,避免空指针或无效。


3.重写onMeasure:

MeasureSpec是measure的关键,每一个MeasureSpec里面存着两个Int数,分别是Mode和Size,也就是测量模式和测量的值。关于Mode的确定,《安卓开发艺术》里做了详细的解释,这里可以简单分为两种:如果Mode是EXACTLY,表示组件指定了精确的值或者match_parent,测量的值是我们希望的值,不用动。否则的话,也就是wrap_content,如果我们不做处理,组件会铺满剩余空间,也就是测量的大小效果等同于match_parent,这和我们平时的印象不同。我们直接使用Button,TextView的时候,这些组件的大小确实是内容自适应的,其实是因为这些组件的wrap_content都经过了判断处理,而对于我们自定义组件,显然需要我们自己处理,手动计算出组件需要的宽高再赋给他作为测量值。比如下面这段代码:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width,height;
        //上面一张照片下面一段文字的布局
        int widthMode=MeasureSpec.getMode(widthMeasureSpec);
        int wigthValue=MeasureSpec.getSize(widthMeasureSpec);
        if (widthMode==MeasureSpec.EXACTLY){
            width=wigthValue;
        } else {
            // 由图片决定的宽
            int desireByImg = getPaddingLeft() + getPaddingRight() + image.getWidth();
            // 由字体决定的宽
            int desireByTitle = getPaddingLeft() + getPaddingRight() + textRect.width();
            int width2=Math.max(desireByImg,desireByTitle);//取较大的一个
            width=Math.min(wigthValue,width2);//不能超过剩余空间
        }

        int heightMode=MeasureSpec.getMode(heightMeasureSpec);
        int heightValue=MeasureSpec.getSize(heightMeasureSpec);
        if (widthMode==MeasureSpec.EXACTLY){
            height=heightValue;
        } else {
            int height2=getPaddingTop()+getPaddingBottom()+image.getHeight()+textRect.height();
            height=Math.min(heightValue,height2);
        }
        
        setMeasuredDimension(width,height);
    }

要注意padding的考虑,在内容区域外包一层padding才是组件的区域。接下来的onDraw在画的位置也要考虑padding。

另外,只有测量完才有getHeight,getWidth,getMeasuredHeight,getMeasuredWidth,测量前都是0.


4.重写onDraw:

画笔可以画很多东西,这一块的内容主要是canvas的各种方法

paint经常设置抗锯齿:

paint.setAntiAlias(true);//抗锯齿
rect的left,top,bottom,right可以看作左上和右下两个点坐标。

比如用画笔的STROKE模式画边框的时候,边框是从形状的边缘向内外各延伸50%的边框宽度。


5.使用:

在布局文件中放入这个组件,不能忘了声明一个命名空间来使用自定义属性,名称任意:

xmlns:cunstom="http://schemas.android.com/apk/res-auto"

5.其他

还可以直接在构造器内用this.来添加触摸事件点击事件等,实现了与活动的解耦。



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值