小白自定义View

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

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

话不多说,先上代码

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

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 = " " /> < flag name = " s2 " value = " 2 " /> < flag name = " s3 " value = " 3 " /> </ attr > </ declare - styleable ></ resources >
Set attrs ) { super ( context , attrs ) ; }

支持的自定义属性

  • 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 " />

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 ) ; }

自定义控件 - 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 ( 00 , 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

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

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

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

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 ) ; }

就这样,我们简单的绘制了一个圆形,是不是你感觉自定义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 。。。。抱歉,臣妾无能为力,实现想不出来了,以后要是有想法马上补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值