Android _優雅實現元素間的分割線 (支持3.0如下)

轉:http://blog.csdn.net/lmj623565791/article/details/42407923html

一、概述

話說,隨着Android SDK版本的升級,不少控件增長了新的屬性方便咱們的使用,好比LinearLayout中多了:divider、showDividers等,用於爲其內部元素添加分隔;可是呢,這樣的屬性在較低版本的SDK中不能被支持,那麼,咱們在開發過程當中,可能會出現這樣的需求:將這個新的特性想辦法作到儘量的向下兼容。有人說,能夠本身寫個新的控件去實現,這樣的確能夠,可是會不會太霸氣了點。難道就沒有接地氣一點的方式麼?嗯,本文就是這樣的一個目的,以一種較爲接地氣的方式,實現新的屬性的向下兼容。java

這樣的狀況在Android中確定會不少,但願能夠以此進行拋磚引玉,你們遇到相似的狀況,提供必定的思路。這纔是這篇博客的真正目的!android

 

二、divider相關用法

爲了保證簡介性,這裏就不討論divider有多麼多麼好用神馬的,由於不是咱們的重點。固然了這裏提供一篇divider的參考:grid-spacing-on-android (基本就是引出divider的用處,有興趣的看下,本文的demo樣子也將參考本連接)。canvas

你們先看一個效果圖:數組

若是要實現,這樣的效果圖,對於這3個Button你們會怎麼作(主要看button):app

簡單嘛:一個水平的線性佈局,內部三個Button的weight都爲1,而後第二個Button設置leftMargin,rightMargin就能夠了。ide

嗯,沒問題,假設如今我有一個需求:通過某個操做Button3隱藏,而後讓Button1和Button2按以下佈局:佈局

這樣的感受是否是不錯,雖然少了一個,徹底不影響美觀;可是,若是按照上述的答案學習

「一個水平的線性佈局,內部三個Button的weight都爲1,而後第二個Button設置leftMargin,rightMargin就能夠了」  Button2的右邊會多出一個rightMargin 。 測試

因此,這樣的製做方式很明顯不是最優秀的,最優秀的方案是,使用Linearlayout的divider、showDividers屬性:

佈局代碼以下:

 

[html]  view plain copy
 
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="wrap_content"  
  4.     android:padding="20dp"  
  5.     android:layout_margin="10dp"  
  6.     android:background="#22444444"  
  7.     android:orientation="vertical" >  
  8.   
  9.     <TextView  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="128dp"  
  12.         android:background="@android:color/darker_gray"  
  13.         android:gravity="center"  
  14.         android:text="application_logo" />  
  15.   
  16.     <LinearLayout  
  17.         android:id="@+id/buttons_container"  
  18.         android:layout_width="match_parent"  
  19.         android:layout_height="wrap_content"  
  20.         android:layout_marginTop="10dp"  
  21.         android:divider="@drawable/divider"  
  22.         android:orientation="horizontal"  
  23.         android:showDividers="middle" >  
  24.   
  25.         <Button  
  26.             android:id="@+id/btn_first"  
  27.             android:layout_width="0dp"  
  28.             android:layout_height="wrap_content"  
  29.             android:layout_weight="1"  
  30.             android:background="#ff0000"  
  31.             android:text="button_1" />  
  32.   
  33.         <Button  
  34.             android:id="@+id/btn_second"  
  35.             android:layout_width="0dp"  
  36.             android:layout_height="wrap_content"  
  37.             android:layout_weight="1"  
  38.             android:background="#00ff00"  
  39.             android:text="button_2" />  
  40.   
  41.         <Button  
  42.             android:id="@+id/btn_third"  
  43.             android:layout_width="0dp"  
  44.             android:layout_height="wrap_content"  
  45.             android:layout_weight="1"  
  46.             android:background="#0000ff"  
  47.             android:text="button_3" />  
  48.     </LinearLayout>  
  49.   
  50. </LinearLayout>  


其實核心就是放置Button的LinearLayout設置了 android:divider="@drawable/divider"和 android:showDividers="middle" ;

 

固然了,有人會說,我就是任性,我就用margin來實現,消失的時候,我顯示去控制button的rightMargin爲0也能夠。嗯,是的,你不嫌麻煩的確沒問題。那麼如今問題又來了,我如今要求每一個Button間的間隔是藍色的,你怎麼辦?注意:咱們這裏的divider的值設置的是一個drawable噢~~沒轍了吧。

本例的drawable(divider.xml):

 

[html]  view plain copy
 
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:shape="rectangle" >  
  4.     <size android:width="15dp" />  
  5.     <solid android:color="@android:color/transparent" />  
  6. </shape>  


下面簡單介紹下divider、showDividers、dividerPadding:

 

divider能夠設置一個drawable做爲元素間的間隔;

showDividers:可取值爲:middle(子元素間)、beginning(第一個元素左邊)、end(最後一個元素右邊)、none;【關於垂直方向的相似】

dividerPadding:設置繪製間隔元素的上下padding。

很簡單,你們本身動手作下實驗就知道了。

好了,到此,咱們簡單介紹了divider等的好處以及使用方式。可是這麼優雅的來實現元素間的間隔只有在3.0以上才被支持,那麼3.0如下怎麼辦呢?

別怕,下面開始本文的重點,讓divider兼容至3.0一下。

三、自定義LinearLayout

看了標題,你們認爲又是自定義LinearLayout麼~~

嗯,繼承LinearLayout是確定的,咱們沒有辦法改變它的源碼,可是能夠經過繼承去改變一些特性。

注意下:如今的目的是兼容至3.0如下:

首先看一個3.0如下的效果圖,否則你說我騙你:

上面的佈局文件在3.0如下顯示就是這麼個樣子,徹底無視間隔。

首先考慮一個問題,對於divider、showDividers 3.0如下的LinearLayout確定無視呀,咋辦呢?

咱們實現個LinearLayout的子類,讓它認識divider和showDividers~~~重視一下這裏,這裏就是咱們向前邁進的一大步,之後遇到相似問題,都這麼幹。

一、識別高版本的屬性

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. public class IcsLinearLayout extends LinearLayout  
  2. {  
  3.     private static final int[] LL = new int[]  
  4.     { //  
  5.         android.R.attr.divider,//  
  6.             android.R.attr.showDividers,//  
  7.             android.R.attr.dividerPadding //  
  8.     };  
  9.   
  10.     private static final int LL_DIVIDER = 0;  
  11.     private static final int LL_SHOW_DIVIDER = 1;  
  12.     private static final int LL_DIVIDER_PADDING = 2;  
  13.   
  14.     /** 
  15.      * android:dividers 
  16.      */  
  17.     private Drawable mDivider;  
  18.     /** 
  19.      * 對應:android:showDividers 
  20.      */  
  21.     private int mShowDividers;  
  22.     /** 
  23.      * 對應:android:dividerPadding 
  24.      */  
  25.     private int mDividerPadding;  
  26.       
  27.     private int mDividerWidth;  
  28.     private int mDividerHeight;  
  29.   
  30.     public IcsLinearLayout(Context context, AttributeSet attrs)  
  31.     {  
  32.         super(context, attrs);  
  33.   
  34.         TypedArray a = context.obtainStyledAttributes(attrs, LL);  
  35.         setDividerDrawable(a.getDrawable(IcsLinearLayout.LL_DIVIDER));  
  36.         mDividerPadding = a.getDimensionPixelSize(LL_DIVIDER_PADDING, 0);  
  37.         mShowDividers = a.getInteger(LL_SHOW_DIVIDER, SHOW_DIVIDER_NONE);  
  38.         a.recycle();  
  39.     }  
  40.       
  41.     /** 
  42.      * 設置分隔元素,初始化寬高等 
  43.      */  
  44.     public void setDividerDrawable(Drawable divider)  
  45.     {  
  46.         if (divider == mDivider)  
  47.         {  
  48.             return;  
  49.         }  
  50.         mDivider = divider;  
  51.         if (divider != null)  
  52.         {  
  53.             mDividerWidth = divider.getIntrinsicWidth();  
  54.             mDividerHeight = divider.getIntrinsicHeight();  
  55.         } else  
  56.         {  
  57.             mDividerWidth = 0;  
  58.             mDividerHeight = 0;  
  59.         }  
  60.         setWillNotDraw(divider == null);  
  61.         requestLayout();  
  62.     }  



 

這裏貼出了成員變量和咱們的構造方法,成員變量中包含了3個屬性對應的接收變量;而後咱們在構造裏面對這三個屬性進行了獲取並賦值給相應的屬性;

這裏你們確定會困惑,我上面定義了一個整型數組,而後幾個變量爲數組下標,最後利用這個數組和下標在構造裏面獲取了值。是否是要問,你爲何這麼寫,你咋知道的?

嗯,這樣,你們隨便下載我以前包含自定義屬性的文章,或者你本身寫的:

這裏我拿了Android BitmapShader 實戰 實現圓形、圓角圖片這個例子中的源代碼,你們就不用下載了,看看我下面就明白了,我在這裏例子中自定義了兩個屬性:type和border_radius,看看咱們的R.java裏面生成了什麼樣的代碼:

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1.  public static final int border_radius=0x7f010001;  
  2.  public static final int type=0x7f010000;  
  3.   
  4.   public static final int[] RoundImageViewByShader = {  
  5.           0x7f010000, 0x7f010001  
  6.       };  
  7. public static final int RoundImageViewByShader_type = 0;  
  8. public static final int RoundImageViewByShader_border_radius = 1;  


看見木有,整型數組,下標;咱們的android.R.attr.xxx對應於上面的常量。是否是和咱們上例定義的如出一轍~~

 

對,自定義屬性怎麼獲取的,你照着模仿就是,無非如今的屬性是android.R.attr.xxx而不是你自定義的,本質沒區別。

好了,如今你們應該知道怎麼獲取高版本的屬性了~~

二、onMeasure

獲取到分隔元素之後,分隔元素確定有寬和高,咱們這裏把分隔元素的寬和高轉化爲合適的margin

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. @Override  
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
  3. {  
  4.     //將分隔元素的寬高轉化爲對應的margin  
  5.     setChildrenDivider();  
  6.     super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  7. }  
  8.   
  9. /** 
  10.  * 將分隔元素的寬高轉化爲對應的margin 
  11.  */  
  12. protected void setChildrenDivider()  
  13. {  
  14.     final int count = getChildCount();  
  15.     for (int i = 0; i < count; i++)  
  16.     {  
  17.         //遍歷每一個子View  
  18.         View child = getChildAt(i);  
  19.         //拿到索引  
  20.         final int index = indexOfChild(child);  
  21.         //方向  
  22.         final int orientation = getOrientation();  
  23.       
  24.         final LayoutParams params = (LayoutParams) child.getLayoutParams();  
  25.         //判斷是否須要在子View左邊繪製分隔  
  26.         if (hasDividerBeforeChildAt(index))  
  27.         {  
  28.             if (orientation == VERTICAL)  
  29.             {  
  30.                 //若是須要,則設置topMargin爲分隔元素的高度(垂直時)  
  31.                 params.topMargin = mDividerHeight;  
  32.             } else  
  33.             {  
  34.                 //若是須要,則設置leftMargin爲分隔元素的寬度(水平時)  
  35.                 params.leftMargin = mDividerWidth;  
  36.             }  
  37.         }  
  38.     }  
  39. }  
  40.   
  41. /** 
  42.  * 判斷是否須要在子View左邊繪製分隔 
  43.  */  
  44. public boolean hasDividerBeforeChildAt(int childIndex)  
  45. {  
  46.     if (childIndex == 0 || childIndex == getChildCount())  
  47.     {  
  48.         return false;  
  49.     }  
  50.     if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0)  
  51.     {  
  52.         boolean hasVisibleViewBefore = false;  
  53.         for (int i = childIndex - 1; i >= 0; i--)  
  54.         {  
  55.             //當前index的前一個元素不爲GONE則認爲須要  
  56.             if (getChildAt(i).getVisibility() != GONE)  
  57.             {  
  58.                 hasVisibleViewBefore = true;  
  59.                 break;  
  60.             }  
  61.         }  
  62.         return hasVisibleViewBefore;  
  63.     }  
  64.     return false;  
  65. }  


onMeasure中,將divider的寬和高,根據mShowDividers的狀況,設置給了合適的View的margin;

 

其實就是,將divider須要佔據的地方,利用margin空出來,咱們最後會在這個空的區域進行繪製divider,別忘了,咱們的divider是個drawable。

三、onDraw

好了,既然已經經過margin把須要繪製的地方空出來了,那麼下面就是繪製了~~~

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. @Override  
  2.     protected void onDraw(Canvas canvas)  
  3.     {  
  4.   
  5.         if (mDivider != null)  
  6.         {  
  7.             if (getOrientation() == VERTICAL)  
  8.             {  
  9.                 //繪製垂直方向的divider  
  10.                 drawDividersVertical(canvas);  
  11.             } else  
  12.             {  
  13.                 //繪製水平方向的divider  
  14.                 drawDividersHorizontal(canvas);  
  15.             }  
  16.         }  
  17.         super.onDraw(canvas);  
  18.     }  
  19.   
  20.     /** 
  21.      * 繪製水平方向的divider 
  22.      * @param canvas 
  23.      */  
  24.     private void drawDividersHorizontal(Canvas canvas)  
  25.     {  
  26.         final int count = getChildCount();  
  27.         //遍歷全部的子View  
  28.         for (int i = 0; i < count; i++)  
  29.         {  
  30.             final View child = getChildAt(i);  
  31.   
  32.             if (child != null && child.getVisibility() != GONE)  
  33.             {  
  34.                 //若是須要繪製divider  
  35.                 if (hasDividerBeforeChildAt(i))  
  36.                 {  
  37.                     final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child  
  38.                             .getLayoutParams();  
  39.                     //獲得開始的位置,getLeft爲當前View的左側,而左側有margin,因此之差爲divider繪製的開始區域  
  40.                     final int left = child.getLeft() - lp.leftMargin/* 
  41.                                                                      * - 
  42.                                                                      * mDividerWidth 
  43.                                                                      */;  
  44.                     //繪製divider  
  45.                     drawVerticalDivider(canvas, left);  
  46.                 }  
  47.             }  
  48.         }  
  49.     }  
  50.       
  51.     /** 
  52.      * 繪製divider,根據left,水平方向繪製 
  53.      * @param canvas 
  54.      * @param left 
  55.      */  
  56.     public void drawVerticalDivider(Canvas canvas, int left)  
  57.     {  
  58.         //設置divider的範圍  
  59.         mDivider.setBounds(left, getPaddingTop() + mDividerPadding, left  
  60.                 + mDividerWidth, getHeight() - getPaddingBottom()  
  61.                 - mDividerPadding);  
  62.         //繪製  
  63.         mDivider.draw(canvas);  
  64.     }  


爲了代碼的簡短以及幫助你們的理解,這裏沒有貼出垂直方向的,水平方向的整個流程是完整的 。後面會貼出來垂直方向的繪製代碼。

 

其實也比較簡單,在onDraw裏面判斷方向,這裏以水平爲例:遍歷全部的子View,若是發現須要在其前繪製divider的,則算出divider的開始的位置(child.getLeft() - lp.leftMargin),而後調用drawVerticalDivider(),設置divider範圍,緊接着繪製出來。

垂直方向同理,就不贅述了,貼上代碼:

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. private void drawDividersVertical(Canvas canvas)  
  2. {  
  3.     final int count = getChildCount();  
  4.     for (int i = 0; i < count; i++)  
  5.     {  
  6.         final View child = getChildAt(i);  
  7.   
  8.         if (child != null && child.getVisibility() != GONE)  
  9.         {  
  10.             if (hasDividerBeforeChildAt(i))  
  11.             {  
  12.                 final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child  
  13.                         .getLayoutParams();  
  14.                 final int top = child.getTop() - lp.topMargin/* 
  15.                                                              * - 
  16.                                                              * mDividerHeight 
  17.                                                              */;  
  18.                 drawHorizontalDivider(canvas, top);  
  19.             }  
  20.         }  
  21.     }  
  22. }  
  23. private void drawHorizontalDivider(Canvas canvas, int top)  
  24. {  
  25.     mDivider.setBounds(getPaddingLeft() + mDividerPadding, top, getWidth()  
  26.             - getPaddingRight() - mDividerPadding, top + mDividerHeight);  
  27.     mDivider.draw(canvas);  
  28. }  


代碼說完了,下面幹嗎呢?固然是測試了~~不測試怎麼知道結果~~

 

四、測試

首先咱們把佈局文件中包含Button的Linelayout換成咱們的com.zhy.view.IcsLinearLayout

在3.0如下機子上運行:

 

[html]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="wrap_content"  
  4.     android:padding="20dp"  
  5.     android:layout_margin="10dp"  
  6.     android:background="#22444444"  
  7.     android:orientation="vertical" >  
  8.   
  9.     <TextView  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="128dp"  
  12.         android:background="@android:color/darker_gray"  
  13.         android:gravity="center"  
  14.         android:text="application_logo" />  
  15.   
  16.     <com.zhy.view.IcsLinearLayout  
  17.         android:id="@+id/buttons_container"  
  18.         android:layout_width="match_parent"  
  19.         android:layout_height="wrap_content"  
  20.         android:layout_marginTop="10dp"  
  21.         android:divider="@drawable/divider"  
  22.         android:orientation="horizontal"  
  23.         android:showDividers="middle" >  
  24.   
  25.         <Button  
  26.             android:id="@+id/btn_first"  
  27.             android:layout_width="0dp"  
  28.             android:layout_height="wrap_content"  
  29.             android:layout_weight="1"  
  30.             android:background="#ff0000"  
  31.             android:text="button_1" />  
  32.   
  33.         <Button  
  34.             android:id="@+id/btn_second"  
  35.             android:layout_width="0dp"  
  36.             android:layout_height="wrap_content"  
  37.             android:layout_weight="1"  
  38.             android:background="#00ff00"  
  39.             android:text="button_2" />  
  40.   
  41.         <Button  
  42.             android:id="@+id/btn_third"  
  43.             android:layout_width="0dp"  
  44.             android:layout_height="wrap_content"  
  45.             android:layout_weight="1"  
  46.             android:background="#0000ff"  
  47.             android:text="button_3" />  
  48.     </com.zhy.view.IcsLinearLayout>  
  49.   
  50. </LinearLayout>  


效果圖:

 

久違了~~咱們的分隔~~能夠看到在3.0如下機器完美實現~~~

but,別高興太早,咱們這麼改,3.0以上機器是什麼樣子呢?

哈哈,是否是完美實現了間隔~~~

如今能夠高興了~~~

你們如今確定有困惑,我擦,你在構造裏面獲取divider,而後在onDraw裏面本身繪製了divider,你們都知道3.0以上是支持的呀,確定也會繪製呀,你說沒衝突誰信呀~~~!!!

五、答疑

一、爲何和3.0以上沒有發生一些該有的衝突?

嗯,是的,3.0以上是支持的,爲何咱們在onDraw裏面本身繪製,而後調用super.onDraw居然沒有發生什麼衝突?

緣由很簡單:咱們看4.4LinearLayout的源碼:

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. @Override  
  2.     protected void onDraw(Canvas canvas) {  
  3.         if (mDivider == null) {  
  4.             return;  
  5.         }  


其實,源碼中也是在onDraw裏面去繪製divider,可是若是mDivider爲null,就會return。之因此沒有衝突,是由於咱們前面的某個操做讓其mDivider成員變量爲null了~~

 

如今去LinearLayout的構造方法:

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
  1. public LinearLayout(Context context, AttributeSet attrs, int defStyle) {  
  2.         super(context, attrs, defStyle);  
  3.   
  4.         ...  
  5.         setDividerDrawable(a.getDrawable(R.styleable.LinearLayout_divider));  
  6.         ...  
  7.     }  
  8.   public void setDividerDrawable(Drawable divider) {  
  9.         if (divider == mDivider) {  
  10.             return;  
  11.         }  
  12.         mDivider = divider;  
  13.         if (divider != null) {  
  14.             mDividerWidth = divider.getIntrinsicWidth();  
  15.             mDividerHeight = divider.getIntrinsicHeight();  
  16.         } else {  
  17.             mDividerWidth = 0;  
  18.             mDividerHeight = 0;  
  19.         }  
  20.         setWillNotDraw(divider == null);  
  21.         requestLayout();  
  22.     }  


能夠看到它在其構造中調用setDividerDrawable爲其mDivider賦值,關鍵來了~~~~咱們的自定義的LinearLayout複寫了這個方法,也就是說,setDividerDrawable會調用子類的方法,這個父類的setDividerDrawable根本不會調用,從而致使mDivider爲null了~~~

 

爲null就對應了onDraw裏面的繪製~~ok~解答完畢。

二、這篇博客怎麼想到的?你咋知道代碼這麼寫?

我相信這樣的問題,不少人感興趣,其實也算巧合,以前知道有divider這個屬性;而後前段時間寫Android 教你打造炫酷的ViewPagerIndicator 不只僅是高仿MIUI 這篇博客的時候,特地去看了ViewPagerIndicator那個開源項目源碼,發現了一個IcsLinearLayout這樣的一個類,相似咱們上面實現的,固然了,我作了必定的修改;因而乎,仔細研究了這了類,以爲頗有必要寫成博客,達到文章開頭所敘述的的目的~其實你們有心的話,根據咱們上述的代碼,去看看LinearLayout源碼中如何去繪製divider,你會發現代碼基本是同樣的(ps:你沒發現問題1中的LinearLayout源碼的setDividerDrawable和咱們寫的如出一轍麼~);

 

好了,到此整篇文章就結束了,仍是那句話:」這樣的狀況在Android中確定會不少,但願能夠以此進行拋磚引玉,你們遇到相似的狀況,提供必定的思路。這纔是這篇博客的真正目的!「 不要偷懶花點時間去敲一敲,看一看,想想,你會發現裏面還藏着不少東西,別怕浪費時間,我研究和寫這篇博客的時間絕對超出你所學習這篇博客的時間~~

 

最後,歐來來~~

 

 

源碼點擊下載

相關文章
相關標籤/搜索