2019日更挑戰(zhàn)(五),Android--聊聊View的繪制

瞎扯

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();
    }
}
image.png

開(kāi)發(fā)的人早就想到了這點(diǎn).所以寫了這個(gè)子類.

這個(gè)DisplayListCanvasRenderNode初始化創(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)繪制的:

自己看,


image.png

其實(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ù)canvasDisplayListCanvas
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í)的群.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容