Android——自定义控件View详解
2020-10-14 本文已影响0人
多仔百事宅
为什么要自定义控件?
1.特定的显示风格
2.处理特有的用户交互
例:比如原本TextView不能滑动里面的文字,通过自定义控件实现
3.优化我们的布局
例:通过嵌套实现复杂的布局,但是绘制和测量的效率慢,通过自定义控件实现,提升效率
4.封装等
例:app内很多控件可以复用,比如首页底部的tab按钮,封装成自定义控件,方便后续使用
如何自定义控件?
1.自定义属性的声明与获取
提取自定义控件的属性,去声明,然后在构造方法里去获取。
- 分析需要的自定义属性:颜色,文字大小,文字,图标等
- 在res/values/attrs.xml定义声明
- 在layout.xml文件中进行使用
- 在View的构造方法中进行获取
通过TypedArray a=context.obtainStyledAttributes()进行获取,返回来一个TypedArray对象,通过这个对象可以获取到对应的自定义属性的值,获取完成要记得recycle();
中间获取的过程可以使用for循环,直接获取也可以
2.测量onMeasure
测量自身需要多大的范围,
两个数字决定,一个测量的模式,一个测量的值
测量的模式分三种:
- EXACTLY:设置了明确的值,那result就等于我们设置的值
- AT_MOST:最多不能超过某个值,这个模式一般出现在设置了wrap_content的情况下,尺寸是根据自身内容决定的,但是不能超过父控件高度和宽度
- UNSPECIFIED:没有限制高度和宽度,适用于Listview,ScrollView等
使用MeasureSpec对象获取自定义控件的测试模式和数值设置了大小,然后来设置自定义控件的高宽,最后要记得用setMeasuredDimension方法来把得到的result值传进去
需要重新测量的话要调用requestLayout(),用来提供给外部进行重新测量,不包括绘制
3.布局onLayout(ViewGroup)
单纯的View不需要考虑这个,
ViewGroup需要复写这个方法
- 决定子View的位置
- 尽可能将onMeasure中一些操作移动到此方法中(耗时操作移动到这里,onLayout只触发一次)
- 计算好位置,通过requestLayout()这个方法进行布局
4.绘制onDrow
- 绘制内容区域:使用Canvas相关的API绘制你想要的效果
- invalidate(在UI线城中调用),postInvalidate(在子线程中调用)
- Cancas.drawXXX 熟练使用这里面的一些方法
- translate,rotate,scale,skew 巧妙使用这些变换的方法
- save(),restore() 结束要注意这个状态
一般情况下,与用户没有交互的情况下,理论下只考虑onMeasure和onDrow就可以了,如果是自定义的ViewGroup还需要额外考虑onLayout
5.onTouchEvent
如果有交互的地方,需要在这里进行处理
- ACTION_DOWN :
放下手指 - ACTION_MOVE :
移动手指 - ACTION_UP :
抬起手指,如果有加入到速度检测等,在这里判断 - ACTION_POINTER_DOWN:
考虑多点触控时,需要考虑的类型 - ACTION_POINTER_UP:
考虑多点触控时,需要考虑的类型 - getParent().requestDisallowInterceptTouchEvent(true)
如果子view已经拿到事件了,可以调用这个方法,可以告诉父控件,不要拦截我的事件 - VelocityTracker
6.onInterceptTouchEvent(ViewGroup)
事件转发过程中,事件是由交给子控件处理的,但是转发的过程中,父控件有权去拦截子控件的事件,就是通过这个方法,如果返回时true了,那就表明事件被拦截了,事件要交给ViewGroup自己去处理
- ACTION_DOWN :
放下手指 - ACTION_MOVE :
移动手指,进行一些移动距离的计算 - ACTION_UP :
抬起手指,如果有加入到速度检测等,在这里判断 - ACTION_POINTER_DOWN:
考虑多点触控时,需要考虑的类型 - ACTION_POINTER_UP:
考虑多点触控时,需要考虑的类型 - getParent().requestDisallowInterceptTouchEvent(true)
如果子view已经拿到事件了,可以调用这个方法,可以告诉父控件,不要拦截我的事件
决定是否拦截该手势
如果自定义的是ViewGroup,想拦截子View里面的方法,还需要写这个方法。