谈谈Android中的Divider,AndroidDivider
在Android应用开发中会经常碰到一个叫divider的东西,就是两个View之间的分割线。最近工作中注意到这个divider并分析了一下,竟然发现内有乾坤,惊为天人…
ListView的divider
1. 定制divider的边距
ListView的divider默认是左右两头到底的,如何简单的设置一个边距呢?
利用inset或者layer-list都可以简单的实现,代码如下:
<!-- 方法一 --> <?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. 最后一项的divider
很多同学可能发现了,ListView最后一项的divider有时候有,有时候又没有。
我画个图大家就都能理解了:
上面是数据不足的显示效果,如果数据满屏的话,都是看不多最后的divider的。
真相是,当ListView高度是不算最后一项divider的,所以只有在match_parent的情况下,ListView的高度是有余的,才能画出最后的那个divider。
ps:网上很多资料,把最后一项的divider和footerDividersEnabled混在一起了,这个是不对的,两个从逻辑上是独立的,类似的还有一个headerDividersEnabled,headerDividersEnabled和footerDividersEnabled不会影响到默认情况下最后的divider的绘制,他们是给header和footer专用的,特此说明。
RecyclerView的Divider
RecyclerView的Divider叫做ItemDecoration,RecyclerView.ItemDecoration本身是一个抽象类,官方没有提供默认实现。
官方的Support7Demos例子中有个DividerItemDecoration, 我们可以直接参考一下,位置在sdk的这里:
extras/android/support/samples/Support7Demos/src/…/…/decorator/DividerItemDecoration.java
但是这个DividerItemDecoration有三个问题:
针对这几个问题,我修复并增强了一下:
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的默认实现 * 1. 默认使用系统的分割线 * 2. 支持自定义Drawable类型 * 3. 支持水平和垂直方向 * 4. 修复了官方垂直Divider显示的bug * 扩展自官方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; } /** * 新增:支持手动为无高宽的drawable制定宽度 * @param width */ public void setWidth(int width) { this.mWidth = width; } /** * 新增:支持手动为无高宽的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(); } }
使用如下:
// 默认系统的divider dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST); // 自定义图片drawable分的divider dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST, getResources().getDrawable(R.drawable.ic_launcher)); // 自定义无高宽的drawable的divider - 垂直列表 dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST, new ColorDrawable(Color.parseColor("#ff00ff"))); dividerItemDecoration.setHeight(1); // 自定义无高宽的drawable的divider - 水平列表 dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.HORIZONTAL_LIST, new ColorDrawable(Color.parseColor("#ff00ff"))); dividerItemDecoration.setWidth(1); // 自定义带边距且无高宽的drawable的divider(以上面InsetDrawable为例子) // 这个地方也可以在drawable的xml文件设置size指定宽高,效果一样 dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.HORIZONTAL_LIST, getResources().getDrawable(R.drawable.list_divider));