[Android FrameWork 6.0源碼學習] View的重繪過程之Layout

来源:http://www.cnblogs.com/kezhuang/archive/2017/08/03/7280746.html
-Advertisement-
Play Games

View繪製的三部曲,測量,佈局,繪畫現在我們分析佈局部分測量部分在上篇文章中已經分析過了。不瞭解的可以去我的博客里找一下View的佈局和測量一樣,都是從ViewRootImpl中發起,ViewRootImpl先通過measure來初始化整個的view樹之後會調用onLayout方法來佈局,View ...


View繪製的三部曲,測量,佈局,繪畫
現在我們分析佈局部分
測量部分在上篇文章中已經分析過了。不瞭解的可以去我的博客里找一下

View的佈局和測量一樣,都是從ViewRootImpl中發起,ViewRootImpl先通過measure來初始化整個的view樹
之後會調用onLayout方法來佈局,ViewRootImpl是通過performLayout函數來發起重繪的
比較重要的部分我會寫註釋,註意看註釋就行

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;
        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
            Log.v(TAG, "Laying out " + host + " to (" +
                    host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
        }

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
        try {
            //通過調用DecorView的layout函數,來發起整個view視圖的重繪
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

            mInLayout = false;
            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {
                // requestLayout() was called during layout.
                // If no layout-request flags are set on the requesting views, there is no problem.
                // If some requests are still pending, then we need to clear those flags and do
                // a full request/measure/layout pass to handle this situation.
                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
                if (validLayoutRequesters != null) {
                    // Set this flag to indicate that any further requests are happening during
                    // the second pass, which may result in posting those requests to the next
                    // frame instead
                    mHandlingLayoutInLayoutRequest = true;

                    // Process fresh layout requests, then measure and layout
                    int numValidRequests = validLayoutRequesters.size();
                    for (int i = 0; i < numValidRequests; ++i) {
                        final View view = validLayoutRequesters.get(i);
                        Log.w("View", "requestLayout() improperly called by " + view +
                                " during layout: running second layout pass");
                        view.requestLayout();
                    }
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
                    mInLayout = true;
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                    mHandlingLayoutInLayoutRequest = false;

                    // Check the valid requests again, this time without checking/clearing the
                    // layout flags, since requests happening during the second pass get noop'd
                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                    if (validLayoutRequesters != null) {
                        final ArrayList<View> finalRequesters = validLayoutRequesters;
                        // Post second-pass requests to the next frame
                        getRunQueue().post(new Runnable() {
                            @Override
                            public void run() {
                                int numValidRequests = finalRequesters.size();
                                for (int i = 0; i < numValidRequests; ++i) {
                                    final View view = finalRequesters.get(i);
                                    Log.w("View", "requestLayout() improperly called by " + view +
                                            " during second layout pass: posting in next frame");
                                    view.requestLayout();
                                }
                            }
                        });
                    }
                }

            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
    }

這個函數主要功能就是調用view的layout方法,接下來要分析的就是layout函數了。這個函數在View中,是觸發onLayout函數的方法

 

    @SuppressWarnings({"unchecked"})
    public void layout(int l, int t, int r, int b) {
        //先判斷一下是否需要重新測量
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        
        //判斷是否使用 optical bound 佈局,並且繪製Frame出來
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        //如果需要重新layout的話,就開始調用DecorView的onLayout方法,我們簡單看一下
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

這個函數的工作就是分發整個的佈局流程,先是DecorView,在FrameLayout ....直到整個view tree佈局完畢

 

        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            //獲取界面的邊框如果有偏移,就需要偏移一下view視窗
            getOutsets(mOutsets);
            if (mOutsets.left > 0) {
                offsetLeftAndRight(-mOutsets.left);
            }
            if (mOutsets.top > 0) {
                offsetTopAndBottom(-mOutsets.top);
            }
        }

這個onLayout是在DecorView中,他調用了super,也就是FrameLayout下邊的onLayout方法,我們在繼續看FrameLayout

 

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

這個函數頁很簡單,直接調用了layoutChildren方法去佈局各種子view

    void layoutChildren(int left, int top, int right, int bottom,
                                  boolean forceLeftGravity) {
        final int count = getChildCount();

        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();

        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();
        //開始佈局,目前這個是FrameLayout,特性就是預設左上角,且會z軸覆蓋
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;
                //處理對齊方式
                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }
            
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }

                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }
                //佈局子view,以此類推,會佈局完整個view樹
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

上面方法運行完後,整個的佈局過程就結束了。view這塊的設計非常棒,採用了組合模式去設計,在上邊迴圈中去調用layout方法,layout在去觸發子view的onLayout來按照各自的規則去佈局,直到整個view樹迴圈完畢


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 概述: 身份證的校驗,識別,分離,處處可見。最近H5移動端的項目就需要掃碼獲取身份證,根據身份證自動識別省份、性別、年齡、生日信息。這裡分享完善版,希望大家喜歡。 環境: 依賴jQuery、BootStrap Html JavaScript:身份證驗證及自動識別部分 JavaScript:表單提交校 ...
  • //AJAX $.ajax({ url:"data.txt", type:'get', dataType:'json', data:null, async:true, timeout:1000,//設定超時時間 一般都是3000毫秒 cache:false,//設定GET請求的時候不走 緩存數據,原 ...
  • 這三個事件只在手機上生效 touchstart,手指開始觸屏 touchmove,手指移動 touchend,手指觸屏結束 這個事件在手機上跟在pc端都生效 scroll事件 addeventlistener(name,callback,optional,useCapture) useCapture ...
  • 所有方法基於這個數組: var arr=[1,3,4,5,6,7,8]; 1.length屬性: arr.length;//7 動態獲取數組長度. 2.shift: arr.shift();//1 刪除數組中第一個元素,返回刪除的那個值,並將長度減1;如:console.log(arr.shift( ...
  • 介紹一下 impress.js是一個非常炫酷的幻燈片展示框架,依靠CSS3技術。 impress.js使用起來非常簡單,下麵就來簡單介紹一下其用法。 Start 首先,當然要引入impress.js。 在div標簽設置id為impress(不要求一定是div),然後在你想進行展示的地方加上calss ...
  • 我們開發軟體中應用各種模式,主要是為了 1. 職責劃分:一個類只做一件事 2. 易用,可維護,方便擴展 3. 解耦,相互獨立,可單獨測試 各種設計模式其實都是在解決上面的問題,讓我們對比看看吧。 一、如何理解MVC設計模式 在通常的定義中,MVC 是下圖的結構 但是在 cocoa 體系中,蘋果建議的 ...
  • TextView實現圖文混合編排 一、簡介 在這裡實現圖文混合編排使用的是:TextView中預定義的類似Html的標簽 二、方法 * 1、設置好html標簽的文本 String html="<font>圖片1</font><img src='image1'/>"; html+="<font>圖片2 ...
  • TextView兩種顯示link的方法 一、簡介 也是TextView顯示文本控制項兩種方法 也是顯示豐富的文本 二、方法 TextView兩種顯示link的方法 1)通過TextView裡面的類html標簽 * 1、設置好html標簽的文本 String text1="<font color='re ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...