自定义View(一)
2017-04-19 本文已影响0人
Jackson杰
写在前面
Android已经为我们提供了丰富的组件库,让我们可以实现各种UI效果。但是如果如此众多的组件还不能满足我们的需求,怎么办呢?别急,android组件也为我们提供了非常方便的拓展方法,通过对现有系统组件的继承,可以方便地实习那我们自己的功能。
自定义View作为Android的一项重要技能,一直以来被初学者认为是代表高手的象征,这篇文章就带大家了解下自定义View的过程。
自定义View的分类
- 继承 View重写 onDraw 方法
这种方式主要用于显示不规则的效果哦,即这种效果不方便用布局组合来实现,往往需要静态或者动态的显示一些不规则的图形采用这种方式需要自己支持wrap_content,并且padding也需要自己处理。 - 继承ViewGroup派生特殊的Layout
这种方法主要用于实现自定义布局,即除了LinearLayout,RelativeLayout等系统布局之外的一种重新定义的全新的布局,当某种效果很像
几种View组合在一起的时候就可以采用这种方法。
这种方法稍微复杂一些,需要合适的处理ViewGroup的测量和布局这俩个过程 - 继承特定的View(比如TextView)
这种方法一般用于扩展某种已有的View功能。这种方法不需要自己支持wrap_content,padding等。 - 继承特定的ViewGroup(比如LinearLayout等)
当某种效果很像几种View组合在一起的时候就可以采用这种方法。这种方法不需要自己处理ViewGroup的测量和布局这俩个过程。
自定义View的注意事项
- 让View支持wrap_content
这是因为直接继承View或ViewGroup的控件,如果不在onMeasure中处理wrap_content,那么外界在布局中使用wrap_content时就无法达到预期效果 - 让View支持padding
直接继承View的控件,如果不再draw方法中处理padding,那么这个属性是无法起作用的。直接继承ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,不然将导致pading和子元素的margin失效 - 不要在View中使用Handler
这是因为View内部本身就提供了post系列方法,完全可以替代Handler的作用。除非你很明确要用Handler来发送消息。 - View中如果有线程和动画,及时停止
如果有线程和动画需要停止的时候,onDetachedFromWindow就恶意做到。这是因为当包含此View的Activity退出或者当前View被remove时,View的onDetachedFromWindow方法就会被调用。相对的,当包含此View的Activity启动时onAttachedToWindow会被调用。同时,View不可见时,我们也需要停止线程和动画,如果不及时停止,可能会导致内存泄漏。 - 如果有滑动嵌套时,当然要处理好滑动冲突的问题。
注意事项
在自定义View中,通常有下列比较重要的方法:
- onFinishInflate():从xml中加载组件后调用
- onSizeChanged():当组件的大小发生变化时调用
- onMeasure():测量组件时调用,是View支持wrap_content属性
- onLayout():确定组件显示位置时调用
-onTouchEvent():界面上有触摸事件时调用
当然,创建自定义View的时候,不一定要全部重写上述方法,只需按照需要重写即可。
通常,有以下三种方法实现自定义View - 对现有控件进行扩展
- 通过组合实现新的控件
- 重写View实现全新控件
下面就用代码展示下自定义View的基本步骤:
- 新建BasicCustomView继承View
完整代码如下
public class BasicCustomView extends View {
private Paint mPaint;
public BasicCustomView(Context context) {
super(context);
initView();
}
public BasicCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public BasicCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
canvas.translate(width/2,height/2);
canvas.drawCircle(0,0,100,mPaint);
}
}
首先验证自定义View是否支持layout_margin,padding,wrap_content等属性,验证代码如下:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_custom_view_basic"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.ahuang.viewandgroup.activity.CustomViewBasicActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.example.ahuang.viewandgroup.View.BasicCustomView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:background="#111fff"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.example.ahuang.viewandgroup.View.BasicCustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#111fff"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.example.ahuang.viewandgroup.View.BasicCustomView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#111fff"
android:padding="20dp"/>
</LinearLayout>
</LinearLayout>
上图证明图我们的自定义View
1.支持layout_margin属性
2.不支持padding属性
3.证明不支持wrap_content
让View支持wrap_content
之所以不支持wrap_content属性,是因为我们的自定义View没有重写onMeasure()方法,View默认的onMeasure()方法只支持EXACTLY模式,所以可以指定控件的具体宽高值或者match_parent属性,如果要自定义的view支持wrap_content属性,就必须重写onMeasure()方法。
加入代码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
}
/**
* 获得测量的宽度
* @param widthMeasureSpec
* @return
*/
private int measureWidth(int widthMeasureSpec){
int width = 0;
int mode=MeasureSpec.getMode(widthMeasureSpec); //获得测量模式
int size=MeasureSpec.getSize(widthMeasureSpec); //获得测量值
if (mode==MeasureSpec.EXACTLY){ //精准测量模式
width=size;
}else {
width=300;
if (mode==MeasureSpec.AT_MOST){
width=Math.min(width,size);
}
}
return width;
}
/**
* 获得测量的高度
* @param heightMeasureSpec
* @return
*/
private int measureHeight(int heightMeasureSpec){
int height = 0;
int mode=MeasureSpec.getMode(heightMeasureSpec); //获得测量模式
int size=MeasureSpec.getSize(heightMeasureSpec); //获得测量值
if (mode==MeasureSpec.EXACTLY){ //精准测量模式
height=size;
}else {
height=300;
if (mode==MeasureSpec.AT_MOST){
height=Math.min(width,size);
}
}
return height;
}
可以看到,重写onMeasure()方法后,VIew已经支持wrap_content了。
让View支持padding属性
修改onDraw()方法如下:
@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);
canvas.translate(width/2,height/2);
canvas.drawCircle(0,0,100,mPaint);
}
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.example.ahuang.viewandgroup.View.BasicCustomView
android:layout_width="150dp"
android:layout_height="150dp"
android:background="#111fff"
android:paddingLeft="30dp"
android:paddingTop="30dp"/>
</LinearLayout>
我们看到已经支持padding属性了.