Android 自定义控件 优雅实现元素间的分割线 (支持3

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

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

{

//将分隔元素的宽高转化为对应的margin

setChildrenDivider();

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

/**

  • 将分隔元素的宽高转化为对应的margin

*/

protected void setChildrenDivider()

{

final int count = getChildCount();

for (int i = 0; i < count; i++)

{

//遍历每个子View

View 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把需要绘制的地方空出来了,那么下面就是绘制了~~~

@Override

protected void onDraw(Canvas canvas)

{

if (mDivider != null)

{

if (getOrientation() == VERTICAL)

{

//绘制垂直方向的divider

drawDividersVertical(canvas);

} else

{

//绘制水平方向的divider

drawDividersHorizontal(canvas);

}

}

super.onDraw(canvas);

}

/**

  • 绘制水平方向的divider

  • @param canvas

*/

private void drawDividersHorizontal(Canvas canvas)

{

final int count = getChildCount();

//遍历所有的子View

for (int i = 0; i < count; i++)

{

final View child = getChildAt(i);

if (child != null && child.getVisibility() != GONE)

{

//如果需要绘制divider

if (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

*/;

//绘制divider

drawVerticalDivider(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>

效果图:

久违了我们的分隔可以看到在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();

}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

th = 0;

mDividerHeight = 0;

}

setWillNotDraw(divider == null);

requestLayout();

}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-nqb51obO-1714956682601)]

[外链图片转存中…(img-TigsEQwu-1714956682602)]

[外链图片转存中…(img-UTiyz0ok-1714956682602)]

[外链图片转存中…(img-kDvps2GF-1714956682602)]

[外链图片转存中…(img-jX2Tqtse-1714956682602)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值