寫在前面
Android已經(jīng)為我們提供了豐富的組件庫爆哑,讓我們可以實現(xiàn)各種UI效果什猖。但是如果如此眾多的組件還不能滿足我們的需求牧愁,怎么辦呢素邪?別急,android組件也為我們提供了非常方便的拓展方法递宅,通過對現(xiàn)有系統(tǒng)組件的繼承,可以方便地實習(xí)那我們自己的功能苍狰。
自定義View作為Android的一項重要技能办龄,一直以來被初學(xué)者認(rèn)為是代表高手的象征,這篇文章就帶大家了解下自定義View的過程淋昭。
自定義View的分類
- 繼承 View重寫 onDraw 方法
這種方式主要用于顯示不規(guī)則的效果哦俐填,即這種效果不方便用布局組合來實現(xiàn),往往需要靜態(tài)或者動態(tài)的顯示一些不規(guī)則的圖形采用這種方式需要自己支持wrap_content翔忽,并且padding也需要自己處理英融。 - 繼承ViewGroup派生特殊的Layout
這種方法主要用于實現(xiàn)自定義布局,即除了LinearLayout歇式,RelativeLayout等系統(tǒng)布局之外的一種重新定義的全新的布局驶悟,當(dāng)某種效果很像
幾種View組合在一起的時候就可以采用這種方法。
這種方法稍微復(fù)雜一些材失,需要合適的處理ViewGroup的測量和布局這倆個過程 - 繼承特定的View(比如TextView)
這種方法一般用于擴(kuò)展某種已有的View功能痕鳍。這種方法不需要自己支持wrap_content,padding等龙巨。 - 繼承特定的ViewGroup(比如LinearLayout等)
當(dāng)某種效果很像幾種View組合在一起的時候就可以采用這種方法笼呆。這種方法不需要自己處理ViewGroup的測量和布局這倆個過程。
自定義View的注意事項
- 讓View支持wrap_content
這是因為直接繼承View或ViewGroup的控件旨别,如果不在onMeasure中處理wrap_content,那么外界在布局中使用wrap_content時就無法達(dá)到預(yù)期效果 - 讓View支持padding
直接繼承View的控件诗赌,如果不再draw方法中處理padding,那么這個屬性是無法起作用的秸弛。直接繼承ViewGroup的控件需要在onMeasure和onLayout中考慮padding和子元素的margin對其造成的影響铭若,不然將導(dǎo)致pading和子元素的margin失效 - 不要在View中使用Handler
這是因為View內(nèi)部本身就提供了post系列方法洪碳,完全可以替代Handler的作用。除非你很明確要用Handler來發(fā)送消息奥喻。 - View中如果有線程和動畫偶宫,及時停止
如果有線程和動畫需要停止的時候,onDetachedFromWindow就惡意做到环鲤。這是因為當(dāng)包含此View的Activity退出或者當(dāng)前View被remove時纯趋,View的onDetachedFromWindow方法就會被調(diào)用。相對的冷离,當(dāng)包含此View的Activity啟動時onAttachedToWindow會被調(diào)用吵冒。同時,View不可見時西剥,我們也需要停止線程和動畫痹栖,如果不及時停止,可能會導(dǎo)致內(nèi)存泄漏瞭空。 - 如果有滑動嵌套時揪阿,當(dāng)然要處理好滑動沖突的問題。
注意事項
在自定義View中咆畏,通常有下列比較重要的方法:
- onFinishInflate():從xml中加載組件后調(diào)用
- onSizeChanged():當(dāng)組件的大小發(fā)生變化時調(diào)用
- onMeasure():測量組件時調(diào)用,是View支持wrap_content屬性
- onLayout():確定組件顯示位置時調(diào)用
-onTouchEvent():界面上有觸摸事件時調(diào)用
當(dāng)然南捂,創(chuàng)建自定義View的時候,不一定要全部重寫上述方法旧找,只需按照需要重寫即可溺健。
通常,有以下三種方法實現(xiàn)自定義View - 對現(xiàn)有控件進(jìn)行擴(kuò)展
- 通過組合實現(xiàn)新的控件
- 重寫View實現(xiàn)全新控件
下面就用代碼展示下自定義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默認(rèn)的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){ //精準(zhǔn)測量模式
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){ //精準(zhǔn)測量模式
height=size;
}else {
height=300;
if (mode==MeasureSpec.AT_MOST){
height=Math.min(width,size);
}
}
return height;
}
可以看到柜候,重寫onMeasure()方法后搞动,VIew已經(jīng)支持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>
我們看到已經(jīng)支持padding屬性了.