自定义View是综合的技术体系,它涉及View的层级结构、事件分发机制和View的工作原理等技术细节。
自定义View的分类
继承View重写onDraw方法
这种方法主要用于实现一些不规则的效果,需要通过绘制的方式实现,即重写onDraw方法,采用这种方式需要自己支持wrap_content,并且padding也要自己处理。
继承ViewGroup派生特殊的Layout
这种方法主要用于实现自定义的布局,当某种效果看起来很像几种View组合在一起的时候,可以采用这种方法,需要合适的处理ViewGroup的测量、布局这两个过程,并同时处理子元素的测量和布局过程。
继承特定的View(如TextView)
这种方法比较常见,一般用于扩展某种已知View的功能,不需要自己支持wrap_content和padding
继承特定的ViewGroup
这种方法也比较常见,采用这种方法不需要自己处理ViewGroup的测量和布局这两个过程,和2的主要差别在于方法2更接近View的底层
自定义View的几个注意点
让View支持wrap_content
直接继承View或者ViewGroup的控件,如果不在onMeasure中对wrap_content做特殊处理,那么在布局中使用wrap_content时就无法达到预期效果(变为match_parent效果),处理示例如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(defaultWidthSize, defaultHeightSize);
}else if(widthSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(defaultWidthSize, heightSpecSize );
}else if(heightSpecMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize , defaultHeightSize);
}
}
让View支持padding
直接继承View的控件,如果不在draw方法中处理padding,那么padding无法生效;直接继承ViewGroup的控件如果不在onMeasure和onLayout中考虑padding和子元素的margin对其的影响,那么padding和子元素的margin无法生效,处理示例如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingTop - paddingBottom;
//得到实际width 和 height
//绘制...
}
没有必要在View中使用Handler
View内部本身就提供了post系列的方法,完全可以替代Handler作用
View中若有线程或动画,要及时停止
在View不可见、View所在Activity退出或当前View被remove时,要及时停止线程和动画,否则可能会造成内存泄漏,其中View所在Activity退出或当前View被remove时View的onDetachedFromWindow方法会被调用,可在中处理这两种情况
View带有滑动嵌套时,需要处理滑动冲突
具体可参见 事件分发机制与滑动冲突
自定义属性步骤
1.在values目录下创建自定义属性的XML,比如attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="circle_color" format="color"/>
</declare-styleable>
</resources>
2.在View构造方法中解析自定义属性
public CircleView(Context context, AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mColor = a.getColor(R.styleable.CircleView+circle_color, Color.RED);
a.recycle();
init();
}
3.布局文件中使用自定义属性
...
xmlns:app="http://schemas.android.com/apk.res-auto"
...
<com.XXX.CircleView
...
app:circle_color="@color/light_green"
...
/>
此文并未深入,只是记录一下方法方便查阅~
但自定义View能力提升的前提就是掌握基本功,比如View的弹性滑动、滑动冲突、绘制原理等
那些看起来很炫的自定义View,往往对基本功要求更高
熟练掌握基本功后,面对新的自定义View,要能够对其分类并选择合适的实现思路,然后多积累一些自定义View经验,并逐渐做到融会贯通,就可以提高自定义View的水平了
完~
参考文章《Android开发艺术探索》