给LinearLayout加上花式分割线
来源:互联网 发布:淘宝上怎样删除评价 编辑:程序博客网 时间:2024/04/29 21:57
给LinearLayout加上花式分割线
前言
写安卓的同学们应该都知道LinearLayout有一个分割线的功能,可以在子View间添加分割线或是给整个LinearLayout上下加上分割线,同时对于分割线的样式,可以通过自定义drawable的方式来实现,灵活度很高,但这种方式也让开发人员的编码变得非常痛苦。写安卓也有一段时间了,你要问我安卓开发的过程中,最不愿意面对的事情是什么,我想说就是打开我的drawable文件夹或是打开layout文件夹了… 对于一个简单的横线,我还是不太愿意定义一个drawable文件来解决这个问题,毕竟图多了不好找,我又不是HashMap! 所以今天来讨论下怎么去掉这个drawable文件的问题。
惯例上图
最终通过继承LinearLayout的方式干掉了drawable文件,直接通过属性指定即可,同时还顺带做了分割线颜色尺寸位置的控制,每条分割线都可以单独控制,是不是很带劲
使用方式
自定义ViewGroup(DividerLayout)是LinearLayout的子类,它可以为每一个直接子View提供在其上下两侧绘制分割线的功能,你可以直接在每个子View中声明app:divider_top="true"
或者app:divider_bottom="true"
来绘制某个子View的上下分割线
同时你可以通过: app:divider_size="2px"
app:divider_color="@color/colorPrimary"
app:divider_padding_left="48dp"
app:divider_padding_right="48dp"
这四个选项来控制每一条分割线的颜色大小及padding。值得一说的是,如果这些属性被声明在DividerLayout中,这些属性将被作为默认属性应用到每一个子view中 但子view可以再次声明相关属性达到覆盖的效果
除了可以在子View上下添加分割线,整个DividerLayout也是支持在自己的上面或下面绘制分割线的 使用方式同子View一样依然是app:divider_top="true"
或者app:divider_bottom="true"
,只不过把他写到DividerLayout中就可以了。
这样一来,整个DividerLayout就实现了全部的LinearLayout提供的分割线功能,同时,提供了更加细化和简单的控制方式,所以,请尽情享用它吧!
贴一下上面那张图的xml代码
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:background="#f0f0f0" android:layout_height="match_parent"> <com.congxiaoyao.xber_admin.widget.XberDividerLayout app:divider_color="@color/colorPrimary" app:divider_size="2px" app:divider_bottom="true" android:layout_marginTop="8dp" android:orientation="vertical" android:background="#ffffff" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="40dp" android:textSize="17sp" android:gravity="center" app:divider_top="true" android:text="第一项" /> <TextView app:divider_top="true" app:divider_padding_left="24dp" app:divider_padding_right="24dp" app:divider_color="#b44bb8" android:layout_width="match_parent" android:layout_height="40dp" android:textSize="17sp" android:gravity="center" android:text="第二项" /> <TextView app:divider_top="true" app:divider_padding_left="48dp" app:divider_padding_right="48dp" app:divider_color="#008d58" android:layout_width="match_parent" android:layout_height="40dp" android:textSize="17sp" android:gravity="center" android:text="第三项" /> <TextView app:divider_top="true" app:divider_padding_left="72dp" app:divider_padding_right="72dp" app:divider_color="#efb11f" android:layout_width="match_parent" android:layout_height="40dp" android:textSize="17sp" android:gravity="center" android:text="第四项" /> </com.congxiaoyao.xber_admin.widget.XberDividerLayout></LinearLayout>
实现方式
前文也提到了,DividerLayout是通过继承LinearLayout的方式来实现的。毕竟为了加个分割线,重写一遍LinearLayout就太伤了,所以这里稍微hack下,只需要单纯的添加一些绘图代码及布局参数控制代码就好了。其实我们面对的技术问题只有两点:
- 如何获取在子View中定义的属性
- 如何在分割线存在的情况下 为子View排布新的位置(要为分割线空出位置)
关于第一点 大家可以参考这篇文章,如果大家了解,可以跳过了,这里简单介绍一下。
其实秘密就在于每一个子View的LayoutParam参数。作为一个ViewGroup,系统在为其每一个子View生成布局参数的时候,给予了ViewGroup一次获取子View属性的机会,也就是说,在生成子View的LayoutParams的时候,ViewGroup还有一次访问AttributeSet的机会,而且这个AttributeSet是子View的AttributeSet!是不是突然明白了?是吧,我也是。所以,我们可以定义一堆属性并且在生成LayoutParam的时候解析他 看代码
public class LayoutParams extends LinearLayout.LayoutParams { private int dividerColor; private int dividerPaddingLeft; private int dividerPaddingRight; private int dividerSize; private boolean dividerTop; private boolean dividerBottom; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.XberDividerLayout); dividerColor = a.getColor(R.styleable.XberDividerLayout_divider_color, XberDividerLayout.this.dividerColor); dividerPaddingLeft = a.getDimensionPixelSize(R.styleable .XberDividerLayout_divider_padding_left, XberDividerLayout.this.dividerPaddingLeft); dividerPaddingRight = a.getDimensionPixelSize(R.styleable .XberDividerLayout_divider_padding_right, XberDividerLayout.this.dividerPaddingRight); ...... ...... a.recycle(); } //下面还有几个构造函数 但是可以不关心她 ... }
这里定义了一个内部类LayoutParams,可能你会经常看到FrameLayout.LayoutParams
、RelativeLayout.LayoutParams
等等,其实就是这个意思。但单纯的定义是没办法让系统把这个布局参数应用给子View的,所以还需要将我们自定义的这个LayoutParams告诉系统,也就是覆写如下方法把我们自己的LayoutParam返回出去。如果这里看的不是很明白可以去看下刚刚提到的那篇文章
@Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { return new LayoutParams(lp); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; }
这样我们在画分割线的时候就可以遍历每一个子View,通过LayoutParam参数拿到相关的属性了。
好第一个问题解决了,那位置怎么控制呢,难道要重写onLayout或onMeasure方法吗?如果这么办的话,那就基本上是把LinearLayout重写一遍了,所以在这个地方,我选择抖一波机灵,直接把分割线的尺寸当做margin加进布局参数,让系统自动的帮我们测量及布局,所以如果是上分割线就加topMargin,下分割线就加bottomMargin,简单且无害,绿色环保。
那么构造函数再加两句
public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.XberDividerLayout); ...... ...... dividerSize = a.getDimensionPixelSize(R.styleable .XberDividerLayout_divider_size, XberDividerLayout.this.dividerSize); dividerTop = a.getBoolean(R.styleable.XberDividerLayout_divider_top, false); if (dividerTop) { topMargin += dividerSize; } dividerBottom = a.getBoolean(R.styleable.XberDividerLayout_divider_bottom, false); if (dividerBottom) { bottomMargin += dividerSize; } a.recycle(); }
所以到这就是所有核心的东西了,有没有比你想象的要简单一点。。。不管怎么说,管用就行。只是还有最后一点,就是要为整个DividerLayout添加上下的分割线,所以这里还是存在一个位置排布的问题,得把上下分割线的位置空出来。之前给子view添加margin的方式到也可以解决这个问题,但是还是有点复杂,仔细想想,其实还有一个特性我们没有用到,那就是LinearLayout本身对分割线的支持啊! 它本身就是支持添加上线分割线的,所以我们直接通过代码调用相关方法就好了 看代码!
public XberDividerLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.XberDividerLayout); dividerSize = a.getDimensionPixelSize(R.styleable.XberDividerLayout_divider_size, 0); dividerColor = a.getColor(R.styleable.XberDividerLayout_divider_color, Color.BLACK); dividerPaddingLeft = a.getDimensionPixelSize(R.styleable .XberDividerLayout_divider_padding_left, 0); dividerPaddingRight = a.getDimensionPixelSize(R.styleable .XberDividerLayout_divider_padding_right, 0); boolean top = a.getBoolean(R.styleable.XberDividerLayout_divider_top, false); boolean bottom = a.getBoolean(R.styleable.XberDividerLayout_divider_bottom, false); a.recycle(); //从这开始看! int showDivider = SHOW_DIVIDER_NONE; if(top) showDivider = showDivider | SHOW_DIVIDER_BEGINNING; if(bottom) showDivider = showDivider | SHOW_DIVIDER_END; if (showDivider != SHOW_DIVIDER_NONE) { //注意这里,通过代码来设置开启上下分割线 setShowDividers(showDivider); //通过代码生成一个ColorDrawable并设置进去 setDividerDrawable(new ColorDrawable(dividerColor){ @Override public int getIntrinsicHeight() { return dividerSize; } @Override public void setBounds(Rect bounds) { super.setBounds(bounds); bounds.left += dividerPaddingLeft; bounds.right -= dividerPaddingRight; } }); } }
你可能注意到了,我们并没有直接传ColorDrawable进去,而是覆写了他两个方法。关于getIntrinsicHeight方法,在系统进行measure和layout的时候会根据此方法的返回值来空出相应的位置画分割线
在LinearLayout的 measureVertical
方法中 有如下语句
if (hasDividerBeforeChildAt(i)) { //mTotalLength表示整个linearLayout的高度 在遍历测量每一个子view的过程中,将分割线的高度也算进了总高度里 mTotalLength += mDividerHeight;}
这里的 mDividerHeight
就是在这个方法里初始化的
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(); }
所以我们直接覆盖getIntrinsicHeight方法,返回布局中设置的高度即可达到让父类帮我们测量尺寸的目的。同理在onLayout方法中,也是根据mDividerHeight
这个变量来控制view的偏移的,代码不再贴了。
还有一点就是上面又覆写了setBounds
方法,是因为我们是支持左右padding的,而系统只支持一个padding,但这并不代表我们没法hack他,仔细观察LinearLayout的onDraw方法,就会看到如下代码
void drawHorizontalDivider(Canvas canvas, int top) { mDivider.setBounds(getPaddingLeft() + mDividerPadding, top, getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight); mDivider.draw(canvas); }
在每次调用drawable的draw方法前,系统都会为这个drawable设置一次边界,所以我们在这里动一下手脚把我们自己的padding加进去就可以了,所以为了padding能够正常工作,请不要使用任何LinearLayout本身的divider设置方法,否则上下分割线就不起作用了。
收工啦
好了 到这就全部结束了,以后有各种分割线的需求,都不用再写个View放那了。暂时不支持横向布局,我想百分之九十九都不会在横向布局里加分割线吧。如果需要,直走左转LinearLayout在门口等你,文末我会把代码全部附上,小玩具我就不做gradle依赖了,大家有需要自己把代码粘走吧~
以上!
package com.congxiaoyao.xber_admin.widget;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Rect;import android.graphics.drawable.ColorDrawable;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;import android.widget.LinearLayout;import com.congxiaoyao.xber_admin.R;/** * Created by congxiaoyao on 2017/4/3. */public class XberDividerLayout extends LinearLayout { private int dividerSize; private int dividerColor; private int dividerPaddingLeft; private int dividerPaddingRight; private ColorDrawable dividerDrawable = new ColorDrawable(); public XberDividerLayout(Context context) { this(context, null); } public XberDividerLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public XberDividerLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.XberDividerLayout); dividerSize = a.getDimensionPixelSize(R.styleable.XberDividerLayout_divider_size, 0); dividerColor = a.getColor(R.styleable.XberDividerLayout_divider_color, Color.BLACK); dividerPaddingLeft = a.getDimensionPixelSize(R.styleable .XberDividerLayout_divider_padding_left, 0); dividerPaddingRight = a.getDimensionPixelSize(R.styleable .XberDividerLayout_divider_padding_right, 0); boolean top = a.getBoolean(R.styleable.XberDividerLayout_divider_top, false); boolean bottom = a.getBoolean(R.styleable.XberDividerLayout_divider_bottom, false); a.recycle(); int showDivider = SHOW_DIVIDER_NONE; if(top) showDivider = showDivider | SHOW_DIVIDER_BEGINNING; if(bottom) showDivider = showDivider | SHOW_DIVIDER_END; if (showDivider != SHOW_DIVIDER_NONE) { setShowDividers(showDivider); setDividerDrawable(new ColorDrawable(dividerColor){ @Override public int getIntrinsicHeight() { return dividerSize; } @Override public void setBounds(Rect bounds) { super.setBounds(bounds); bounds.left += dividerPaddingLeft; bounds.right -= dividerPaddingRight; } }); } setWillNotDraw(false); } @Override protected void onDraw(Canvas canvas) { if (getOrientation() == HORIZONTAL) { throw new RuntimeException("暂不支持横向布局"); } super.onDraw(canvas); drawDividersVertical(canvas); } 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) { ViewGroup.LayoutParams layoutParams = child.getLayoutParams(); if (hasDividerBeforeChild(layoutParams)) { final LayoutParams lp = (LayoutParams) layoutParams; final int top = child.getTop() - lp.dividerSize; drawHorizontalDivider(canvas, top, lp); } if (hasDividerAfterChild(layoutParams)) { final LayoutParams lp = (LayoutParams) layoutParams; final int top = child.getBottom(); drawHorizontalDivider(canvas, top, lp); } } } } private boolean hasDividerBeforeChild(ViewGroup.LayoutParams lp) { if (!(lp instanceof LayoutParams)) { return false; } LayoutParams layoutParams = (LayoutParams) lp; return layoutParams.dividerSize > 0 && layoutParams.dividerTop; } private boolean hasDividerAfterChild(ViewGroup.LayoutParams lp) { if (!(lp instanceof LayoutParams)) { return false; } LayoutParams layoutParams = (LayoutParams) lp; return layoutParams.dividerSize > 0 && layoutParams.dividerBottom; } private void drawHorizontalDivider(Canvas canvas, int top, LayoutParams lp) { dividerDrawable.setColor(lp.dividerColor); dividerDrawable.setBounds(getPaddingLeft() + lp.dividerPaddingLeft, top, getWidth() - getPaddingRight() - lp.dividerPaddingRight, top + lp.dividerSize); dividerDrawable.draw(canvas); } @Override protected LayoutParams generateDefaultLayoutParams() { if (getOrientation() == HORIZONTAL) { throw new RuntimeException("暂不支持横向布局"); } return new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { return new LayoutParams(lp); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } public class LayoutParams extends LinearLayout.LayoutParams { private int dividerColor; private int dividerPaddingLeft; private int dividerPaddingRight; private int dividerSize; private boolean dividerTop; private boolean dividerBottom; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.XberDividerLayout); dividerColor = a.getColor(R.styleable.XberDividerLayout_divider_color, XberDividerLayout.this.dividerColor); dividerPaddingLeft = a.getDimensionPixelSize(R.styleable .XberDividerLayout_divider_padding_left, XberDividerLayout.this.dividerPaddingLeft); dividerPaddingRight = a.getDimensionPixelSize(R.styleable .XberDividerLayout_divider_padding_right, XberDividerLayout.this.dividerPaddingRight); dividerSize = a.getDimensionPixelSize(R.styleable .XberDividerLayout_divider_size, XberDividerLayout.this.dividerSize); dividerTop = a.getBoolean(R.styleable.XberDividerLayout_divider_top, false); if (dividerTop) { topMargin += dividerSize; } dividerBottom = a.getBoolean(R.styleable.XberDividerLayout_divider_bottom, false); if (dividerBottom) { bottomMargin += dividerSize; } a.recycle(); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(int width, int height, float weight) { super(width, height, weight); } public LayoutParams(ViewGroup.LayoutParams p) { super(p); } public LayoutParams(LinearLayout.LayoutParams source) { super(source); } }}
res/values/attrs.xml
<declare-styleable name="XberDividerLayout" > <attr name="divider_size" format="dimension"/> <attr name="divider_color" format="color" /> <attr name="divider_padding_left" format="dimension"/> <attr name="divider_padding_right" format="dimension"/> <attr name="divider_top" format="boolean" /> <attr name="divider_bottom" format="boolean" /> <attr name="enable_header" format="boolean"/> <attr name="enable_footer" format="boolean" /> </declare-styleable>
- 给LinearLayout加上花式分割线
- 给GridView的单元格加上分割线
- LinearLayout显示分割线
- linearLayout设置分割线
- LinearLayout分(hun)割线
- LinearLayout 分割线
- Android之给gridview的单元格加上分割线
- 【Android进阶】如何给gridview的单元格加上分割线
- Android Linearlayout 添加分割线
- LinearLayout增加divider分割线
- LinearLayout显示分割线(Divider)
- LinearLayout增加divider分割线
- android LinearLayout分割线设置
- LinearLayout增加divider分割线
- LinearLayout均分的分割线配置方式
- android LinearLayout容器添加分割线
- LinearLayout增加divider分割线转
- Android 在 LinearLayout 添加分割线 divider
- Webpack2 起步
- iOS app性能优化的那些事
- 总结:eclipse编写struts.xml没有提示的问题
- 连续特征的离散化:在什么情况下将连续的特征离散化之后可以获得更好的效果?
- caffe上手:mnist学习
- 给LinearLayout加上花式分割线
- 关键字_标识符_常量_变量_数据类型
- 如何利用训练好的神经网络进行预测
- PAT-A-1077. Kuchiguse (20)
- myeclipse中创建mave管理的web项目
- 数字和字符串的转换
- C++ STL queue
- 康拓展开和康拓逆展开
- Ionic2之tabs相关内容和其他一些坑