自定义view

来源:互联网 发布:南风知我意叶小意txt 编辑:程序博客网 时间:2024/05/29 16:02

最近在学习自定义view,想着既然学了那就记下来,以后忘了可以看看,顺便分享给大家,一起学习。

好,那我们就进入正题吧,我们知道,一般自定义view时,最简单的我们会继承view或viewgroup,首先呢便是复写三个构造方法

我们来看下这三个构造方法

public class SetItemView extends RelativeLayout {public SetItemView(Context context) {        this(context, null);    }    public SetItemView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public SetItemView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);           }}


第一个构造方法就是我们普通在代码中新建一个view用到的方法,例如

SetItemView setItemView = new SetItemView(this);
一个自定义的view就被新建出来了,然后可以根据需求添加到布局里

第二个构造方法就是我们一般在xml文件里添加一个view

 <com.example.leac.mysixstudy.com.SetItemView                android:id="@+id/rl_clear"                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:layout_marginTop="14dp"  />

我们就把一个SetItemView添加到布局文件里,并且加了一些布局属性,宽高属性以及margin属性等系统自带默认的,这些属性会存放在第二个构造函数的AttributeSet参数里


第三个构造函数比第二个构造函数多了一个int型的值,名字叫defStyleAttr,这是一个关于自定义属性的参数,第三个构造函数不会被系统默认调用,而是需要我们自己去显式调用,比如在第二个构造函数里调用调用第三个函数,并将第三个参数设为0。

再来看看自定义属性

要给view支持自定义属性,需要在values/attrs.xml 文件里定义一个name为自己定义view名字的declare-styleable

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="SetItemView">        <attr name="leftText" format="string"/>        <attr name="leftIcon" format="integer"/>        <attr name="rightIcon" format="integer"/>        <attr name="textSize" format="float"/>        <attr name="textColor" format="color"/>        <attr name="isShowUnderLine" format="boolean"/>    </declare-styleable></resources>

这样就可以在xml文件里使用自己定义的属性了

 <com.example.leac.mysixstudy.com.SetItemView                android:id="@+id/rl_clear"                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:layout_marginTop="14dp"                app:leftIcon="@drawable/clearcache"                app:leftText="@string/fragment_me_tv_clear"                app:rightIcon="@drawable/task_arrow"                app:textColor="@color/text_color_6"                app:textSize="16"/>

别忘了在前面加上自定义的命名空间,但要自定义属性生效还是要耗费一些功夫的,这时候前面讲的第三个构造方法的defStyleAttr参数就派上用场了。

/**     * 初始化控件自定义属性信息     *     * @param context     * @param attrs     */    private void getCustomStyle(Context context, AttributeSet attrs) {        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SetItemView);        int num = typedArray.getIndexCount();        for (int i = 0; i < num; i++) {            int arr = typedArray.getIndex(i);            if (arr == R.styleable.SetItemView_leftText) {                mText = typedArray.getString(arr);                mTvLeftText.setText(mText);            } else if (arr == R.styleable.SetItemView_leftIcon) {                mLeftIcon = typedArray.getDrawable(arr);                mIvLeftIcon.setImageDrawable(mLeftIcon);            } else if (arr == R.styleable.SetItemView_rightIcon) {                mRightIcon = typedArray.getDrawable(arr);                mIvRightIcon.setImageDrawable(mRightIcon);            } else if (arr == R.styleable.SetItemView_textSize) {                // 默认设置为16sp                float textSize = typedArray.getFloat(arr, 16);                mTvLeftText.setTextSize(textSize);            } else if (arr == R.styleable.SetItemView_textColor) {                //文字默认灰色                mTextColor = typedArray.getColor(arr, Color.GRAY);                mTvLeftText.setTextColor(mTextColor);            } else if (arr == R.styleable.SetItemView_isShowUnderLine) {                boolean flag = typedArray.getBoolean(arr, true);                if (!flag) {                    mUnderLine.setVisibility(View.GONE);                }            }        }        typedArray.recycle();    }

接下来我们看看控件的绘制流程

在Android里,一个view的绘制流程包括:Measure,Layout和Draw,通过onMeasure知道一个view要占界面的大小,然后通过onLayout知道这个控件应该放在哪个位置,最后通过onDraw方法将这个控件绘制出来,然后才能展现在用户面前,下面我将挨个分析一下这三个方法的作用。


onMeasure 测量,通过测量知道一个一个view要占的大小,measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。我们都知道,在java中,int型由4个字节(32bit)组成,在MeasureSpce中,用前两位表示mode,用后30位表示size

MeasureSpce的mode有三种:EXACTLY, AT_MOST,UNSPECIFIED,除却UNSPECIFIED不谈,其他两种mode:当父布局是EXACTLY时,子控件确定大小或者match_parent,mode都是EXACTLY,子控件是wrap_content时,mode为AT_MOST;当父布局是AT_MOST时,子控件确定大小,mode为EXACTLY,子控件wrap_content或者match_parent时,mode为AT_MOST。所以在确定控件大小时,需要判断MeasureSpec的mode,不能直接用MeasureSpec的size。在进行一些逻辑处理以后,调用setMeasureDimension()方法,将测量得到的宽高传进去供layout使用。需注意是 ,测量所得的宽高不一定是最后展示的宽高,最后宽高确定是在onLayout方法里,layou(left,top,right,bottom),不过一般都是一样的。


host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);

onLayout 实际上,方法里主要是具体摆放子view的位置,水平摆放或者垂直摆放,所以在单纯的自定义view(即继承的是view)是不需要重写onLayout方法,不过需要注意的一点是,子view的margin属性是否生效就要看parent是否在自身的onLayout方法进行处理,而view得padding属性是在onDraw方法中生效的。但是如果继承自ViewGroup,则所有ViewGroup的子类都必须重写onLayout这个方法,子视图的layout()方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的,


onDraw ,一般自定义控件耗费心思最多的就是这个方法了,需要在这个方法里,用Paint在Canvas上画出你想要的图案,这样一个自定义view才算结束。





原创粉丝点击