4.4 自定义View
自定义View
是一个综合的技术体系,它涉及View
的层次结构、事件分发机制和View
的工作原理等技术细节。
4.4.1 自定义View的分类
1. 继承View重写onDraw方法
这种方法主要用于实现一些不规则的效果,即这种效果不方便通过布局的组合方式来达到,往往需要静态或者动态地显示一些不规则的图形。很显然这需要通过绘制的方式来实现,即重写
onDraw
方法。采用这种方式需要自己支持wrap_content
,并且padding
也需要自己处理。
2. 继承ViewGroup派生特殊的Layout
这种方法主要用于实现自定义的布局,即除了
LinearLayout
,RelativeLayout
,FrameLayout
这几种系统的布局之外,我们重新定义一种新布局,当某种效果看起来很像几种View
组合在一起的时候,可以采用这种方法来实现。采用这种方式稍微复杂一些,需要合适地处理ViewGroup
的测量、布局这两个过程,并同时处理子元素的测量和布局过程。
3. 继承特定的View(比如TextView)
这种方法比较常见,一般是用于拓展某种已有的
View
的功能、比如TextView
、这种方法比较容易实现。这种方法不需要自己支持wrap_content
和padding
等。
4. 继承特定的ViewGroup(比如LinearLayout)
这种方法也比较常见,当某种效果看起来很像几种
VIew
组合在一起的时候,可以采用这种方法来实现。采用这种方法不需要自己处理ViewGroup
的测量和布局这两个过程。需要注意这种方法和方法2的区别,一般来说方法2能实现的效果方法4也都能实现,两者的主要差别在于方法2更接近View
的底层。
4.4.2 自定义VIew须知
本节将介绍自定义View过程中的一些注意事项,这些问题如果处理不好,有些会影响View
的正常使用,而有些则会导致内存泄漏等。
1. 让View
支持wrap_content
这是因为直接继承
View
或者ViewGroup
的控件,如果不在onMeasure
中对wrap_content
做特殊处理,那么当外界在布局中使用wrap_content
时就无法达到预期的效果。
2. 如果有必要,让你的View
支持padding
这是因为直接继承
View
的控件,如果不在draw
方法中处理padding
,那么padding
属性是无法起作用的。另外,直接继承自ViewGroup
的控件需要在onMeasure
和onLayout
中考虑padding
和子元素的margin
对其造成的影响,不然将导致padding
和子元素的margin
失效。
3. 尽量不要在View
中使用Handler
,没必要
这是因为
View
内部本身就提供了post
系列的方法,完全可以替代Handler
的作用,当然除非你很明确地要使用Handler
来发送消息。
4. View
中如果有线程或者动画,需要及时停止,参考View#onDetachedFromWindow
这一条也很好理解如果有线程或者动画需要停止时,那么
onDetachedFromWindow
是一个很好的时机。当包含此View
的Activity
退出或者当前View
被remove
时,View
的onDetachedFromWindow
方法会被调用,和此方法对应的是onAttachedToWindow
,当包含此View
的Activity
启动时,View
的onAttachedToWindow
方法会被调用。同时,当View
变得不可见时我们也需要停止线程和动画,如果不及时处理这种问题,有可能会造成内存的泄露。
5. View
带有滑动嵌套情形时,需要处理好滑动冲突
如果有滑动冲突的话,那么要合适地处理滑动冲突,否则将会严重影响
View
的效果。
4.4.3 自定义View的示例
1. 继承View重写onDraw()方法
这种方法主要用于实现一些不规则的效果,一般需要重写onDraw()
方法。采用这种方式需要自己支持wrap_content
,并且padding
也需要自己处理。
为了实现一个规范的控件,在实现过程中必须考虑到wrap_content
模式以及padding
,同时为了提高便携性,还要对外提供自定义属性。
public class CircleView extends View {
private int mColor = Color.RED;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CircleView(Context context) {
super(context);
init();
}
public CircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs,0);
init();
}
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint.setColor(mColor);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int radius = Math.min(width,height) / 2;
canvas.drawCircle(width / 2,height / 2, radius,mPaint);
}
}
上面的代码实现了一个具有圆形效果的自定义View
,它会在自己的中心点以宽/高的最小值为直径绘制一个红色的实心圆,它的代码实现很简单。