Android 手把手教您自定義ViewGroup(二) 實戰篇 -> 實現FlowLayout

轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/38352503 ,本文出自【張鴻洋的博客】

1、概述

上一篇已經基本給大家介紹瞭如何自定義ViewGroup,如果你還不瞭解,請查看:Android 手把手教您自定ViewGroup ,本篇將使用上篇介紹的方法,給大家帶來一個實例:實現FlowLayout,何爲FlowLayout,如果對Java的Swing比較熟悉的話一定不會陌生,就是控件根據ViewGroup的寬,自動的往右添加,如果當前行剩餘空間不足,則自動添加到下一行。有點所有的控件都往左飄的感覺,第一行滿了,往第二行飄~所以也叫流式佈局。Android並沒有提供流式佈局,但是某些場合中,流式佈局還是非常適合使用的,比如關鍵字標籤,搜索熱詞列表等,比如下圖:


這些都特別適合使用FlowLayout,本篇博客會帶領大家自己實現FlowLayout,然後使用我們自己定義的FlowLayout實現上面的標籤效果。對了,github已經有了這樣FlowLayout,但是我覺得絲毫不影響我們對其的學習,學會使用一個控件和學會寫一個控件,我相信大家都明白,授人以魚不如授人以漁。

2、簡單的分析

1、對於FlowLayout,需要指定的LayoutParams,我們目前只需要能夠識別margin即可,即使用MarginLayoutParams.

2、onMeasure中計算所有childView的寬和高,然後根據childView的寬和高,計算自己的寬和高。(當然,如果不是wrap_content,直接使用父ViewGroup傳入的計算值即可)

3、onLayout中對所有的childView進行佈局。

3、generateLayoutParams

因爲我們只需要支持margin,所以直接使用系統的MarginLayoutParams

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2. public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)  
  3. {  
  4.     return new MarginLayoutParams(getContext(), attrs);  
  5. }  

4、onMeasure

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.      * 負責設置子控件的測量模式和大小 根據所有子控件設置自己的寬和高 
  3.      */  
  4.     @Override  
  5.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
  6.     {  
  7.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  8.         // 獲得它的父容器爲它設置的測量模式和大小  
  9.         int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);  
  10.         int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);  
  11.         int modeWidth = MeasureSpec.getMode(widthMeasureSpec);  
  12.         int modeHeight = MeasureSpec.getMode(heightMeasureSpec);  
  13.   
  14.         Log.e(TAG, sizeWidth + "," + sizeHeight);  
  15.   
  16.         // 如果是warp_content情況下,記錄寬和高  
  17.         int width = 0;  
  18.         int height = 0;  
  19.         /** 
  20.          * 記錄每一行的寬度,width不斷取最大寬度 
  21.          */  
  22.         int lineWidth = 0;  
  23.         /** 
  24.          * 每一行的高度,累加至height 
  25.          */  
  26.         int lineHeight = 0;  
  27.   
  28.         int cCount = getChildCount();  
  29.   
  30.         // 遍歷每個子元素  
  31.         for (int i = 0; i < cCount; i++)  
  32.         {  
  33.             View child = getChildAt(i);  
  34.             // 測量每一個child的寬和高  
  35.             measureChild(child, widthMeasureSpec, heightMeasureSpec);  
  36.             // 得到child的lp  
  37.             MarginLayoutParams lp = (MarginLayoutParams) child  
  38.                     .getLayoutParams();  
  39.             // 當前子空間實際佔據的寬度  
  40.             int childWidth = child.getMeasuredWidth() + lp.leftMargin  
  41.                     + lp.rightMargin;  
  42.             // 當前子空間實際佔據的高度  
  43.             int childHeight = child.getMeasuredHeight() + lp.topMargin  
  44.                     + lp.bottomMargin;  
  45.             /** 
  46.              * 如果加入當前child,則超出最大寬度,則的到目前最大寬度給width,類加height 然後開啓新行 
  47.              */  
  48.             if (lineWidth + childWidth > sizeWidth)  
  49.             {  
  50.                 width = Math.max(lineWidth, childWidth);// 取最大的  
  51.                 lineWidth = childWidth; // 重新開啓新行,開始記錄  
  52.                 // 疊加當前高度,  
  53.                 height += lineHeight;  
  54.                 // 開啓記錄下一行的高度  
  55.                 lineHeight = childHeight;  
  56.             } else  
  57.             // 否則累加值lineWidth,lineHeight取最大高度  
  58.             {  
  59.                 lineWidth += childWidth;  
  60.                 lineHeight = Math.max(lineHeight, childHeight);  
  61.             }  
  62.             // 如果是最後一個,則將當前記錄的最大寬度和當前lineWidth做比較  
  63.             if (i == cCount - 1)  
  64.             {  
  65.                 width = Math.max(width, lineWidth);  
  66.                 height += lineHeight;  
  67.             }  
  68.   
  69.         }  
  70.         setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth  
  71.                 : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight  
  72.                 : height);  
  73.   
  74.     }  

首先得到其父容器傳入的測量模式和寬高的計算值,然後遍歷所有的childView,使用measureChild方法對所有的childView進行測量。然後根據所有childView的測量得出的寬和高得到該ViewGroup如果設置爲wrap_content時的寬和高。最後根據模式,如果是MeasureSpec.EXACTLY則直接使用父ViewGroup傳入的寬和高,否則設置爲自己計算的寬和高。

5、onLayout

onLayout中完成對所有childView的位置以及大小的指定

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.      * 存儲所有的View,按行記錄 
  3.      */  
  4.     private List<List<View>> mAllViews = new ArrayList<List<View>>();  
  5.     /** 
  6.      * 記錄每一行的最大高度 
  7.      */  
  8.     private List<Integer> mLineHeight = new ArrayList<Integer>();  
  9.     @Override  
  10.     protected void onLayout(boolean changed, int l, int t, int r, int b)  
  11.     {  
  12.         mAllViews.clear();  
  13.         mLineHeight.clear();  
  14.   
  15.         int width = getWidth();  
  16.   
  17.         int lineWidth = 0;  
  18.         int lineHeight = 0;  
  19.         // 存儲每一行所有的childView  
  20.         List<View> lineViews = new ArrayList<View>();  
  21.         int cCount = getChildCount();  
  22.         // 遍歷所有的孩子  
  23.         for (int i = 0; i < cCount; i++)  
  24.         {  
  25.             View child = getChildAt(i);  
  26.             MarginLayoutParams lp = (MarginLayoutParams) child  
  27.                     .getLayoutParams();  
  28.             int childWidth = child.getMeasuredWidth();  
  29.             int childHeight = child.getMeasuredHeight();  
  30.   
  31.             // 如果已經需要換行  
  32.             if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width)  
  33.             {  
  34.                 // 記錄這一行所有的View以及最大高度  
  35.                 mLineHeight.add(lineHeight);  
  36.                 // 將當前行的childView保存,然後開啓新的ArrayList保存下一行的childView  
  37.                 mAllViews.add(lineViews);  
  38.                 lineWidth = 0;// 重置行寬  
  39.                 lineViews = new ArrayList<View>();  
  40.             }  
  41.             /** 
  42.              * 如果不需要換行,則累加 
  43.              */  
  44.             lineWidth += childWidth + lp.leftMargin + lp.rightMargin;  
  45.             lineHeight = Math.max(lineHeight, childHeight + lp.topMargin  
  46.                     + lp.bottomMargin);  
  47.             lineViews.add(child);  
  48.         }  
  49.         // 記錄最後一行  
  50.         mLineHeight.add(lineHeight);  
  51.         mAllViews.add(lineViews);  
  52.   
  53.         int left = 0;  
  54.         int top = 0;  
  55.         // 得到總行數  
  56.         int lineNums = mAllViews.size();  
  57.         for (int i = 0; i < lineNums; i++)  
  58.         {  
  59.             // 每一行的所有的views  
  60.             lineViews = mAllViews.get(i);  
  61.             // 當前行的最大高度  
  62.             lineHeight = mLineHeight.get(i);  
  63.   
  64.             Log.e(TAG, "第" + i + "行 :" + lineViews.size() + " , " + lineViews);  
  65.             Log.e(TAG, "第" + i + "行, :" + lineHeight);  
  66.   
  67.             // 遍歷當前行所有的View  
  68.             for (int j = 0; j < lineViews.size(); j++)  
  69.             {  
  70.                 View child = lineViews.get(j);  
  71.                 if (child.getVisibility() == View.GONE)  
  72.                 {  
  73.                     continue;  
  74.                 }  
  75.                 MarginLayoutParams lp = (MarginLayoutParams) child  
  76.                         .getLayoutParams();  
  77.   
  78.                 //計算childView的left,top,right,bottom  
  79.                 int lc = left + lp.leftMargin;  
  80.                 int tc = top + lp.topMargin;  
  81.                 int rc =lc + child.getMeasuredWidth();  
  82.                 int bc = tc + child.getMeasuredHeight();  
  83.   
  84.                 Log.e(TAG, child + " , l = " + lc + " , t = " + t + " , r ="  
  85.                         + rc + " , b = " + bc);  
  86.   
  87.                 child.layout(lc, tc, rc, bc);  
  88.                   
  89.                 left += child.getMeasuredWidth() + lp.rightMargin  
  90.                         + lp.leftMargin;  
  91.             }  
  92.             left = 0;  
  93.             top += lineHeight;  
  94.         }  
  95.   
  96.     }  

allViews的每個Item爲每行所有View的List集合。

mLineHeight記錄的爲每行的最大高度。

23-48行,遍歷所有的childView,用於設置allViews的值,以及mLineHeight的值。

57行,根據allViews的長度,遍歷所有的行數

67-91行,遍歷每一行的中所有的childView,對childView的left , top , right , bottom 進行計算,和定位。

92-93行,重置left和top,準備計算下一行的childView的位置。

好了,到此完成了所有的childView的繪製區域的確定,到此,我們的FlowLayout的代碼也結束了~~靜下心來看一看是不是也不難~

6、測試

我準備使用TextView作爲我們的標籤,所以爲其簡單寫了一點樣式:

res/values/styles.xml中:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <style name="text_flag_01">  
  2.        <item name="android:layout_width">wrap_content</item>  
  3.        <item name="android:layout_height">wrap_content</item>  
  4.        <item name="android:layout_margin">4dp</item>  
  5.        <item name="android:background">@drawable/flag_01</item>  
  6.        <item name="android:textColor">#ffffff</item>  
  7.    </style>  

flag_01.xml

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android" >  
  3.   
  4.     <solid android:color="#7690A5" >  
  5.     </solid>  
  6.   
  7.     <corners android:radius="5dp"/>  
  8.     <padding  
  9.         android:bottom="2dp"  
  10.         android:left="10dp"  
  11.         android:right="10dp"  
  12.         android:top="2dp" />  
  13.   
  14. </shape>  

佈局文件:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent"  
  5.     android:background="#E1E6F6"  
  6.     android:orientation="vertical" >  
  7.   
  8.     <com.zhy.zhy_flowlayout02.FlowLayout  
  9.         android:layout_width="fill_parent"  
  10.         android:layout_height="wrap_content" >  
  11.   
  12.         <TextView  
  13.             style="@style/text_flag_01"  
  14.             android:text="Welcome" />  
  15.   
  16.         <TextView  
  17.             style="@style/text_flag_01"  
  18.             android:text="IT工程師" />  
  19.   
  20.         <TextView  
  21.             style="@style/text_flag_01"  
  22.             android:text="學習ing" />  
  23.   
  24.         <TextView  
  25.             style="@style/text_flag_01"  
  26.             android:text="戀愛ing" />  
  27.   
  28.         <TextView  
  29.             style="@style/text_flag_01"  
  30.             android:text="掙錢ing" />  
  31.   
  32.         <TextView  
  33.             style="@style/text_flag_01"  
  34.             android:text="努力ing" />  
  35.   
  36.         <TextView  
  37.             style="@style/text_flag_01"  
  38.             android:text="I thick i can" />  
  39.     </com.zhy.zhy_flowlayout02.FlowLayout>  
  40.       
  41.     </LinearLayout>  

效果圖:


是不是還不錯,下面繼續簡單自定義幾個背景:

res/drawble/flog_02.xml

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android" >  
  3.   
  4.     <solid android:color="#FFFFFF" >  
  5.     </solid>  
  6.   
  7.     <corners android:radius="40dp"/>  
  8.     <stroke android:color="#C9C9C9" android:width="2dp"/>  
  9.       
  10.     <padding  
  11.         android:bottom="2dp"  
  12.         android:left="10dp"  
  13.         android:right="10dp"  
  14.         android:top="2dp" />  
  15.   
  16. </shape>  

flag_03.xml

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android" >  
  3.   
  4.     <solid android:color="#FFFFFF" >  
  5.     </solid>  
  6.   
  7.     <corners android:radius="40dp"/>  
  8.       
  9.     <padding  
  10.         android:bottom="2dp"  
  11.         android:left="10dp"  
  12.         android:right="10dp"  
  13.         android:top="2dp" />  
  14.   
  15. </shape>  

佈局文件:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent"  
  5.     android:background="#E1E6F6"  
  6.     android:orientation="vertical" >  
  7.   
  8.     <com.zhy.zhy_flowlayout02.FlowLayout  
  9.         android:layout_width="fill_parent"  
  10.         android:layout_height="wrap_content" >  
  11.   
  12.         <TextView  
  13.             style="@style/text_flag_01"  
  14.             android:text="Welcome" />  
  15.   
  16.         <TextView  
  17.             style="@style/text_flag_01"  
  18.             android:text="IT工程師" />  
  19.   
  20.         <TextView  
  21.             style="@style/text_flag_01"  
  22.             android:text="學習ing" />  
  23.   
  24.         <TextView  
  25.             style="@style/text_flag_01"  
  26.             android:text="戀愛ing" />  
  27.   
  28.         <TextView  
  29.             style="@style/text_flag_01"  
  30.             android:text="掙錢ing" />  
  31.   
  32.         <TextView  
  33.             style="@style/text_flag_01"  
  34.             android:text="努力ing" />  
  35.   
  36.         <TextView  
  37.             style="@style/text_flag_01"  
  38.             android:text="I thick i can" />  
  39.     </com.zhy.zhy_flowlayout02.FlowLayout>  
  40.       
  41.   
  42.     <com.zhy.zhy_flowlayout02.FlowLayout  
  43.         android:layout_width="fill_parent"  
  44.         android:layout_height="wrap_content"  
  45.         android:layout_marginTop="20dp" >  
  46.   
  47.         <TextView  
  48.             style="@style/text_flag_01"  
  49.             android:background="@drawable/flag_02"  
  50.             android:text="Welcome"  
  51.             android:textColor="#888888" />  
  52.   
  53.         <TextView  
  54.             style="@style/text_flag_01"  
  55.             android:background="@drawable/flag_02"  
  56.             android:text="IT工程師"  
  57.             android:textColor="#888888" />  
  58.   
  59.         <TextView  
  60.             style="@style/text_flag_01"  
  61.             android:background="@drawable/flag_02"  
  62.             android:text="學習ing"  
  63.             android:textColor="#888888" />  
  64.   
  65.         <TextView  
  66.             style="@style/text_flag_01"  
  67.             android:background="@drawable/flag_02"  
  68.             android:text="戀愛ing"  
  69.             android:textColor="#888888" />  
  70.   
  71.         <TextView  
  72.             style="@style/text_flag_01"  
  73.             android:background="@drawable/flag_02"  
  74.             android:text="掙錢ing"  
  75.             android:textColor="#888888" />  
  76.   
  77.         <TextView  
  78.             style="@style/text_flag_01"  
  79.             android:background="@drawable/flag_02"  
  80.             android:text="努力ing"  
  81.             android:textColor="#888888" />  
  82.   
  83.         <TextView  
  84.             style="@style/text_flag_01"  
  85.             android:background="@drawable/flag_02"  
  86.             android:text="I thick i can"  
  87.             android:textColor="#888888" />  
  88.     </com.zhy.zhy_flowlayout02.FlowLayout>  
  89.   
  90.     <com.zhy.zhy_flowlayout02.FlowLayout  
  91.         android:layout_width="fill_parent"  
  92.         android:layout_height="wrap_content"  
  93.         android:layout_marginTop="20dp" >  
  94.   
  95.         <TextView  
  96.             style="@style/text_flag_01"  
  97.             android:background="@drawable/flag_03"  
  98.             android:text="Welcome"  
  99.             android:textColor="#43BBE7" />  
  100.   
  101.         <TextView  
  102.             style="@style/text_flag_01"  
  103.             android:background="@drawable/flag_03"  
  104.             android:text="IT工程師"  
  105.             android:textColor="#43BBE7" />  
  106.   
  107.         <TextView  
  108.             style="@style/text_flag_01"  
  109.             android:background="@drawable/flag_03"  
  110.             android:text="學習ing"  
  111.             android:textColor="#43BBE7" />  
  112.   
  113.         <TextView  
  114.             style="@style/text_flag_01"  
  115.             android:background="@drawable/flag_03"  
  116.             android:text="戀愛ing"  
  117.             android:textColor="#43BBE7" />  
  118.   
  119.         <TextView  
  120.             style="@style/text_flag_01"  
  121.             android:background="@drawable/flag_03"  
  122.             android:text="掙錢ing"  
  123.             android:textColor="#43BBE7" />  
  124.   
  125.         <TextView  
  126.             style="@style/text_flag_01"  
  127.             android:background="@drawable/flag_03"  
  128.             android:text="努力ing"  
  129.             android:textColor="#43BBE7" />  
  130.   
  131.         <TextView  
  132.             style="@style/text_flag_01"  
  133.             android:background="@drawable/flag_03"  
  134.             android:text="I thick i can"  
  135.             android:textColor="#43BBE7" />  
  136.     </com.zhy.zhy_flowlayout02.FlowLayout>  
  137.   
  138. </LinearLayout>  

效果圖:


暫不讚~~上面都是match_parent~~下面固定下寬度,實現文章最開始的移動開發熱門標籤:

flag_04.xml

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android" >  
  3.   
  4.     <solid android:color="#E7E7E7" >  
  5.     </solid>  
  6.     <corners  
  7.         android:radius="30dp"  
  8.          />  
  9.   
  10.     <padding  
  11.         android:bottom="2dp"  
  12.         android:left="10dp"  
  13.         android:right="10dp"  
  14.         android:top="2dp" />  
  15. </shape>  

佈局文件:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <com.zhy.zhy_flowlayout02.FlowLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="200dp"  
  4.     android:layout_height="wrap_content"  
  5.     android:background="#FFFFFF" >  
  6.   
  7.     <TextView  
  8.         style="@style/text_flag_01"  
  9.         android:background="@drawable/flag_04"  
  10.         android:text="Welcome"  
  11.         android:textColor="#323232" />  
  12.   
  13.     <TextView  
  14.         style="@style/text_flag_01"  
  15.         android:background="@drawable/flag_04"  
  16.         android:text="IT工程師"  
  17.         android:textColor="#323232" />  
  18.   
  19.     <TextView  
  20.         style="@style/text_flag_01"  
  21.         android:background="@drawable/flag_04"  
  22.         android:text="學習ing"  
  23.         android:textColor="#323232" />  
  24.   
  25.     <TextView  
  26.         style="@style/text_flag_01"  
  27.         android:background="@drawable/flag_04"  
  28.         android:text="戀愛ing"  
  29.         android:textColor="#323232" />  
  30.   
  31.     <TextView  
  32.         style="@style/text_flag_01"  
  33.         android:background="@drawable/flag_04"  
  34.         android:text="掙錢ing"  
  35.         android:textColor="#323232" />  
  36.   
  37.     <TextView  
  38.         style="@style/text_flag_01"  
  39.         android:background="@drawable/flag_04"  
  40.         android:text="努力ing"  
  41.         android:textColor="#323232" />  
  42.   
  43.     <TextView  
  44.         style="@style/text_flag_01"  
  45.         android:background="@drawable/flag_04"  
  46.         android:text="I thick i can"  
  47.         android:textColor="#323232" />  
  48.   
  49. </com.zhy.zhy_flowlayout02.FlowLayout>  

效果圖:


是不是完全相同~~o了~


如果你覺得本篇博客對你有用,那麼就留個言或者頂一個~~


源碼點擊下載 



發佈了8 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章