*/
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的源码:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
3Nkbi5uZXQvbG1qNjIzNTY1Nzkx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
哈哈,是不是完美实现了间隔~~~
现在可以高兴了~~~
大家现在肯定有困惑,我擦,你在构造里面获取divider,然后在onDraw里面自己绘制了divider,大家都知道3.0以上是支持的呀,肯定也会绘制呀,你说没冲突谁信呀~~~!!!
5、答疑
====
1、为什么和3.0以上没有发生一些该有的冲突?
嗯,是的,3.0以上是支持的,为什么我们在onDraw里面自己绘制,然后调用super.onDraw竟然没有发生什么冲突?
原因很简单:我们看4.4LinearLayout的源码:
[外链图片转存中…(img-q0l1Xa4i-1714193050074)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!