簡單的例子瞭解自定義ViewGroup(一)

来源:http://www.cnblogs.com/qifengshi/archive/2016/08/16/5774852.html
-Advertisement-
Play Games

在Android中,控制項可以分為ViewGroup控制項與View控制項。自定義View控制項,我之前的文章已經說過。這次我們主要說一下自定義ViewGroup控制項。ViewGroup是作為父控制項可以包含多個View控制項,並管理其中包含的View控制項。 一般自定義ViewGroup的流程如下: 我們一般不 ...


在Android中,控制項可以分為ViewGroup控制項與View控制項。自定義View控制項,我之前的文章已經說過。這次我們主要說一下自定義ViewGroup控制項。ViewGroup是作為父控制項可以包含多個View控制項,並管理其中包含的View控制項。
一般自定義ViewGroup的流程如下:

  • onMeasure()
  • onLayout()
    我們一般不需要像自定義View一樣重寫onDraw(),這裡需要多寫一個onLayout來擺放子View的位置。除了onLayout方法之外,我們還需要確定LayoutParams,這個是子View告訴父佈局的一些參數信息,比如MarginLayoutParams,則表明該ViewGroup支持margin,當然這個也可以沒有。
    下麵我們通過一個例子來說明簡單的自定義ViewGroup

一個簡單的例子

這個例子,我們將寫一個ViewGroup,該ViewGroup中4個button排成一列。這個例子主要說明onMeasure和onLayout的寫法。
首先我們新建一個MyViewGroup繼承ViewGroup,然後重寫onMeasure方法。

   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);
        measureChildren(widthMeasureSpec,heightMeasureSpec);

    }

這個重寫非常的簡單,調用父類的測量方法,然後測量所有的子控制項的,只要子控制項不是wrap_content都會測量精準。這裡為了簡單,沒有去考慮wrap_content的情況,後面我們完善的時候會說道。
然後重寫onLayout()方法

  @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int height = 0;
        int count = getChildCount();
        View child;
        Log.e("ri", count + "");
        for(int i = 0 ;i < count;i++) {
            child = getChildAt(i);
            child.layout(0, height, child.getMeasuredWidth(),height +  child.getMeasuredHeight());
            height += child.getMeasuredHeight();

        }
    }

這裡的代碼很好理解,因為我們要實現4個button一列顯示,然後每個子View的寬度是一樣的,並且每個子View的left和right是一樣的。所以每個子View只有top和bottom不一樣。我們首先定義個高度height初始為0,然後得到所有子View的個數,依次設置每個子View的top和bottom。top就是定義的height,bottom則為height加上子View的高度。設置完後height累加。
xml中佈局如下:

 <com.example.byhieg.viewpratice.MyViewGroup android:layout_height="match_parent"
        android:layout_width="match_parent">

        <Button
            android:text="1"
            android:layout_width="50dp"
            android:layout_height="80dp" />
        <Button
            android:text="2"
            android:layout_width="50dp"
            android:layout_height="80dp" />
        <Button
            android:text="3"
            android:layout_width="50dp"
            android:layout_height="80dp" />
        <Button
            android:text="4"
            android:layout_width="50dp"
            android:layout_height="80dp" />
    </com.example.byhieg.viewpratice.MyViewGroup>

效果如下:

下麵,我們繼續完善這個控制項,讓他適應wrap_content。這裡,我們主要重寫onMeasure()

   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        measureChildren(widthMeasureSpec,heightMeasureSpec);
        //開始處理wrap_content,如果一個子元素都沒有,就設置為0
        if (getChildCount() == 0) {
            setMeasuredDimension(0,0);
        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            //ViewGroup,寬,高都是wrap_content,根據我們的需求,寬度是子控制項的寬度,高度則是所有子控制項的總和
            View childOne = getChildAt(0);
            int childWidth = childOne.getMeasuredWidth();
            int childHeight = childOne.getMeasuredHeight();
            setMeasuredDimension(childWidth, childHeight * getChildCount());
        } else if (widthMode == MeasureSpec.AT_MOST) {
            //ViewGroup的寬度為wrap_content,則高度不需要管,寬度還是自控制項的寬度
            View childOne = getChildAt(0);
            int childWidth = childOne.getMeasuredWidth();
            setMeasuredDimension(childWidth,heightSize);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            //ViewGroup的高度為wrap_content,則寬度不需要管,高度為子View的高度和
            View childOne = getChildAt(0);
            int childHeight = childOne.getMeasuredHeight();
            setMeasuredDimension(widthSize, childHeight * getChildCount());
        }
    }

主要通過註釋,就可以很明白wrap_content的情況下,如何計算viewGroup的高度和寬度。在viewGroup的onMeasure,我們是不需要在這裡面考慮每一個View的寬高,這個通過measureChildren來通知每一個子View自己測量的。我們只需要考慮viewGroup的寬高在自適應的情況下,該是多大。

LayoutParams

在上面這個簡單的ViewGroup中,我們是沒有設置margin的,也就是說,即使我們在子View中設置了margin也是沒有效的。我們需要修改我們的自定義ViewGroup來適應margin的情況。這裡我們為了簡化情況,只設定第一個button有一個android:layout_margin="30dp"的屬性。
這裡,我們修改onMeasure方法,讓viewGroup的寬度變為原來的寬度加上margin的寬度,高度也是原來的高度加上margin的高度。代碼如下:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);

        MarginLayoutParams params = null;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        measureChildren(widthMeasureSpec,heightMeasureSpec);

        //開始處理wrap_content,如果一個子元素都沒有,就設置為0
        if (getChildCount() == 0) {
            setMeasuredDimension(0,0);
        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            //ViewGroup,寬,高都是wrap_content,根據我們的需求,寬度是子控制項的寬度,高度則是所有子控制項的總和
            View childOne = getChildAt(0);
            params = (MarginLayoutParams) childOne.getLayoutParams();
            int childWidth = childOne.getMeasuredWidth();
            int childHeight = childOne.getMeasuredHeight();
            setMeasuredDimension(childWidth + params.leftMargin + params.rightMargin,
                                childHeight * getChildCount() + params.topMargin + params.bottomMargin);

        } else if (widthMode == MeasureSpec.AT_MOST) {
            //ViewGroup的寬度為wrap_content,則高度不需要管,寬度還是自控制項的寬度
            View childOne = getChildAt(0);
            params = (MarginLayoutParams) childOne.getLayoutParams();
            int childWidth = childOne.getMeasuredWidth();
            setMeasuredDimension(childWidth + params.leftMargin + params.rightMargin,heightSize);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            //ViewGroup的高度為wrap_content,則寬度不需要管,高度為子View的高度和
            View childOne = getChildAt(0);
            params = (MarginLayoutParams) childOne.getLayoutParams();
            int childHeight = childOne.getMeasuredHeight();
            setMeasuredDimension(widthSize, childHeight * getChildCount() + params.topMargin + params.bottomMargin);
        }
    }

這裡,註意這個語句params = (MarginLayoutParams) childOne.getLayoutParams(); 如果不重寫layoutParams相關的代碼,這樣直接轉換會出現問題。所以,我們需要重寫如下代碼:讓他返回MarginLayoutParams類型的對象

@Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(),attrs);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT);
    }

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

同樣,我們已經測量得到了viewGroup的寬和高,接下來,需要對添加了margin的view,重新擺放。主要的擺放規則,左邊的坐標為Leftmargin,第一個view的上面的坐標為topMargin,同時,第二個view的上面的坐標要加上bottomMargin。這個只是一個簡單的例子來說明放入margin之後要怎麼考慮,一般不會這麼具體到只計算第一個view的Margin。代碼看看就好

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int height = 0;
        int count = getChildCount();
        View child;
        Log.e("ri", count + "");
        child = getChildAt(0);
        MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
        int c1 = params.leftMargin;
        int c2 = params.topMargin;
        int c3 = c1 + child.getMeasuredWidth();
        int c4 = c2 + child.getMeasuredHeight();
        child.layout(c1,c2, c3,c4);
        height = c4 + params.bottomMargin;
        for(int i = 1 ;i < count;i++) {
            child = getChildAt(i);
            child.layout(c1, height, c3, height + child.getMeasuredHeight());
            height += child.getMeasuredHeight();
        }
    }

總結

這是自定義ViewGroup的第一篇文章,自定義ViewGroup是比自定義View知識點更多,而且應用也更廣泛。這篇只是簡單的介紹了自定義ViewGroup中需要覆寫的方法,但是一般的viewGroup不會是這樣的。上面的東西,用一個LinearLayout佈局實現,然後include該佈局更方便。一般viewGroup都需要我們來實現view事件的分發以及滑動的處理,接下來的文章,講介紹滑動的多種方式。


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

-Advertisement-
Play Games
更多相關文章
  • 接上節:[APP] Android 開發筆記 001 4. 預設項目結構說明: 這裡我使用Sublime Text 進行載入。 1) res目錄: -drawable-*dpi 目錄: 該目錄下放置這各種圖片資源.不同的dpi對應不同的屏幕尺寸 -layout 目錄: 該目錄下放置佈局文件 -val ...
  • 1. 安裝SDK Android SDK http://developer.android.com/sdk/index.html https://dl.google.com/android/android-sdk_r24.4.1-windows.zip (No installer) https:// ...
  • 1、把aar複製到項目中的 libs 裡面 2、在module 裡面的build.gradle 的根目錄添加 3、在module 裡面的build.gradle 的根目錄的 dependencies 標簽裡面添加 其中 SDK-release 是你的aar的名字 4、在做完了前三步以後,會看到在項目 ...
  • iOS開發 中的代理實現 關於今天為什麼要發這篇文字的原因:今天在和同事聊天的時候他跟我說項目中給他的block有時候不太能看的懂,讓我儘量用代理寫,好吧心累了,那就先從寫個代理demo,防止以後他看不懂,嘿嘿 iOS開發 中的代理實現 今天我舉例的東西呢我就不寫demo了,直接從項目中吧需要的片段 ...
  • 解決辦法Item xml 根節點添加 android:descendantFocusability="blocksDescendants" Button 設置 android:focusable="false" 這樣點擊Button 和ListView Item 可以分別響應自己的點擊事件 開發中很 ...
  • 開源框架利與弊 開源框架給開發者提供了便利,避免了重覆造輪子,但是卻隱藏了一些開發上的細節,如果不關註其內部實現,那麼將不利於開發人員掌握核心技術,當然也談不上更好的使用它,計劃分析項目的集成使用和低層實現。 基本四部曲 1. ImageLoaderConfiguration(有預設) 通過Imag ...
  • 代碼: - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. [self showAlertView:@"11111"]; } //自定義彈出框 -(void)sho ...
  • 上一篇(Android 設計隨便說說之簡單實踐(模塊劃分))例舉了應用商店設計來說明怎麼做模塊劃分。模塊劃分主要依賴於第一是業務需求,具體是怎麼樣的業務。應用商店則包括兩個業務,就是向用戶展示applist,和下載app。第二是運行環境,在Android平臺,有androidsdk提供socket等 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...