內容會很多哦。如果這節自定義View描述有什麼錯誤的地方,麻煩指正一下。
如果不懂的同學,希望你們耐心看完本章節。我也是剛入門的。好了,廢話不多說!!
Android自定義View 是什麼?
顧名思義,自定義View就是我們自己定義的View,並且能和用戶進行交互的控件。我們使用的button都是繼承view這個父類的。當安卓內置的View不足以滿足我們日常的開發需求的時候,我們就可以通過自定義View來實現自己想要的效果。比如如下圖所示,跳過顯示廣告網頁直接進入主界面的按鈕。
涉及到的類和方法?
onDraw():是View顯示界面的方法,在這個方法的內部進行描繪你想要的效果。一般都會涉及到幾個比較重要的類。
Paint 畫筆類,設置畫筆的粗細,顏色等屬性
Canvas 畫布類,提供各種畫各種圖形的api
Bitmap 位圖類,用來獲取圖片的信息。
matrix 矩陣類,用來控制圖片的現實
View的繪製流程
假設一輛車的生產過程。
測量車的框架大小,根據車的框架大小來測量車內飾大小的,比如座位等。onMeasure()
開始定製,根據測量的尺寸來進行生產。onDraw()
生產完成之後,將需要的東西填充到框架裏去。onLayout()
最後,該車具備了走的功能了,我們就能使用它了。onTouch() 用於和用戶進行交互
初步使用?
首先我創建一個類,RectView類。這個類需要繼承自View。其次覆寫裏面的方法,在這裏我們覆寫前面兩種方法。
public class RectView extends View {
public RectView(Context context) {
super(context);
}
public RectView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
}
我們首先簡單的畫一個矩形。此時我們需要覆寫onDraw()方法。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//創建一個矩形區域,裏面的數字分別代表 left,top,right,bottom
RectF rectF = new RectF(0, 0, 100, 100);
//創建一個畫筆
Paint paint = new Paint();
//用畫布畫出一個矩形
canvas.drawRect(rectF, paint);
}
然後添加一下xml的標籤,相當於添加一個button按鈕這樣。
<com.example.does.customview.RectView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
然後Run,這樣矩形就畫出來了。
onDraw(Canvas canvas)中的canvas相當於畫布,不瞭解的話可以百度一下。理解爲一張畫布就行了。Paint()這個就是創建一個畫筆,RectF()中的Rect代表的意思是矩形,F代表的float類型,可以Ctrl點進去看源碼就知道了。
Canvas這個類裏面有許多方法,比如畫一個圓。我們修改一下,
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//創建一個矩形區域,裏面的數字分別代表 left,top,right,bottom
RectF rectF = new RectF(0, 0, 500, 500);
//創建一個畫筆
Paint paint = new Paint();
//用畫布畫出一個圓
canvas.drawOval(rectF,paint);
}
效果如下,
當然,我們還可以對其描繪的東西做其它屬性的修改,
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//創建一個矩形區域,裏面的數字分別代表 left,top,right,bottom
RectF rectF = new RectF(10, 10, 500, 500);
//創建一個畫筆
Paint paint = new Paint();
//抗鋸齒
paint.setAntiAlias(true);
//設置一個空心圓
paint.setStyle(Paint.Style.STROKE);
//設置畫筆的顏色
paint.setColor(Color.BLUE);
//設置外邊粗一點
paint.setStrokeWidth(5);
//用畫布畫出一個圓
canvas.drawOval(rectF,paint);
}
具體更多的屬性,自己自定的過程中再摸索其裏面更多的方法。這裏再多做演示了。
自定義控件view覆寫方法的調用順序
添加需要實現的其它方法,並且Log日誌打印
public class RectView extends View {
public RectView(Context context) {
super(context);
}
public RectView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i("does", "onDraw: ");
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.i("does", "onMeasure: ");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.i("does", "onLayout: ");
}
}
運行,看輸出日誌
07-15 12:37:22.155 3907-3907/com.example.does.customview I/does: onMeasure:
07-15 12:37:22.328 3907-3907/com.example.does.customview I/does: onMeasure:
07-15 12:37:22.329 3907-3907/com.example.does.customview I/does: onLayout:
07-15 12:37:22.388 3907-3907/com.example.does.customview I/does: onMeasure:
07-15 12:37:22.388 3907-3907/com.example.does.customview I/does: onLayout:
07-15 12:37:22.388 3907-3907/com.example.does.customview I/does: onDraw:
顯示測量兩次,顯示,再測量,最後繪畫。
爲什麼這樣?因爲我們自定義View添加到xml佈局文件裏面去的,系統是先測量我們自定義控件的,寬度高度,然後再測量父控件的寬度高度,然後爲了保險一點,系統再次測量我們的自定義控件的高度,最後再畫出來。
onMeasure()方法講解
我們先用一個實例看看具體有什麼樣的效果。
我在onDraw()裏給canvas畫一個顏色,然後在xml裏面添加兩個寬高屬性,都是warp_content
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLUE);
}
<com.example.does.customview.RectView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
運行,效果如下。
如果我再修改xml的屬性,將高度改成50dp,出現的是這樣的效果,
<com.example.does.customview.RectView
android:layout_width="wrap_content"
android:layout_height="50dp" />
接着,我們在onMeasure方法裏添加一行代碼
setMeasuredDimension(100,100);
最終的效果是這樣的
神奇吧 ?!! 哈哈。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
1.widthMeasureSpec
2.heightMeasureSpec
Log日誌輸入看看分別是什麼值,將setMeasuredDimension(100,100);這行代碼註釋掉。
07-15 14:26:39.508 31129-31129/com.example.does.customview I/does: onMeasure: -2147482568
07-15 14:26:39.508 31129-31129/com.example.does.customview I/does: onMeasure: 1073741955
數值是什麼我們暫且不管它。
這兩個參數分別都是由兩個int類型構成的。我們創建一個變量抽離出來,
int widethMode = MeasureSpec.getMode(widthMeasureSpec);
int widethSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
打印log日誌,
07-15 14:35:35.704 3319-3319/com.example.does.customview I/does: heightMode: 1073741824 widthMode-2147483648
07-15 14:35:35.704 3319-3319/com.example.does.customview I/does: heightSize: 131 widthSize1080
數值我們也暫且不管它。
我們看看getMode裏面的源碼,源碼裏定義了三個變量,分別是
- AT_MOST 表示父控件對自定義控件的最大值
- EXACTLY 表示父控件對自定義控件的精確值
UNSPECIFIED 表示父控件對自定義控件大小無限制 比如listview
log打印輸出,
07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: heightMode: 1073741824 widthMode-2147483648
07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: heightSize: 131 widthSize1080
07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: UNSPECIFIED : 0
07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: AT_MOST : -2147483648
07-15 14:42:04.036 3319-3319/com.example.does.customview I/does: EXACTLY : 1073741824
爲了驗證這三個值,我們在onMeasure()方法裏添加switch語句。
switch (widthMode){
case MeasureSpec.UNSPECIFIED:
Log.i("does", "UNSPECIFIED : "+ MeasureSpec.UNSPECIFIED);
break;
case MeasureSpec.AT_MOST:
Log.i("does", "AT_MOST : "+ MeasureSpec.AT_MOST );
break;
case MeasureSpec.EXACTLY:
Log.i("does", "EXACTLY : "+ MeasureSpec.EXACTLY );
break;
default:
break;
}
然後運行,發現輸出語句
07-16 01:19:06.835 5190-5190/com.example.does.customview I/does: heightMode: 1073741824 widthMode-2147483648
07-16 01:19:06.835 5190-5190/com.example.does.customview I/does: heightSize: 131 widthSize1080
07-16 01:19:06.835 5190-5190/com.example.does.customview I/does: AT_MOST : -2147483648
此時輸出的是AT_MOST的值。
如果我們將佈局文件裏,我們自定義屬性的寬高的wrap_content改成固定的值,輸出的則是EXACTLY。精確值嘛,有確定的值。
那UNSPECIFIED呢?如果我們的父控件改成用scrollview,那麼輸出的值便是UNSPECIFIED。
onLayout()方法講解
該方法是提供給ViewGroup來放置每個子控件的。通常我們需要配onMeasure來一起使用。
這個方法是每次佈局大小變化,或者位置改變的時候都會本調用。
onLayout(boolean changed, int l, int t, int r, int b) changed 這個控件是否更改位置或者尺寸了
l 相對於父控件的左邊的位置
t 相對於父控件的頂部的位置
r 相對於父控件的右邊的位置
b 相對於父控件的下部的位置
我們必須在這個方法內部將子控件有序的放置在合適的位置。View 提供了llayout(int l, int t, int r, int b)方法給我們。