自定义view

引言

自定义 View,一个顾名思义的东西,在 Android 开发中说多也不多,不多在面向百度/谷歌的开发大快人心的使用着,说少也不少,任何 APP 都想把自己的应用做的美轮美奂的。


简介

在开始关于自定义的 View 的话题时,先对对他做个简单的介绍把,虽然小猪前辈在之前的问题也有提及( View 与 ViewGoup 的概念),但是感觉有点粗糙(这是前辈一年多前写的,那时候我还在搭酱油呢!)。

Android 给我们有限的控件时,同时也提供了扩展的渠道,通过继承控件,可以任性的丰富我们的APP(控件)。也就是所谓的自定义让 Android 能像 js 等语言在 github,掘金等很多社区中有大量的开源,好的作品不断的会被学习仿效,这么说来,这个自定义 View(Group) 看起来非学不可了。 打住,当然一味的追求自定义在实际开发中不是特别好,尤其是面对适配的问题,虽然说和上面一句话有点矛盾,希望大家仁者见仁智者见智(其实博主不怎么写自定义控件,因为 material design 足以完成个性的花样了,当然该说的还是要说,每星期的博客还是给自己交代的)。


自定义 View 的继承与构造方法

凡事总有有个开头,关于集成的话,其实只要是 View 的子类包括 View 就可以了,当然 View 的子类(比如 TextView,ImageView 都已经自带了一些属性,如何选择请自行掂量),自定义啥的就先从构造函数说起吧

话不多说,先上代码

//就称呼为方法一吧 public MyView(Context context) { super(context); } //就称呼为方法二吧 public MyView(Context context, AttributeSet attrs) { super(context, attrs); }

如果这个控件会在 java 代码中 new 的话,必须实现方法一,如果控件直接在 xml 中使用,必须实现方法二,两者至少实现一个,也有的时候,可以简单粗暴点,把方法一中的 super(context) 改成 this(context,null)。当然,什么情况下可以这么做就不阐述了,下面我们还是介绍下关于 AttributeSer这个参数把。

AttributeSer - 自定义属性

每个控件都会有属性,既然是自定义控件怎么能少了自定义属性呢! 1.为了方便展开话题,我们先照做在 res/values/ 目录下添加一个 attrs.xml 这个文件。

attrs.xml

<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="MyViewStyle" > <declare-styleable name="MyViewStyle" > <attr name="content" format="string" /> <attr name="isShow" format="boolean"/> <attr name="background" format="color"/> <attr name="select" > <flag name="s1" value="1"/> <flag name="s2" value="2"/> <flag name="s3" value="3"/> </attr> </declare-styleable> </resources>

支持的自定义属性

  • reference:引用资源
  • string:字符串
  • Color:颜色
  • boolean:布尔值
  • dimension:尺寸值
  • float:浮点型
  • integer:整型
  • fraction:百分数
  • enum:枚举类型
  • flag:位或运算

2.有了这些自定义属性接下来当然就是依附在我们需要的自定义控件上了,先写在 xml 代码上把。

<com.xlhgo.viewdemo.MyView  
app:content="内容为王"  
app:is_show="false"  
app:view_background="@color/colorAccent"  
app:select="s1"  
android:layout_width="wrap_content"  
android:layout_height="wrap_content"  />

做好了上面两步以后你或许会波不急待的去运行 demo,或许你会发现毛都没出来。

image

我才没开始多久呢,给我点时间,下面才是重点。

3. 既然有了自定义属性与控件,接下来就是要在我们的java代码中去获取这些自定义属性了。

private String mContent;
private Boolean mIsShow;
private int mBackground;
private int mSelect;
public MyView(Context context, AttributeSet attrs) {
    super(context, attrs);
 
    TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.MyViewStyle);
 
    if(typedArray != null){
        //这里要注意,String类型是没有默认值的,所以必须定义好,不然又是空指针大法
        mContent = typedArray.getString(R.styleable.MyViewStyle_content);
        mIsShow = typedArray.getBoolean(R.styleable.MyViewStyle_is_show, true);
        mBackground = typedArray.getColor(R.styleable.MyViewStyle_view_background, Color.RED);
        mSelect = typedArray.getInt(R.styleable.MyViewStyle_select, 0);
    }
    Log.d("viewStyle","content:"+mContent);
    Log.d("viewStyle","isShow:"+mIsShow);
    Log.d("viewStyle","background:"+mBackground);
    Log.d("viewStyle","select:"+mSelect);
}

最后日志输出:

image

自定义控件 - View - onMeasure

既然是一个控件,啥都别说,肯定要先有他的宽高,但是我们明明已经在xml中定义了控件的 layout_widght 与 layout_height,为什么还要特地重写自定义控件的。至于为什么请看下面。

几个概念

Android 系统给我们提供了一个 MeasureSpec类,通过它来帮助我们测量 View,MeasureSpec 是一个 32 的 int 值,其中高 2 为为测量模式,低30位为测量的大小,这难道是二进制!是的,但是大可放心,google 还是挺为广大数学渣着想的,通过 MeasureSpec 的方法可以获取我们需要的数值。

再来说说测量模式把,测试模式可以为一下三种:

  • EXACTLY:精确模式 当我们将控件的 layout_width 与 layout_height 属性制定为具体数值时(排除 wrap_content),系统就使用这个模式,(同时 View 类的 onMeasure 默认是 EXACTLY 模式,所以不重写 onMeasure 的话,就只能使用默认模式)
  • AT_MOST:最大值模式 当控件的 layout_width 属性或 layout_height 属性为 wrap_content 时,控件大小一般随着子控件或内容的变化而变化,此时控件的此尺寸只要不超过父控件允许的最大尺寸。
  • UNSPECIFIED: 它不能其大小测量模式,View想多大就多大,通常情况在回执自定义View时才使用。

懵懂了以上的几个概念以后就开始来重写这个方法把,或许你会波不急待,因为如果你对先前对自定义 View 没有认识,也会像我一开始看了还是一脸懵逼像。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //设置控件的宽高,记住这里默认是px,记得要分辨率转换实现适配,这里不做说明
    setMeasuredDimension(getSize(widthMeasureSpec),getSize(heightMeasureSpec));
}
 
private int getSize(int measureSpec){
    int result = 0;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    switch (specMode){
        case MeasureSpec.EXACTLY:
            //当layout_width与layout_height match_parent 为固定数值走这里
            result = 200;
            break;
        case MeasureSpec.AT_MOST:
            //当layout_width与layout_height定义为 wrap_content 就走这里
            result = Math.min(100,specSize);
            break;
        case MeasureSpec.UNSPECIFIED:
            //如果没有指定大小
            result = 400;
            break;
    }
    return result;
}

就是这,大功搞成了。 相应大家应该对 View 的 onMeasure 不陌生了,但是故事还没结束,我们再来说说 layout 方法把。


自定义控件 - View - layout

layout,顾名思义,就是定义这个控件所处的布局,其实即便我们通过 onMeasure 决定了这个控件的宽高,但是并不是所有的宽高都是能显示出来的,我们还需要通过 layout 给这个控件分配可以使用的控件。

因为 layout 不难,理解即可,所以毫不客气的直接上代码了。

@Override
public void layout(int l, int t, int r, int b) {
    super.layout(l, t, r, b);
    Log.d("layout:","l:"+l);
    Log.d("layout:","t:"+t);
    Log.d("layout:","r:"+r);
    Log.d("layout:","b:"+b);
}

当前控件的宽高为200px 输出结果为: l:48 t:48 r:248 b:248

聪明的同学想必已经看出来了这四个参数的意思,不懂的我就补个图吧。

image

大致就是这样,自己去体会我画图的意境把...(我用触摸板画图容易吗!)

理解了 layout 使用之后,个人认为 layout 在自定义 View 中使用的并不是非常多。但我还是要多提两句,如果我们修改 super.layout(l,t,r±100,b±100) 之后,控件也会更具所分配的位置进行改变,或者增加,但是不会再去影响其他控件的位置,这点与onMeasure是有很大的不同。 demo:我们在MyView这个控件下添加一个控件,看看他的位置是如何显示的:

image

c

大致就 layout 的使用说完了,接下来就说说自定以中最重要的一个方法把!


自定义控件 - View - onDraw

该准备的准备了,不该说的也说了,接下来就就是要重写的我们的onDraw方法,既然是自定义View,无非就是显得'花销'一点,定制性高点,那么onDraw方法非调用不可。

View的绘制离不开 Canvas,所以在重写 onDraw 方法的时候,系统传递给我们一个 Canvas 对象,剩下的就是配合一个自己创建的 Paint 去绘制我们想要的界面。话不说多,上代码 duang!

@Override  
protected  void  onDraw(Canvas  canvas)  {  
     super.onDraw(canvas); 
     Paint  paint = new  Paint(); 
     canvas.drawCircle(50, 50, 50, paint); 
}
image

就这样,我们简单的绘制了一个圆形,是不是你感觉自定义View这篇文章到此结束了,错!我们来做个小实验。我们在 super.onDraw(canvas); 下面添加一个 log Log.d("myView","onDraw");,接着给这个控件添加一个事件,来看看会是怎么样。

findViewById(R.id.myview).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { view.invalidate(); //刷新这个View } });

invalidate 方法是刷新 View,当我们点击这个控件的时候,这个onDraw 会被反复的调用(除了点解,这个方法经常被调用),同时也意味着,其实我们 onDraw 代码中的 Paint 会被多次创建,嘿嘿,是不是明白什么了,接下来我们最后说说关于自定义View优化。

:关于 Canvas,Paint 等绘制的 API 使用可以参考小猪前辈写的文章,毕竟这篇博客是延续小猪的博客来写的,所以就不重复造轮子了。


自定义控件 - View - 优化之路

不管是任何开发,优化之路任重道远,其实关于自定义控件的优化方式挺多,在这里博客可能说的不是非常详细。如果有更多的建议欢迎提出。

优化方式1 基于上述我们发现的问题,onDraw 方法会被方法调用,导致 Paint 被反复实例化,所以我们不能在这个方法去new对象,最好的解决方法,我们可以直接在构造方法中是实例化,定义为成员变量,就是辣么简单。

优化方式2 。。。。抱歉,臣妾无能为力,实现想不出来了,以后要是有想法马上补充。


补充

其实出来上述一些重写的方法,自定义控件还有其他可以重写的方法:

  • onFinishInflate(): 从XML加载组件后回调。
  • **onSizeChanged(): **组件大小改变回调。
  • **onTouchEvent(): **坚挺到触摸事件时回调。

关于自定义控件的一些博客:

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

推荐阅读更多精彩内容