談?wù)凙ndroid中的Divider是個(gè)什么東東
在Android應(yīng)用開(kāi)發(fā)中會(huì)經(jīng)常碰到一個(gè)叫divider的東西,就是兩個(gè)View之間的分割線。最近工作中注意到這個(gè)divider并分析了一下,竟然發(fā)現(xiàn)內(nèi)有乾坤,驚為天人…
ListView的divider
1. 定制divider的邊距
ListView的divider默認(rèn)是左右兩頭到底的,如何簡(jiǎn)單的設(shè)置一個(gè)邊距呢?
利用inset或者layer-list都可以簡(jiǎn)單的實(shí)現(xiàn),代碼如下:
<!-- 方法一 --> <?xml version="1.0" encoding="utf-8"?> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:insetLeft="16dp" > <shape android:shape="rectangle" > <solid android:color="#f00" /> </shape> </inset> <!-- 方法二 --> <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:left="16dp"> <shape android:shape="rectangle"> <solid android:color="#f00" /> </shape> </item> </layer-list>
其中inset除了左邊距insetLeft, 還有insetTop、insetRight、insetBottom, 效果圖:
2. 最后一項(xiàng)的divider
很多同學(xué)可能發(fā)現(xiàn)了,ListView最后一項(xiàng)的divider有時(shí)候有,有時(shí)候又沒(méi)有。
我畫(huà)個(gè)圖大家就都能理解了:
上面是數(shù)據(jù)不足的顯示效果,如果數(shù)據(jù)滿屏的話,都是看不多最后的divider的。
真相是,當(dāng)ListView高度是不算最后一項(xiàng)divider的,所以只有在match_parent的情況下,ListView的高度是有余的,才能畫(huà)出最后的那個(gè)divider。
ps:網(wǎng)上很多資料,把最后一項(xiàng)的divider和footerDividersEnabled混在一起了,這個(gè)是不對(duì)的,兩個(gè)從邏輯上是獨(dú)立的,類(lèi)似的還有一個(gè)headerDividersEnabled,headerDividersEnabled和footerDividersEnabled不會(huì)影響到默認(rèn)情況下最后的divider的繪制,他們是給header和footer專(zhuān)用的,特此說(shuō)明。
RecyclerView的Divider
RecyclerView的Divider叫做ItemDecoration,RecyclerView.ItemDecoration本身是一個(gè)抽象類(lèi),官方?jīng)]有提供默認(rèn)實(shí)現(xiàn)。
官方的Support7Demos例子中有個(gè)DividerItemDecoration, 我們可以直接參考一下,位置在sdk的這里:
extras/android/support/samples/Support7Demos/src/…/…/decorator/DividerItemDecoration.java
但是這個(gè)DividerItemDecoration有三個(gè)問(wèn)題:
只支持系統(tǒng)默認(rèn)樣式,不支持自定義Drawable類(lèi)型的divider
里面的算法對(duì)于無(wú)高寬的Drawable(比如上面用到的InsetDrawable)是畫(huà)不出東西的水平列表的Divider繪制方法drawHorizontal()的right計(jì)算有誤,導(dǎo)致垂直Divider會(huì)繪制不出來(lái),應(yīng)該改為:final int right = left + mDivider.getIntrinsicWidth();;
針對(duì)這幾個(gè)問(wèn)題,我修復(fù)并增強(qiáng)了一下:
import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v4.view.ViewCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; /** * RecyclerView的ItemDecoration的默認(rèn)實(shí)現(xiàn) * 1. 默認(rèn)使用系統(tǒng)的分割線 * 2. 支持自定義Drawable類(lèi)型 * 3. 支持水平和垂直方向 * 4. 修復(fù)了官方垂直Divider顯示的bug * 擴(kuò)展自官方android sdk下的Support7Demos下的DividerItemDecoration */ public class DividerItemDecoration extends RecyclerView.ItemDecoration { private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; private Drawable mDivider; private int mWidth; private int mHeight; private int mOrientation; public DividerItemDecoration(Context context, int orientation) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); setOrientation(orientation); } /** * 新增:支持自定義dividerDrawable * * @param context * @param orientation * @param dividerDrawable */ public DividerItemDecoration(Context context, int orientation, Drawable dividerDrawable) { mDivider = dividerDrawable; setOrientation(orientation); } public void setOrientation(int orientation) { if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { throw new IllegalArgumentException("invalid orientation"); } mOrientation = orientation; } /** * 新增:支持手動(dòng)為無(wú)高寬的drawable制定寬度 * @param width */ public void setWidth(int width) { this.mWidth = width; } /** * 新增:支持手動(dòng)為無(wú)高寬的drawable制定高度 * @param height */ public void setHeight(int height) { this.mHeight = height; } @Override public void onDraw(Canvas c, RecyclerView parent) { if (mOrientation == VERTICAL_LIST) { drawVertical(c, parent); } else { drawHorizontal(c, parent); } } public void drawVertical(Canvas c, RecyclerView parent) { final int left = parent.getPaddingLeft(); final int right = parent.getWidth() - parent.getPaddingRight(); final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int top = child.getBottom() + params.bottomMargin + Math.round(ViewCompat.getTranslationY(child)); final int bottom = top + getDividerHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } public void drawHorizontal(Canvas c, RecyclerView parent) { final int top = parent.getPaddingTop(); final int bottom = parent.getHeight() - parent.getPaddingBottom(); final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int left = child.getRight() + params.rightMargin + Math.round(ViewCompat.getTranslationX(child)); final int right = left + getDividerWidth(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } @Override public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { if (mOrientation == VERTICAL_LIST) { outRect.set(0, 0, 0, getDividerHeight()); } else { outRect.set(0, 0, getDividerWidth(), 0); } } private int getDividerWidth() { return mWidth > 0 ? mWidth : mDivider.getIntrinsicWidth(); } private int getDividerHeight() { return mHeight > 0 ? mHeight : mDivider.getIntrinsicHeight(); } }
使用如下:
// 默認(rèn)系統(tǒng)的divider dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST); // 自定義圖片drawable分的divider dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST, getResources().getDrawable(R.drawable.ic_launcher)); // 自定義無(wú)高寬的drawable的divider - 垂直列表 dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST, new ColorDrawable(Color.parseColor("#ff00ff"))); dividerItemDecoration.setHeight(1); // 自定義無(wú)高寬的drawable的divider - 水平列表 dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.HORIZONTAL_LIST, new ColorDrawable(Color.parseColor("#ff00ff"))); dividerItemDecoration.setWidth(1); // 自定義帶邊距且無(wú)高寬的drawable的divider(以上面InsetDrawable為例子) // 這個(gè)地方也可以在drawable的xml文件設(shè)置size指定寬高,效果一樣 dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.HORIZONTAL_LIST, getResources().getDrawable(R.drawable.list_divider)); dividerItemDecoration.setWidth(DisplayLess.$dp2px(16) + 1);
手動(dòng)的Divider
有的時(shí)候沒(méi)有系統(tǒng)控件的原生支持,只能手動(dòng)在兩個(gè)view加一個(gè)divider,比如,設(shè)置界面每項(xiàng)之間的divider,水平平均分隔的幾個(gè)view之間加一個(gè)豎的divider等等。
無(wú)論橫的豎的,都非常簡(jiǎn)單,定一個(gè)View,設(shè)置一個(gè)background就可以了,正常情況下沒(méi)什么好說(shuō)的。
下面我們來(lái)考慮一種常見(jiàn)設(shè)置界面,這種設(shè)置界面的分割線是有左邊距的,比如微信的設(shè)置界面,我相信絕大部分人的布局代碼都是這樣實(shí)現(xiàn)的:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!--這個(gè)group_container的background一定要設(shè)置, 而且要和list_item_bg的list_item_normal一致, 否則效果會(huì)不正確。 --> <LinearLayout android:id="@+id/group_container" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_marginTop="48dp" android:background="#fff" android:orientation="vertical"> <RelativeLayout android:id="@+id/account_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/list_item_bg" android:clickable="true"> <TextView android:id="@+id/account_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_margin="16dp" android:text="First Item" android:textColor="#f00" android:textSize="16sp" /> </RelativeLayout> <View android:layout_width="match_parent" android:layout_height="1px" android:layout_marginLeft="16dp" android:background="#f00" /> <RelativeLayout android:id="@+id/phone_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/list_item_bg" android:clickable="true"> <TextView android:id="@+id/phone_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_margin="16dp" android:text="Second Item" android:textColor="#f00" android:textSize="16sp" /> </RelativeLayout> </LinearLayout> </RelativeLayout>
效果圖如下,順便我們也看看它的Overdraw狀態(tài):
通過(guò)分析Overdraw的層次,我們發(fā)現(xiàn)為了一個(gè)小小的邊距,設(shè)置了整個(gè)groud_container的背景,從而導(dǎo)致了一次Overdraw。
能不能優(yōu)化掉這個(gè)Overdraw?答案是肯定的。
背景肯定要去掉,但是這個(gè)左邊距的View就不能這么簡(jiǎn)單的寫(xiě)了,需要自定義一個(gè)View,它要支持能把左邊距的空出的16dp的線用list_item_normal的顏色值繪制一遍,這樣才能看的出左邊距。
這個(gè)View具體代碼如下:
import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; import com.jayfeng.lesscode.core.R; public class SpaceDividerView extends View { private int mSpaceLeft = 0; private int mSpaceTop = 0; private int mSpaceRight = 0; private int mSpaceBottom = 0; private int mSpaceColor = Color.TRANSPARENT; private Paint mPaint = new Paint(); public SpaceDividerView(Context context) { this(context, null); } public SpaceDividerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SpaceDividerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SpaceDividerView, defStyleAttr, 0); mSpaceLeft = a.getDimensionPixelSize(R.styleable.SpaceDividerView_spaceLeft, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0, getResources().getDisplayMetrics())); mSpaceTop = a.getDimensionPixelSize(R.styleable.SpaceDividerView_spaceTop, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0, getResources().getDisplayMetrics())); mSpaceRight = a.getDimensionPixelSize(R.styleable.SpaceDividerView_spaceRight, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0, getResources().getDisplayMetrics())); mSpaceBottom = a.getDimensionPixelSize(R.styleable.SpaceDividerView_spaceBottom, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0, getResources().getDisplayMetrics())); mSpaceColor = a.getColor(R.styleable.SpaceDividerView_spaceColor, Color.TRANSPARENT); a.recycle(); mPaint.setColor(mSpaceColor); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mSpaceLeft > 0) { canvas.drawRect(0, 0, mSpaceLeft, getMeasuredHeight(), mPaint); } if (mSpaceTop > 0) { canvas.drawRect(0, 0, getMeasuredWidth(), mSpaceTop, mPaint); } if (mSpaceRight > 0) { canvas.drawRect(getMeasuredWidth() - mSpaceRight, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint); } if (mSpaceBottom > 0) { canvas.drawRect(0, getMeasuredHeight() - mSpaceBottom, getMeasuredWidth(), getMeasuredHeight(), mPaint); } } }
用這個(gè)SpaceDividerView我們重寫(xiě)一下上面的布局代碼:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> <LinearLayout android:id="@+id/group_container" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_marginTop="48dp" android:orientation="vertical"> <RelativeLayout android:id="@+id/account_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/list_item_bg" android:clickable="true"> <TextView android:id="@+id/account_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_margin="16dp" android:text="First Item" android:textColor="#f00" android:textSize="16sp" /> </RelativeLayout> <com.jayfeng.lesscode.core.other.SpaceDividerView android:layout_width="match_parent" android:layout_height="1px" android:background="#f00" app:spaceLeft="16dp" app:spaceColor="@color/list_item_normal"/> <RelativeLayout android:id="@+id/phone_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/list_item_bg" android:clickable="true"> <TextView android:id="@+id/phone_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_margin="16dp" android:text="Second Item" android:textColor="#f00" android:textSize="16sp" /> </RelativeLayout> </LinearLayout> </RelativeLayout>
效果圖和Overdraw狀態(tài)如下:
界面中g(shù)roup_container那塊由之前的綠色變成了藍(lán)色,說(shuō)明減少了一次Overdraw。
上述情況下,SpaceDividerView解耦了背景色,優(yōu)化了Overdraw,而且這個(gè)SpaceDividerView也是支持4個(gè)方向的,使用起來(lái)特別方便。
陰影divider
陰影分割線的特點(diǎn)是重疊在下面的view之上的,它的目的是一種分割線的立體效果。
使用RelativeLayout并控制上邊距離可以實(shí)現(xiàn):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- layout_marginTop的值應(yīng)該就是不包括陰影高度的header高度--> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentTop="true" android:layout_marginTop="@dimen/header_height" android:orientation="vertical"> </LinearLayout> <!-- 這個(gè)要放在最后,才能顯示在最上層,這個(gè)header里面包括一個(gè)陰影View--> <include android:id="@+id/header" layout="@layout/include_header" /> </RelativeLayout>
雖然再簡(jiǎn)單不過(guò)了,還是稍微分析一下,header包括內(nèi)容48dp和陰影8dp,那么marginTop就是48dp了。
下面給大家介紹Android給ListView設(shè)置分割線Divider樣式
給ListView設(shè)置分割線,只需設(shè)置如下兩個(gè)屬性:
android:divider="#000" //設(shè)置分割線顯示顏色
android:dividerHeight="1px" //此處非0,否則無(wú)效
<ListView android:id="@+id/listView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:divider="#FFF" android:dividerHeight="1px" android:layout_margin="10dip"/>
以上內(nèi)容給大家簡(jiǎn)單介紹了Android中的Divider,希望對(duì)大家有所幫助!
相關(guān)文章
Flutter應(yīng)用集成極光推送的實(shí)現(xiàn)示例
這篇文章主要介紹了Flutter應(yīng)用集成極光推送的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02Flutter使用AnimatedOpacity實(shí)現(xiàn)圖片漸現(xiàn)動(dòng)畫(huà)
其實(shí)在Flutter中提供了一些封裝好的動(dòng)畫(huà)組件,以便我們快速應(yīng)用。本文將利用其中的AnimatedOpacity組件實(shí)現(xiàn)圖片漸現(xiàn)動(dòng)畫(huà)效果,需要的可以參考一下2022-03-03Android 7.0以上版本實(shí)現(xiàn)應(yīng)用內(nèi)語(yǔ)言切換的方法
本篇文章主要介紹了Android 7.0以上版本實(shí)現(xiàn)應(yīng)用內(nèi)語(yǔ)言切換的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02簡(jiǎn)單實(shí)現(xiàn)Android計(jì)算器功能
這篇文章主要為大家詳細(xì)介紹了自己動(dòng)手實(shí)現(xiàn)的Android計(jì)算器功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01Android中使用ScrollView指定view的頂部懸停效果
在項(xiàng)目開(kāi)發(fā)中遇到這樣的需求,需要實(shí)現(xiàn)scrollview頂部的懸停效果,實(shí)現(xiàn)原理非常簡(jiǎn)單,下面小編通過(guò)本文給大家分享實(shí)例代碼,需要的朋友參考下2017-04-04Android AsyncTask用法巧用實(shí)例代碼
這篇文章主要介紹了Android AsyncTask用法巧用實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-01-01Android 簡(jiǎn)單的照相機(jī)程序的實(shí)例代碼
終于經(jīng)過(guò)多次找錯(cuò),修改把一個(gè)簡(jiǎn)單的照相機(jī)程序完成了,照相類(lèi)代碼如下:2013-05-05