瞎扯
android中所有控件的基類,其重要性不用說(shuō)了.
裝逼必備
簡(jiǎn)歷上加上擅長(zhǎng)自定義view,實(shí)現(xiàn)各種牛逼效果.
是不是瞬間感覺(jué)有逼格了.
View做了什么.
記得剛寫Android的時(shí)候.不怎么看源碼
那時(shí)我就認(rèn)為.屏幕展示出來(lái)的東西,就是view這個(gè)類實(shí)現(xiàn)出來(lái)的,覺(jué)得很神奇.
然后看源碼,也是各種類,不明所以.
后來(lái),我才明白,跟想的完全不是一回事
View繪制
實(shí)際上,之所以能在界面上看到不同的View.
真正做事的.并不是View.
是誰(shuí)呢?
Canvas
我們能在屏幕上看到內(nèi)容.都是Canvas畫(huà)出來(lái)的.
不管是圖片也好.背景也好.控件.動(dòng)畫(huà)也好
那View做了什么呢?
持有繪制參數(shù) 繪制規(guī)則.然后通過(guò)Canvas.繪制出來(lái)的.
參數(shù)指什么:
就是view中保存各種屬性參數(shù).padding,margin,Width,Height等等自定義的參數(shù)
規(guī)則指什么
常說(shuō)的3大方法.
onLayout onDraw onMeasure 這些讓你復(fù)寫的.還有自定義的方法.
作用,無(wú)非就是改變View的參數(shù)屬性而已
View的繪制時(shí)機(jī)
讓view的刷新的方法,invalidate.
如下:
public void invalidate() {
invalidate(true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
//省略..
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
//這里獲取是否有父級(jí).通知繪制
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
//省略..
}
}
但是,看完發(fā)現(xiàn).除了通知父級(jí)以外.根本找不到任務(wù)調(diào)用onDraw的代碼
再看:
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called. When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
@CallSuper
public void draw(Canvas canvas) {
//....省略
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
//....省略
}
ViewGroup的:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
這里才調(diào)用了實(shí)際的onDraw方法.
那invalidate是如何操作回調(diào)用到draw()的呢.
代碼太多了.
一張圖可以搞定.不過(guò)是通過(guò)異常來(lái)看的
java.lang.ArithmeticException: divide by zero
at com.example.jlanglang.myapplication.TestView.onDraw(TestView.java:66)
at android.view.View.draw(View.java:20497)
at android.view.View.updateDisplayListIfDirty(View.java:19308)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4375)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4348)
at android.view.View.updateDisplayListIfDirty(View.java:19268)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4375)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4348)
at android.view.View.updateDisplayListIfDirty(View.java:19268)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4375)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4348)
at android.view.View.updateDisplayListIfDirty(View.java:19268)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4375)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4348)
at android.view.View.updateDisplayListIfDirty(View.java:19268)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4375)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4348)
at android.view.View.updateDisplayListIfDirty(View.java:19268)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4375)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4348)
at android.view.View.updateDisplayListIfDirty(View.java:19268)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:729)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:735)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:850)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:3586)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:3370)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2701)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1624)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7926)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1079)
at android.view.Choreographer.doCallbacks(Choreographer.java:885)
at android.view.Choreographer.doFrame(Choreographer.java:809)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1065)
大致就是,調(diào)用后,會(huì)優(yōu)先通知到ViewRootImpl,然后層層遞歸到View.調(diào)用onDraw進(jìn)行繪制
不過(guò),不用擔(dān)心頁(yè)面是否一個(gè)控件改變,整個(gè)頁(yè)面的View都重新繪制.有判斷的.
Canvas的子類--DisplayListCanvas
既然頁(yè)面上的所以東西都是Canvas一次一次畫(huà)出來(lái)的.
那我們平常用的Canvas,要在上面畫(huà)東西,是需要Bitmap的.
難道每個(gè)界面都會(huì)創(chuàng)建一層Bitmap?
我是不是可以拿到onDraw()傳進(jìn)來(lái)的Canvas.改掉這個(gè)Bitmap?
哈哈,天真
public final class DisplayListCanvas extends RecordingCanvas {
//...省略
@Override
public void setBitmap(Bitmap bitmap) {
throw new UnsupportedOperationException();
}
}

開(kāi)發(fā)的人早就想到了這點(diǎn).所以寫了這個(gè)子類.
這個(gè)DisplayListCanvas是RenderNode初始化創(chuàng)建的.
public View(Context context) {
mRenderNode = RenderNode.create(getClass().getName(), this);
}
public DisplayListCanvas start(int width, int height) {
return DisplayListCanvas.obtain(this, width, height);
}
static DisplayListCanvas obtain(@NonNull RenderNode node, int width, int height) {
if (node == null) throw new IllegalArgumentException("node cannot be null");
DisplayListCanvas canvas = sPool.acquire();
if (canvas == null) {
canvas = new DisplayListCanvas(node, width, height);
} else {
nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, node.mNativeRenderNode,
width, height);
}
canvas.mNode = node;
canvas.mWidth = width;
canvas.mHeight = height;
return canvas;
}
而RenderNode是啥?
/**
* RenderNode holding View properties, potentially holding a DisplayList of View content.
* <p>
* When non-null and valid, this is expected to contain an up-to-date copy
* of the View content. Its DisplayList content is cleared on temporary detach and reset on
* cleanup.
*/
final RenderNode mRenderNode;
每個(gè)View都會(huì)有這玩意.你說(shuō)干啥的,繪制的唄.
所以DisplayListCanvas,基本就是專門繪制布局的Canvas.
canvas如何實(shí)現(xiàn)繪制的:
自己看,

其實(shí)Canvas也就是一個(gè)鏈接底層c的jni交互類而已.具體實(shí)現(xiàn)調(diào)用硬件還是c
總結(jié):
今天周末,稍微多寫點(diǎn)東西,好像也沒(méi)寫什么,還是一堆廢話,哈哈
大致想說(shuō)的就是.
所有的界面展示出來(lái)的東西,都是canvas畫(huà)的
View的源碼看起來(lái)嚇人,實(shí)際上.
如果只看頭尾,不去管那些隱藏的方法邏輯
其實(shí)就幾個(gè)注意的地方:
1.onDraw的參數(shù)canvas是DisplayListCanvas
2.只有調(diào)用了invalidate之類的方法,才會(huì)刷新布局
3.View真正有繪制邏輯是draw(),onDraw只是給我們復(fù)寫的.
4.invalidate調(diào)用,就像事件分發(fā)一樣.會(huì)一直往上到ViewRootImpl.然后再往下到需要繪制的view的Preant,再通過(guò)view的引用調(diào)用draw()
就這樣吧.日更第5天.
交流群:493180098,這是個(gè)很少吹水,交流學(xué)習(xí)的群.