自定义view的基本流程

有时候我们需要一些特殊的效果或者功能,而系统控件无法满足我们的需求,这时候就需要自己定义一个控件。

自定义view流程

继承View

要自定义View首先需要需要继承View或者其子类,如果需要实现的效果比较复杂,通常需要继承View,有时候我们需要的是系统的控件再加上一些特殊的效果则可以继承View的子类(如TextView)

如果是要自己设计一种布局或者要组合其他控件,这时候就需要继承ViewGroup或者LinearLayout、FrameLayout等系统自带的布局

重写构造方法

自定义View至少需要重写两个构造方法

  • 在java代码中直接创建控件所用的构造方法
public CustomView(Context context) {
        this(context, null);
}
  • 在xml文件中定义View所用的构造函数
public CustomView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
}

public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
}

自定义xml中的属性

首先需要新建res/values/custom_view_attrs.xml,并在里面声明如下

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomView">
        <attr name="custom_width" format="dimension" />
    </declare-styleable>
</resources>

然后就可以在xml布局文件中声明了

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="cn.lkllkllkl.customview.MainActivity">

    <cn.lkllkllkl.customview.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:custom_width="100dp"/>

</LinearLayout>

接着我们就可以在自定义View的构造方法中将该属性的值取出使用了

public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        float customWidth = 0;
        TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
        customWidth = typeArray.getDimension(R.styleable.CustomView_custom_width, 100);
        // 需要记得回收
        typeArray.recycle();
    }

onMeasure方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);    

MeasureSpec

这里要明白onMeasure的两个参数首先需要明白MeasureSpec这个类。

MeasureSpec通过将SpecMode(模式)和SpecSize(具体尺寸)打包成一个int(即widthMeasureSpec、heightMeasureSpec),前2位表示SpecMode,后30位表示尺寸。

SpecMode有三类

  • UNSPECIFIED: 父容器对View的大小没有限制,一般我们自定义View用的较少

  • EXACTLY: 在xml文件中设置具体大小为多少dp,或者设置为match_parent则SpecMode为EXACTLY,此模式表示则直接使用SpecSize作为自定义View的尺寸

  • AT_MOST: 对应xml文件中的wrap_content,表示设置的尺寸大小不能超过SpecSize

通常我们需要view支持wrap_content的时候才需要重写onMeasure,如果不重写则wrap_content效果和match_parent一样。一般使用如下代码就够了

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        // dp转换成px, 事先定义的默认宽高单位为dp
        int defaultWidth = dp2px(getContext(), DEFAULT_WIDTH);
        int defaultHeight = dp2px(getContext(), DEFAULT_HEIGHT);

        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            widthSize = Math.min(defaultWidth, widthSize);
            heightSize = Math.min(defaultHeight, heightSize);
        } else if (widthMode == MeasureSpec.AT_MOST) {
            widthSize = Math.min(defaultWidth, widthSize);
        } else if (heightMode == MeasureSpec.AT_MOST){
            heightSize = Math.min(defaultHeight, heightSize);
        }
        
        setMeasuredDimension(widthSize, heightSize);
    }

一般为了适配不同屏幕需要使用dp为单位,而setMeasuredDimension是使用px为单位的,所以上面的代码将我们设置的默认宽高转换成px,转换方法如下

public static int dp2px(Context context, float dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dp, context.getResources().getDisplayMetrics());
    }

onLayout方法

一般自定义ViewGroup才需要重写该方法对子View进行排放

onDraw方法

通过onDraw方法我们可以将view绘制到屏幕上,如果要让view支持padding属性则需要在onDraw中做处理

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /* mPaint为成员变量,画笔Paint的对象,
         * 在构造函数中进行初始化,可以设置一些在canvas上绘制的通用属性
         */
        mPaint.setColor(Color.RED);
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        //在View所占区域绘制一条对角线
        canvas.drawLine(paddingLeft, paddingTop,
                getWidth() - paddingRight, getHeight() - paddingBottom, mPaint);
    }

注意事项

  • onMeasure、onLayout、onDraw三个方法可能会对此调用,特别是onDraw方法,所以最好不要在这些方法中创建对象避免频繁分配内存造成内存抖动,或者做一些耗时操作导致跳帧

  • View中有提供post系列方法,所以不需要在View中使用Handler

  • View中若有创建线程或者动画需要及时停止,View#onDetachedFromWindow就是一个很好的时机

  • View中若有滑动嵌套的情形,需要处理好滑动冲突

reference

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

推荐阅读更多精彩内容