Android視圖重繪,使用invalidate還是requestLayout

来源:https://www.cnblogs.com/ganchuanpu/archive/2018/04/12/8807919.html
-Advertisement-
Play Games

概述 在我們在進行自定義View的相關開發中,當我們更改了當前View的狀態,比如大小,位置等,我們需要重新刷新整個界面,保證顯示最新的狀態。在Android中,讓當前的視圖重繪有兩種方式,invalidate和requestLayout,今天我們看看這兩種方式的原理以及區別。 分析 invalid ...


概述

在我們在進行自定義View的相關開發中,當我們更改了當前View的狀態,比如大小,位置等,我們需要重新刷新整個界面,保證顯示最新的狀態。在Android中,讓當前的視圖重繪有兩種方式,invalidate和requestLayout,今天我們看看這兩種方式的原理以及區別。

分析

invalidate的原理

public void invalidate() {
        invalidate(true);
}

最後會調用到invalidateInternal這個方法

 1 void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
 2             boolean fullInvalidate) {
 3         if (mGhostView != null) {
 4             mGhostView.invalidate(true);
 5             return;
 6         }
 7 
 8         if (skipInvalidate()) {
 9             return;
10         }
11 
12         if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
13                 || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
14                 || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
15                 || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
16             if (fullInvalidate) {
17                 mLastIsOpaque = isOpaque();
18                 mPrivateFlags &= ~PFLAG_DRAWN;
19             }
20 
21             mPrivateFlags |= PFLAG_DIRTY;
22 
23             if (invalidateCache) {
24                 mPrivateFlags |= PFLAG_INVALIDATED;
25                 mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
26             }
27 
28             // Propagate the damage rectangle to the parent view.
29             final AttachInfo ai = mAttachInfo;
30             final ViewParent p = mParent;
31             if (p != null && ai != null && l < r && t < b) {
32                 final Rect damage = ai.mTmpInvalRect;
33                 damage.set(l, t, r, b);
34                 p.invalidateChild(this, damage);
35             }
36             .....

我們看到方法的最後調用了ViewParent的invalidateChild方法,因為ViewParent是個介面,invalidateChild是空實現,我們去看看它的實現類ViewRootImpl中的invalidateChild是如何做的

 @Override
    public void invalidateChild(View child, Rect dirty) {
        invalidateChildInParent(null, dirty);
    }

 

 1  @Override
 2     public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
 3         checkThread();
 4         if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
 5 
 6         if (dirty == null) {
 7             invalidate();
 8             return null;
 9         } else if (dirty.isEmpty() && !mIsAnimating) {
10             return null;
11         }
12 
13         if (mCurScrollY != 0 || mTranslator != null) {
14             mTempRect.set(dirty);
15             dirty = mTempRect;
16             if (mCurScrollY != 0) {
17                 dirty.offset(0, -mCurScrollY);
18             }
19             if (mTranslator != null) {
20                 mTranslator.translateRectInAppWindowToScreen(dirty);
21             }
22             if (mAttachInfo.mScalingRequired) {
23                 dirty.inset(-1, -1);
24             }
25         }
26 
27         invalidateRectOnScreen(dirty);
28 
29         return null;
30     }

又會調用ViewRootImpl中的invalidate方法

void invalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        if (!mWillDrawSoon) {
            scheduleTraversals();
        }
    }

這裡調用了scheduleTraversals重新開始了View的繪製,我們知道View的繪製是從ViewRootImpl的performTraversals方法開始的。我們看看scheduleTraversals是不是觸發了performTraversals。

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

在scheduleTraversals方法中我們發現了一個mTraversalRunnable對象,這個對象就是我們要觀察的重點

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

我們發現這個對象就是一個Runnable對象,我們在scheduleTraversals方法中傳入mTraversalRunnable 就會執行run方法,其中又調用了doTraversal這個方法

 void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

最後我們發現在doTraversal方法中調用了performTraversals開始了View的重新繪製,這就是invalidate的整個過程。

requestLayout的原理

public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

其中會調用ViewParent的requestLayout方法,同樣,我們去看ViewRootImpl中的requestLayout方法。

Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

這裡調用了scheduleTraversals,後面的步驟就和上面invalidate時一樣了。相對來說,requestLayout的流程還是比較簡單的。

區別

既然兩種方式都可以完成View的重繪,那麼有什麼區別呢? 
使用invalidate重繪當前視圖是不會再次執行measure和layout流程的。因為視圖沒有強制重新測量的標誌位,而且大小也沒有發生過變化,所以這時只有draw流程可以得到執行。 
如果你希望視圖的繪製流程可以完完整整地重新走一遍,就不能使用invalidate()方法,而應該調用requestLayout()了

 


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

-Advertisement-
Play Games
更多相關文章
  • 這裡要分享的HanLP是我在學習使用大快DKhadoop大數據一體化平臺時使用到的自然語言處理技術,使用這個組建可以很高效的進行自然語言的處理工作,比如進行文章摘要,語義判別以及提高內容檢索的精確度和有效性等。 本想找個通俗的案例來介紹一下HanLP,一時間也沒想到什麼好的案例,索性就從HanLp... ...
  • 前言 我在上篇博客 “Spring Boot 的實踐與思考” 中比對不同規範的 ORM 框架應用場景的時候提到過主從與讀寫分離,本篇隨筆將針對此和分庫分表進行更深入地探討。 1. 漫談 在進入正題之前,我想先隨意談談對架構的拓展周期的想法(僅個人觀點)。首先,我認為初期規劃不該太複雜或者龐大,無論項 ...
  • 訂閱+鏡像切換 主資料庫掛了之後,鏡像資料庫沒掛,那麼就需要把鏡像資料庫設置成主資料庫。 1.修改主機名,改成和主資料庫一樣的,重啟 2.修改資料庫中的主機名 IF SERVERPROPERTY('ServerName')<>@@SERVERNAME BEGIN DECLARE @srvname s ...
  • 在mysql的語法中。修改或者刪除的時候不能直接調用子查詢的結果集。需要先給子查詢其別名。在調用。 比如:下麵的去除重覆項 DELETE FROM t_equipment_type t WHERE t.type = 2 AND (t.type_code,t.type_name) IN ( SELEC ...
  • OUTPUT 子句 可以在數據進行增刪改的時候,可以返回受影響的行。先準備一張表 1、insert ,影響行在inserted表裡 返回結果: id name 1 a 批量插入: id name 2 b 2、delete ,影響行在deleted表裡 返回結果: id 1 3、update,會將新數 ...
  • 1.1游標的概念 游標(Cursor)它使用戶可逐行訪問由SQL Server返回的結果集。使用游標(cursor)的一個主要的原因就是把集合操作轉換成單個記錄處理方式。用SQL語言從資料庫中檢索數據後,結果放在記憶體的一塊區域中,且結果往往是一個含有多個記錄的集合。游標機制允許用戶在SQL serv ...
  • 當年忠貞為國酬,何曾怕斷頭? 如今天下紅遍,江山靠誰守? 業未就,身軀倦,鬢已秋。 你我之輩,忍將夙願,付與東流? 資料庫結構如下: 倉庫(倉庫號, 城市, 面積) 訂購單(職工號, 供應商號, 訂購單號, 訂購日期) 供應商(供應商號, 供應商名, 地址) 職工(倉庫號, 職工號, 工資) 具體數 ...
  • 在設置標題欄時常常遇到修改標題、修改返回按鈕標題、增加一些按鈕等需求,實現過程中一般會把UINavigationController、UINavigationBar、navigationItem及self.navigationController.navigationItem之間概念會混淆。 概念描 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...