View和ViewGroup的基本繪製流程
阿新 • • 發佈:2019-02-20
需要了解的
- 先來張圖說明一下它們的關係
你還要知道ViewGroup之間是可以巢狀的.
View的繪製流程
- 不知道大家有沒有這種疑惑, 為什麼我們在寫佈局檔案的時候, 一定要寫layout_width和layout_height呢, 難道就沒有預設值嗎? 顏色, 背景, 等等其他的都有預設值, 為什麼寬高就一定要我們手動寫呢? 接下來就讓我們一起來解答這個疑惑吧.
- 繪製流程的原始碼就不貼出來了, 有興趣的可以開啟View的原始碼對照著來看, 印象會更深刻, 當然, 不看原始碼, 理解以下的例項程式碼, 也不會影響你對整個流程的理解.
- 首先看一下View的繪製流程示例:
public class MyView extends View {
private static final String TAG = "MyView";
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* measure - > onMeasure ,view的原始碼中,measure會呼叫onMeasure
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //測量, 表示這個view的大小, 在View的原始碼中, Measure是final修飾的, 我們只能重寫onMeasure方法
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d(TAG, "measure");
}
/**
* layout - > setFrame 和 onLayout
*/
@Override
public void layout(int l, int t, int r, int b) { //佈局,決定了擺放在父容器中的哪個位置
// TODO Auto-generated method stub
super.layout(l, t, r, b);
Log.d(TAG, "layout");
}
/**
* draw - > onDraw
*/
@Override
public void draw(Canvas canvas) { //繪製
// TODO Auto-generated method stub
super.draw(canvas);
Log.d(TAG, "draw");
}
}
- 以上就是View繪製顯示在螢幕上必定會呼叫的三個方法, 可能你還不太理解, 不過沒關係, 先混個臉熟, 有個印象先, 下面我們一個個分析.
View的onMeasure
- 來看一段Demo
public class MyView extends View {
private static final String TAG = "MyView";
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* measure -> onMeasure
*
* 1.父容器拿到孩子的申請的寬高layout_width, layout_height封裝成寬高的期望widthMeasureSpec和heightMeasureSpec
* 父容器Relativelayout(或者其他Linearlyout)
* 呼叫MyView的 measure(int widthMeasureSpec, int heightMeasureSpec)傳入對孩子寬高的期望
* measure -> onMeasure(widthMeasureSpec, heightMeasureSpec)
*
* @param widthMeasureSpec 父容器(RelativeLaoyut)對孩子MyView的寬度期望, 跟layout_width相關
* @param heightMeasureSpec 父容器(RelativeLaoyut)對孩子MyView的高度期望, 跟layout_height相關
* 這是我們為什麼一定要指定layout_width和layout_height的原因.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/**
* int widthMeasureSpec
* 32位二進位制
* 前兩位 是測量模式 mode
* public static final int UNSPECIFIED = 0 << MODE_SHIFT; 父容器對孩子沒有任何的限制,孩子想多大多大
* public static final int EXACTLY = 1 << MODE_SHIFT; 父容器對孩子有確切的大小要求,大小就會後30位
* public static final int AT_MOST = 2 << MODE_SHIFT; 父容器對孩子的最大值有要求,大小就會後30位
* 後30位表示大小
*/
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
Log.d(TAG, "onMeasure mode " + (mode>>30) + " " + size);
//super方法預設使用父容器對我的期望的寬高
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//我們也可以不呼叫super直接呼叫setMeasuredDimension(50, 50)來指定寬高
}
}
- 註釋裡的setMeasuredDimension(int measuredWidth, int measuredHeight)是一個重要的方法, 它是整個測量結束的標誌, 只有這個方法呼叫了, 我們才能呼叫getMeasuredWidth()或者getMeasuredHeight()方法獲得測量寬高(注意, 不是實際寬高, 實際寬高要在佈局完成之後)。
- 自定義View如果要使用wrap_content屬性的話,則需重寫onMeasure方法。
View的layout
- layout方法裡面呼叫setFrame(),給View的上下左右四個位置mLeft, mTop,mRight, mBottom賦值,完成佈局工作.
- onLayout()是一個空的方法,說明具體的佈局不應該由view來決定
- 我們的view並不需要關心layout方法, 佈局的事應該交由父容器去處理, 讓它決定它的孩子應該擺放在哪個地方.
- 在佈局完成後, 我們才能呼叫getWidth()和getHeight獲得實際的寬高.
- getWidth()和getMeasuredWidth()的區別: getMeasureWidth()方法在measure()過程結束後就可以獲取到了,而getWidth()方法要在layout()過程結束後才能獲取到。另外,getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設定的,而getWidth()方法中的值則是通過檢視右邊的座標減去左邊的座標計算出來的。
View的draw
view的繪製分為6步:
- 對檢視的背景進行繪製
- If necessary, save the canvas’ layers to prepare for fading (暫時忽略它)
- 對檢視的內容進行繪製, 在onDraw(canvas)方法中完成
- 對當前檢視的所有子檢視進行繪製 ,呼叫dispatchDraw。
- If necessary, draw the fading edges and restore layers (暫時忽略它)
- 繪製裝飾品(如滾動條)任何一個檢視都是有滾動條的,只是一般情況下我們都沒有讓它顯示出來而已.
即我們關心四個步驟:
- 繪製背景
- 繪製內容
- 繪製孩子
- 繪製裝飾
繪製需要兩個類, 畫布(Canvas)和畫筆(Paint), 通過以下Demo通過onDraw方法利用畫筆在畫布上繪製我們的圖案吧.
public class MyView extends View {
private Paint mPaint;
private Bitmap mBitmap;
private Path mPath;
private RectF mOval;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
//設定去鋸齒
mPaint.setAntiAlias(true);
//配置畫筆,畫空心圓
mPaint.setStyle(Style.STROKE);
//設定畫筆寬度
mPaint.setStrokeWidth(3);
//設定畫筆顏色
mPaint.setColor(Color.BLUE);
//畫圖片時需要設定圖片
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.haha);
//設定扇形的大小
mOval = new RectF(5, 5, 195, 195);
initPath();
}
private void initPath() {
mPath = new Path();
//確定三個點
int x1 = 100, y1 = 5;
int x2 = 195, y2 = 195;
int x3 = 5, y3 = 195;
//移動到第一個點
mPath.moveTo(x1, y1);
//連結第一個點和第二個點
mPath.lineTo(x2, y2);
//連結第二個點和第三個點
mPath.lineTo(x3, y3);
mPath.lineTo(x1, y1);
}
/**
* 不要在onDraw方法裡面建立新的物件,因為onDraw方法可能會頻繁呼叫
*/
@Override
protected void onDraw(Canvas canvas) {
// 6. 裁剪
// canvas.clipPath(mPath);
// 1. 畫直線
// int startX =5, startY = 100;
// int stopX = 195, stopY = 100;
// canvas.drawLine(startX, startY, stopX, stopY, mPaint);
// 2. 畫圓
// int cx = 100, cy = 100;
// int radius = 80;
// canvas.drawCircle(cx, cy, radius, mPaint);
// 3. 畫空心圓
// 4. 畫圖片
// canvas.drawBitmap(mBitmap, 0, 0, mPaint);
// 5. 畫三角形
// canvas.drawPath(mPath, mPaint);
//7.畫扇形
int startAngle = -90; //開始的角度
int sweepAngle = 45; //掃過的角度
boolean useCenter = false;//是否畫出扇形的兩邊
canvas.drawArc(mOval, startAngle, sweepAngle, useCenter, mPaint);
}
}
View的重新繪製
- invalidate(); //觸發View的重新繪製 onDraw
- postInvalidate(); //請求在主執行緒重新繪製控制元件 onDraw
ViewGroup的繪製流程
- ViewGroup繼承View,繪製流程跟View是一致
ViewGroup的測量
- 相同點:measure -> onMeasure
- 不同點:ViewGroup需要在onMeasure去測量孩子
- 自定義ViewGroup一定要重寫onMeasure方法,如果不重寫則子View獲取不到寬和高。重寫是在onMeasure方法中呼叫measureChildern()方法,遍歷出所有子View並對其進行測量。
ViewGroup的佈局
- 相同點:layout (父容器呼叫) -》 onLayout
- 不同點:ViewGroup需要實現onLayout方法去佈局孩子,呼叫孩子的layout方法,指定孩子上下左右的位置
- requestLayout();//請求重新佈局 onLayout
ViewGroup的繪製
- 相同點:draw -> onDraw
- 不同點:ViewGroup一般不繪製自己,ViewGroup預設實現dispatchDraw去繪製孩子