標籤:android divider
轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/42407923 ,本文出自:【張鴻洋的部落格】
1、概述
話說,隨著Android SDK版本的升級,很多控制項增加了新的屬性方便我們的使用,比如LinearLayout中多了:divider、showDividers等,用於為其內部元素添加分隔;但是呢,這樣的屬性在較低版本的SDK中不能被支援,那麼,我們在開發過程中,可能會出現這樣的需求:將這個新的特性想辦法做到儘可能的向下相容。有人說,可以自己寫個新的控制項去實現,這樣的確可以,但是會不會太霸氣了點。難道就沒有接地氣一點的方式嗎?嗯,本文就是這樣的一個目的,以一種較為接地氣的方式,實現新的屬性的向下相容。
這樣的情況在Android中肯定會很多,希望可以以此進行拋磚引玉,大家遇到類似的情況,提供一定的思路。這才是這篇部落格的真正目的!
2、divider相關用法
為了保證簡介性,這裡就不討論divider有多麼多麼好用神馬的,因為不是我們的重點。當然了這裡提供一篇divider的參考:grid-spacing-on-android (基本就是引出divider的用處,有興趣的看下,本文的demo樣子也將參考本連結)。
大家先看一個:
如果要實現,這樣的,對於這3個Button大家會怎麼做(主要看button):
簡單嘛:一個水平的線性布局,內部三個Button的weight都為1,然後第二個Button設定leftMargin,rightMargin就可以了。
嗯,沒問題,假設現在我有一個需求:經過某個操作Button3隱藏,然後讓Button1和Button2按如下布局:
這樣的感覺是不是不錯,雖然少了一個,完全不影響美觀;但是,如果按照上述的答案
“一個水平的線性布局,內部三個Button的weight都為1,然後第二個Button設定leftMargin,rightMargin就可以了” Button2的右邊會多出一個rightMargin 。
所以,這樣的製作方式很明顯不是最優秀的,最優秀的方案是,使用Linearlayout的divider、showDividers屬性:
布局代碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="20dp" android:layout_margin="10dp" android:background="#22444444" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="128dp" android:background="@android:color/darker_gray" android:gravity="center" android:text="application_logo" /> <LinearLayout android:id="@+id/buttons_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:divider="@drawable/divider" android:orientation="horizontal" android:showDividers="middle" > <Button android:id="@+id/btn_first" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="#ff0000" android:text="button_1" /> <Button android:id="@+id/btn_second" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="#00ff00" android:text="button_2" /> <Button android:id="@+id/btn_third" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="#0000ff" android:text="button_3" /> </LinearLayout></LinearLayout>
其實核心就是放置Button的LinearLayout設定了 android:divider="@drawable/divider"和 android:showDividers="middle" ;
當然了,有人會說,我就是任性,我就用margin來實現,消失的時候,我顯示去控制button的rightMargin為0也可以。嗯,是的,你不嫌麻煩的確沒問題。那麼現在問題又來了,我現在要求每個Button間的間隔是藍色的,你怎麼辦?注意:我們這裡的divider的值設定的是一個drawable噢~~沒轍了吧。
本例的drawable(divider.xml):
<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <size android:width="15dp" /> <solid android:color="@android:color/transparent" /></shape>
下面簡單介紹下divider、showDividers、dividerPadding:
divider可以設定一個drawable作為元素間的間隔;
showDividers:可取值為:middle(子項目間)、beginning(第一個元素左邊)、end(最後一個元素右邊)、none;【關於垂直方向的類似】
dividerPadding:設定繪製間隔元素的上下padding。
很簡單,大家自己動手做下實驗就知道了。
好了,到此,我們簡單介紹了divider等的好處以及使用方式。但是這麼優雅的來實現元素間的間隔只有在3.0以上才被支援,那麼3.0以下怎麼辦呢?
別怕,下面開始本文的重點,讓divider相容至3.0一下。
3、自訂LinearLayout
看了標題,大家認為又是自訂LinearLayout麼~~
嗯,繼承LinearLayout是肯定的,我們沒有辦法改變它的源碼,但是可以通過繼承去改變一些特性。
注意下:現在的目的是相容至3.0以下:
首先看一個3.0以下的,不然你說我騙你:
上面的布局檔案在3.0以下顯示就是這麼個樣子,完全無視間隔。
首先考慮一個問題,對於divider、showDividers 3.0以下的LinearLayout肯定無視呀,咋辦呢?
我們實現個LinearLayout的子類,讓它認識divider和showDividers~~~重視一下這裡,這裡就是我們向前邁進的一大步,以後遇到類似問題,都這麼幹。
1、識別高版本的屬性
public class IcsLinearLayout extends LinearLayout{private static final int[] LL = new int[]{ //android.R.attr.divider,//android.R.attr.showDividers,//android.R.attr.dividerPadding //};private static final int LL_DIVIDER = 0;private static final int LL_SHOW_DIVIDER = 1;private static final int LL_DIVIDER_PADDING = 2;/** * android:dividers */private Drawable mDivider;/** * 對應:android:showDividers */private int mShowDividers;/** * 對應:android:dividerPadding */private int mDividerPadding;private int mDividerWidth;private int mDividerHeight;public IcsLinearLayout(Context context, AttributeSet attrs){super(context, attrs);TypedArray a = context.obtainStyledAttributes(attrs, LL);setDividerDrawable(a.getDrawable(IcsLinearLayout.LL_DIVIDER));mDividerPadding = a.getDimensionPixelSize(LL_DIVIDER_PADDING, 0);mShowDividers = a.getInteger(LL_SHOW_DIVIDER, SHOW_DIVIDER_NONE);a.recycle();}/** * 設定分隔元素,初始化寬高等 */public void setDividerDrawable(Drawable divider){if (divider == mDivider){return;}mDivider = divider;if (divider != null){mDividerWidth = divider.getIntrinsicWidth();mDividerHeight = divider.getIntrinsicHeight();} else{mDividerWidth = 0;mDividerHeight = 0;}setWillNotDraw(divider == null);requestLayout();}
這裡貼出了成員變數和我們的構造方法,成員變數中包含了3個屬性對應的接收變數;然後我們在構造裡面對這三個屬性進行了擷取並賦值給相應的屬性;
這裡大家肯定會困惑,我上面定義了一個整型數組,然後幾個變數為數組下標,最後利用這個數組和下標在構造裡面擷取了值。是不是要問,你為什麼這麼寫,你咋知道的?
嗯,這樣,大家隨便下載我之前包含自訂屬性的文章,或者你自己寫的:
這裡我拿了Android BitmapShader 實戰 實現圓形、圓角圖片這個例子中的原始碼,大家就不用下載了,看看我下面就明白了,我在這裡例子中自訂了兩個屬性:type和border_radius,看看我們的R.java裡面產生了什麼樣的代碼:
public static final int border_radius=0x7f010001; public static final int type=0x7f010000; public static final int[] RoundImageViewByShader = { 0x7f010000, 0x7f010001 }; public static final int RoundImageViewByShader_type = 0; public static final int RoundImageViewByShader_border_radius = 1;
看見木有,整型數組,下標;我們的android.R.attr.xxx對應於上面的常量。是不是和我們上例定義的一模一樣~~
對,自訂屬性怎麼擷取的,你照著模仿就是,無非現在的屬性是android.R.attr.xxx而不是你自訂的,本質沒區別。
好了,現在大家應該知道怎麼擷取高版本的屬性了~~
2、onMeasure
擷取到分隔元素以後,分隔元素肯定有寬和高,我們這裡把分隔元素的寬和高轉化為合適的margin
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){//將分隔元素的寬高轉化為對應的marginsetChildrenDivider();super.onMeasure(widthMeasureSpec, heightMeasureSpec);}/** * 將分隔元素的寬高轉化為對應的margin */protected void setChildrenDivider(){final int count = getChildCount();for (int i = 0; i < count; i++){//遍曆每個子ViewView child = getChildAt(i);//拿到索引final int index = indexOfChild(child);//方向final int orientation = getOrientation();final LayoutParams params = (LayoutParams) child.getLayoutParams();//判斷是否需要在子View左邊繪製分隔if (hasDividerBeforeChildAt(index)){if (orientation == VERTICAL){//如果需要,則設定topMargin為分隔元素的高度(垂直時)params.topMargin = mDividerHeight;} else{//如果需要,則設定leftMargin為分隔元素的寬度(水平時)params.leftMargin = mDividerWidth;}}}}/** * 判斷是否需要在子View左邊繪製分隔 */public boolean hasDividerBeforeChildAt(int childIndex){if (childIndex == 0 || childIndex == getChildCount()){return false;}if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0){boolean hasVisibleViewBefore = false;for (int i = childIndex - 1; i >= 0; i--){//當前index的前一個元素不為GONE則認為需要if (getChildAt(i).getVisibility() != GONE){hasVisibleViewBefore = true;break;}}return hasVisibleViewBefore;}return false;}
onMeasure中,將divider的寬和高,根據mShowDividers的情況,設定給了合適的View的margin;
其實就是,將divider需要佔據的地方,利用margin空出來,我們最後會在這個空的地區進行繪製divider,別忘了,我們的divider是個drawable。
3、onDraw
好了,既然已經通過margin把需要繪製的地方空出來了,那麼下面就是繪製了~~~
@Overrideprotected void onDraw(Canvas canvas){if (mDivider != null){if (getOrientation() == VERTICAL){//繪製垂直方向的dividerdrawDividersVertical(canvas);} else{//繪製水平方向的dividerdrawDividersHorizontal(canvas);}}super.onDraw(canvas);}/** * 繪製水平方向的divider * @param canvas */private void drawDividersHorizontal(Canvas canvas){final int count = getChildCount();//遍曆所有的子Viewfor (int i = 0; i < count; i++){final View child = getChildAt(i);if (child != null && child.getVisibility() != GONE){//如果需要繪製dividerif (hasDividerBeforeChildAt(i)){final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams();//得到開始的位置,getLeft為當前View的左側,而左側有margin,所以之差為divider繪製的開始地區final int left = child.getLeft() - lp.leftMargin/* * - * mDividerWidth */;//繪製dividerdrawVerticalDivider(canvas, left);}}}}/** * 繪製divider,根據left,水平方向繪製 * @param canvas * @param left */public void drawVerticalDivider(Canvas canvas, int left){//設定divider的範圍mDivider.setBounds(left, getPaddingTop() + mDividerPadding, left+ mDividerWidth, getHeight() - getPaddingBottom()- mDividerPadding);//繪製mDivider.draw(canvas);}
為了代碼的簡短以及協助大家的理解,這裡沒有貼出垂直方向的,水平方向的整個流程是完整的 。後面會貼出來垂直方向的繪製代碼。
其實也比較簡單,在onDraw裡面判斷方向,這裡以水平為例:遍曆所有的子View,如果發現需要在其前繪製divider的,則算出divider的開始的位置(child.getLeft() - lp.leftMargin),然後調用drawVerticalDivider(),設定divider範圍,緊接著繪製出來。
垂直方向同理,就不贅述了,貼上代碼:
private void drawDividersVertical(Canvas canvas){final int count = getChildCount();for (int i = 0; i < count; i++){final View child = getChildAt(i);if (child != null && child.getVisibility() != GONE){if (hasDividerBeforeChildAt(i)){final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams();final int top = child.getTop() - lp.topMargin/* * - * mDividerHeight */;drawHorizontalDivider(canvas, top);}}}}private void drawHorizontalDivider(Canvas canvas, int top){mDivider.setBounds(getPaddingLeft() + mDividerPadding, top, getWidth()- getPaddingRight() - mDividerPadding, top + mDividerHeight);mDivider.draw(canvas);}
代碼說完了,下面幹嘛呢?當然是測試了~~不測試怎麼知道結果~~
4、測試
首先我們把布局檔案中包含Button的Linelayout換成我們的com.zhy.view.IcsLinearLayout
在3.0以下機子上運行:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="20dp" android:layout_margin="10dp" android:background="#22444444" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="128dp" android:background="@android:color/darker_gray" android:gravity="center" android:text="application_logo" /> <com.zhy.view.IcsLinearLayout android:id="@+id/buttons_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:divider="@drawable/divider" android:orientation="horizontal" android:showDividers="middle" > <Button android:id="@+id/btn_first" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="#ff0000" android:text="button_1" /> <Button android:id="@+id/btn_second" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="#00ff00" android:text="button_2" /> <Button android:id="@+id/btn_third" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="#0000ff" android:text="button_3" /> </com.zhy.view.IcsLinearLayout></LinearLayout>
:
久違了~~我們的分隔~~可以看到在3.0以下機器完美實現~~~
but,別高興太早,我們這麼改,3.0以上機器是什麼樣子呢?
哈哈,是不是完美實現了間隔~~~
現在可以高興了~~~
大家現在肯定有困惑,我擦,你在構造裡面擷取divider,然後在onDraw裡面自己繪製了divider,大家都知道3.0以上是支援的呀,肯定也會繪製呀,你說沒衝突誰信呀~~~!!!
5、答疑
1、為什麼和3.0以上沒有發生一些該有的衝突?
嗯,是的,3.0以上是支援的,為什麼我們在onDraw裡面自己繪製,然後調用super.onDraw竟然沒有發生什麼衝突?
原因很簡單:我們看4.4LinearLayout的源碼:
@Override protected void onDraw(Canvas canvas) { if (mDivider == null) { return; }
其實,源碼中也是在onDraw裡面去繪製divider,但是如果mDivider為null,就會return。之所以沒有衝突,是因為我們前面的某個操作讓其mDivider成員變數為null了~~
現在去LinearLayout的構造方法:
public LinearLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); ... setDividerDrawable(a.getDrawable(R.styleable.LinearLayout_divider)); ... } public void setDividerDrawable(Drawable divider) { if (divider == mDivider) { return; } mDivider = divider; if (divider != null) { mDividerWidth = divider.getIntrinsicWidth(); mDividerHeight = divider.getIntrinsicHeight(); } else { mDividerWidth = 0; mDividerHeight = 0; } setWillNotDraw(divider == null); requestLayout(); }
可以看到它在其構造中調用setDividerDrawable為其mDivider賦值,關鍵來了~~~~我們的自訂的LinearLayout複寫了這個方法,也就是說,setDividerDrawable會調用子類的方法,這個父類的setDividerDrawable根本不會調用,從而導致mDivider為null了~~~
為null就對應了onDraw裡面的繪製~~ok~解答完畢。
2、這篇部落格怎麼想到的?你咋知道代碼這麼寫?
我相信這樣的問題,很多人感興趣,其實也算巧合,之前知道有divider這個屬性;然後前段時間寫Android 教你打造炫酷的ViewPagerIndicator 不僅僅是高仿MIUI 這篇部落格的時候,特意去看了ViewPagerIndicator那個開源項目源碼,發現了一個IcsLinearLayout這樣的一個類,類似我們上面實現的,當然了,我做了一定的修改;於是乎,仔細研究了這了類,覺得很有必要寫成部落格,達到文章開頭所敘述的的目的~其實大家有心的話,根據我們上述的代碼,去看看LinearLayout源碼中如何去繪製divider,你會發現代碼基本是一樣的(ps:你沒發現問題1中的LinearLayout源碼的setDividerDrawable和我們寫的一模一樣麼~);
好了,到此整篇文章就結束了,還是那句話:”這樣的情況在Android中肯定會很多,希望可以以此進行拋磚引玉,大家遇到類似的情況,提供一定的思路。這才是這篇部落格的真正目的!“ 不要偷懶花點時間去敲一敲,看一看,想一想,你會發現裡面還藏著很多東西,別怕浪費時間,我研究和寫這篇部落格的時間絕對超出你所學習這篇部落格的時間~~
最後,歐來來~~
源碼點擊下載
我建了一個QQ群,方便大家交流。群號:423372824
----------------------------------------------------------------------------------------------------------
博主部分視頻已經上線,如果你不喜歡枯燥的文本,請猛戳(初錄,期待您的支援):
1、Android中百度地圖的使用
2、Android 自訂控制項實戰 電商活動中的刮刮卡
3、Android自訂控制項實戰 打造Android流式布局和熱門標籤
4、Android智能機器人“小慕”的實現
5、高仿QQ5.0側滑
6、高仿5.2.1主介面及訊息提醒
Android 自訂控制項 優雅實現元素間的分割線 (支援3.0以下)