嘉兴市城乡规划建设管理网站,一个虚拟主机能安装2个网站吗,广州市网站建设科技公司,集团做网站需要多大的带宽谷歌Material Design推出了许多非常好用的控件#xff0c;所以我决定写一个专题来讲述MaterialDesign#xff0c;今天带来Material Design系列的第一弹 LinearLayoutCompat。
以前要在LinearLayout布局之间的子View之间添加分割线#xff0c;还需要自己去自定义控件进行添加…
谷歌Material Design推出了许多非常好用的控件所以我决定写一个专题来讲述MaterialDesign今天带来Material Design系列的第一弹 LinearLayoutCompat。
以前要在LinearLayout布局之间的子View之间添加分割线还需要自己去自定义控件进行添加或者就是在子View之间写很多个TextView但是谷歌已经给我们提供了这样一个组件可以很轻松的解决分割线的问题妈妈再也不用担心分割线问题啦这个组件就是Material Design中的 LinearLayoutCompat。本篇博客将会从以下两个方面来对LinearLayoutCompat进行介绍
\1. LinearLayoutCompat的使用
\2. LinearLayoutCompat的源码分析
LinearLayoutCompat的使用
LinearLayoutCompat位于support-v7包中LinearLayoutCompat其实就是LinerLayout组件只是为了兼容低版本所以你必须的引用 V7包下面的LinearLayoutCompat。 LinearLayoutCompat除了拥有LinerLayout原本的属性之外主要有如下几种属性来实现间隔线效果。
当然使用LinearLayoutCompat需要自定义命名空间xmlns:app”http://schemas.android.com/apk/res-auto”
app:divider”drawable/line”给分隔线设置自定义的drawable这里你需要在drawable在定义shape资源否则将没有效果。
app:dividerPadding 给分隔线设置距离左右边距的距离。
app:showDividersbeginning|middle|end属性。 beginningmiddleend属性值分别指明将在何处添加分割线。 beginning表示从该LinearLayoutCompat布局的最顶一个子view的顶部开始。 middle表示在此LinearLayoutCompat布局内的子view之间添加。 end表示在此LinearLayoutCompat最后一个子view的底部添加分割线。
none表示不设置间隔线。
使用LinearLayoutCompat可以很方便的就做出微信的发现界面 布局的代码如下
RelativeLayout xmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:toolshttp://schemas.android.com/toolsxmlns:apphttp://schemas.android.com/apk/res-autoandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:backgroundcolor/grayandroid:gravitytoptools:contextcom.example.linearlayoutcompatdemo.MainActivity !-- android.support.v7.widget.LinearLayoutCompat --android.support.v7.widget.LinearLayoutCompatandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:layout_marginTop30dpandroid:backgroundcolor/whiteandroid:gravitycenterandroid:orientationverticalapp:dividerdrawable/lineapp:dividerPadding1dpapp:showDividersmiddle|beginning|end LinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationhorizontalandroid:paddingBottom10dipandroid:paddingLeft5dipandroid:paddingTop10dip ImageViewandroid:layout_width35dpandroid:layout_height35dpandroid:scaleTypefitCenterandroid:srcdrawable/find_more_friend_photograph_icon /TextViewandroid:layout_widthwrap_contentandroid:layout_heightfill_parentandroid:layout_marginLeft10dpandroid:gravitycenterandroid:text朋友圈android:textColorcolor/blackandroid:textSize15dip //LinearLayoutLinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationhorizontalandroid:paddingBottom10dipandroid:paddingLeft5dipandroid:paddingTop10dip ImageViewandroid:layout_width35dpandroid:layout_height35dpandroid:scaleTypefitCenterandroid:srcdrawable/find_more_friend_scan /TextViewandroid:layout_widthwrap_contentandroid:layout_heightfill_parentandroid:layout_marginLeft10dpandroid:gravitycenterandroid:text扫一扫android:textColorcolor/blackandroid:textSize15dip //LinearLayoutLinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationhorizontalandroid:paddingBottom10dipandroid:paddingLeft5dipandroid:paddingTop10dip ImageViewandroid:layout_width35dpandroid:layout_height35dpandroid:scaleTypefitCenterandroid:srcdrawable/find_more_friend_shake /TextViewandroid:layout_widthwrap_contentandroid:layout_heightfill_parentandroid:layout_marginLeft10dpandroid:gravitycenterandroid:text摇一摇android:textColorcolor/blackandroid:textSize15dip //LinearLayoutLinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationhorizontalandroid:paddingBottom10dipandroid:paddingLeft5dipandroid:paddingTop10dip ImageViewandroid:layout_width35dpandroid:layout_height35dpandroid:scaleTypefitCenterandroid:srcdrawable/find_more_friend_near_icon /TextViewandroid:layout_widthwrap_contentandroid:layout_heightfill_parentandroid:layout_marginLeft10dpandroid:gravitycenterandroid:text附近的人android:textColorcolor/blackandroid:textSize15dip //LinearLayoutLinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationhorizontalandroid:paddingBottom10dipandroid:paddingLeft5dipandroid:paddingTop10dip ImageViewandroid:layout_width35dpandroid:layout_height35dpandroid:scaleTypefitCenterandroid:srcdrawable/find_more_friend_bottle /TextViewandroid:layout_widthwrap_contentandroid:layout_heightfill_parentandroid:layout_marginLeft10dpandroid:gravitycenterandroid:text漂流瓶android:textColorcolor/blackandroid:textSize15dip //LinearLayoutLinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationhorizontalandroid:paddingBottom10dipandroid:paddingLeft5dipandroid:paddingTop10dip ImageViewandroid:layout_width35dpandroid:layout_height35dpandroid:scaleTypefitCenterandroid:srcdrawable/more_game /TextViewandroid:layout_widthwrap_contentandroid:layout_heightfill_parentandroid:layout_marginLeft10dpandroid:gravitycenterandroid:text游戏android:textColorcolor/blackandroid:textSize15dip //LinearLayoutLinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:orientationhorizontalandroid:paddingBottom10dipandroid:paddingLeft5dipandroid:paddingTop10dip ImageViewandroid:layout_width35dpandroid:layout_height35dpandroid:scaleTypefitCenterandroid:srcdrawable/more_emoji_store /TextViewandroid:layout_widthwrap_contentandroid:layout_heightfill_parentandroid:layout_marginLeft10dpandroid:gravitycenterandroid:text表情商店android:textColorcolor/blackandroid:textSize15dip //LinearLayout/android.support.v7.widget.LinearLayoutCompat/RelativeLayout当然和真正微信里的界面还是不一样的还需要处理很多细节这里就不过分纠结于细节了主要还是了解LinearLayoutCompat的用法。
LinearLayoutCompat的源码分析
在使用完LinearLayoutCompat之后我们会很好奇它内部是如何实现添加分割线的那我们就看一下LinearLayoutCompat的源码进行分析。
\1. 观看源码首先可以知道 LinearLayoutCompat继承了ViewGroup然后我们查看它的构造函数
public LinearLayoutCompat(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);final TintTypedArray a TintTypedArray.obtainStyledAttributes(context, attrs,R.styleable.LinearLayoutCompat, defStyleAttr, 0);int index a.getInt(R.styleable.LinearLayoutCompat_android_orientation, -1);if (index 0) {setOrientation(index);}index a.getInt(R.styleable.LinearLayoutCompat_android_gravity, -1);if (index 0) {setGravity(index);}boolean baselineAligned a.getBoolean(R.styleable.LinearLayoutCompat_android_baselineAligned, true);if (!baselineAligned) {setBaselineAligned(baselineAligned);}mWeightSum a.getFloat(R.styleable.LinearLayoutCompat_android_weightSum, -1.0f);mBaselineAlignedChildIndex a.getInt(R.styleable.LinearLayoutCompat_android_baselineAlignedChildIndex, -1);mUseLargestChild a.getBoolean(R.styleable.LinearLayoutCompat_measureWithLargestChild, false);setDividerDrawable(a.getDrawable(R.styleable.LinearLayoutCompat_divider));mShowDividers a.getInt(R.styleable.LinearLayoutCompat_showDividers, SHOW_DIVIDER_NONE);mDividerPadding a.getDimensionPixelSize(R.styleable.LinearLayoutCompat_dividerPadding, 0);a.recycle();}从构造函数中首先会把LinearLayoutCompat的所有风格属性的值保存到一个TintTypedArray数组中然后从中取出用户给LinearLayoutCompat设置的orientation, gravitybaselineAligned的值如果这些值存在就给LinearLayoutCompat设置这些值。当然还会从TintTypedArray中取出weightSumbaselineAlignedChildIndexmeasureWithLargestChild等属性然后在构造函数的最低部会发现这一段代码 span stylewhite-space:pre /spansetDividerDrawable(a.getDrawable(R.styleable.LinearLayoutCompat_divider));mShowDividers a.getInt(R.styleable.LinearLayoutCompat_showDividers, SHOW_DIVIDER_NONE);mDividerPadding a.getDimensionPixelSize(R.styleable.LinearLayoutCompat_dividerPadding, 0);可以发现setDividerDrawable方法看名字意思是设置分割线的Drawable非常明显和分割线有关系接着是从TintTypedArray中继续获取mShowDividers和mDividerPadding的值分别用于判断显示分割线的模式和分割线的Padding值为多少。我们查看setDividerDrawable方法的内部实现 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();}可以看到该方法中传进来一个Drawable然后会进行if判断是否和原有的Drawable相等如果为true则return不执行下面的语句如果不是则将该Drawable设置给全局的mDivider又是if判断如果传进来的divider! null则获取它的固有宽高并设置给mDivider否则mDivider的宽高设为0然后会执行setWillNotDraw和requestLayout方法。
我们都知道每一个ViewGroup都会拥有onDraw,onLayout和onMeasure方法下面我们就查看一下这几个方法的源码进行分析看看分割线是如何进行绘制的。从源码往下看首先会看到onDraw方法。 protected void onDraw(Canvas canvas) {if (mDivider null) {return;}if (mOrientation VERTICAL) {drawDividersVertical(canvas);} else {drawDividersHorizontal(canvas);}}onDraw方法内部逻辑很简单判断mDivider是否为空然后是根据mOrientation的属性来调用不同的方法进行横或者竖的分割线绘制。查看drawDividersVertical方法内部
void drawDividersVertical(Canvas canvas) {final int count getVirtualChildCount();for (int i 0; i count; i) {final View child getVirtualChildAt(i);if (child ! null child.getVisibility() ! GONE) {if (hasDividerBeforeChildAt(i)) {final LayoutParams lp (LayoutParams) child.getLayoutParams();final int top child.getTop() - lp.topMargin - mDividerHeight;drawHorizontalDivider(canvas, top);}}}循环遍历所有子孩子进行是否为空和是否为不可见的判断然后调用hasDividerBeforeChildAt(i)如果为true则通过获取child的LayoutParams进行计算然后就可以计算出分割线的top距离然后调用drawHorizontalDivider(canvas,top)方法查看一下hasDividerBeforeChildAt方法的内部逻辑
protected boolean hasDividerBeforeChildAt(int childIndex) {if (childIndex 0) {return (mShowDividers SHOW_DIVIDER_BEGINNING) ! 0;} else if (childIndex getChildCount()) {return (mShowDividers SHOW_DIVIDER_END) ! 0;} else if ((mShowDividers SHOW_DIVIDER_MIDDLE) ! 0) {boolean hasVisibleViewBefore false;for (int i childIndex - 1; i 0; i--) {if (getChildAt(i).getVisibility() ! GONE) {hasVisibleViewBefore true;break;}}return hasVisibleViewBefore;}return false;}基本就是根据子孩子的位置进行相应的判断第一个位置最后一个位置还有中间所有位置返回一个boolean值会根据这个值来判断是否画分割线。然后回到drawDividersVertical方法中它会在遍历子View的最后调用drawHorizontalDivider方法查看一下这个方法
void drawHorizontalDivider(Canvas canvas, int top) {mDivider.setBounds(getPaddingLeft() mDividerPadding, top,getWidth() - getPaddingRight() - mDividerPadding, top mDividerHeight);mDivider.draw(canvas);}发现分割线其实是通过Drawable的setBounds方法进行设置的然后会调用 Drawable的draw方法对分割线进行绘制。drawDividersHorizontal方法的逻辑跟drawDividersVertical方法差不多它最后调用的是drawVerticalDivider方法。 void drawDividersHorizontal(Canvas canvas) {final int count getVirtualChildCount();final boolean isLayoutRtl ViewUtils.isLayoutRtl(this);for (int i 0; i count; i) {final View child getVirtualChildAt(i);if (child ! null child.getVisibility() ! GONE) {if (hasDividerBeforeChildAt(i)) {final LayoutParams lp (LayoutParams) child.getLayoutParams();final int position;if (isLayoutRtl) {position child.getRight() lp.rightMargin;} else {position child.getLeft() - lp.leftMargin - mDividerWidth;}drawVerticalDivider(canvas, position);}}}然后我们查看一下onMeasure方法内部就是根据Orientation的不同调用不同的方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (mOrientation VERTICAL) {measureVertical(widthMeasureSpec, heightMeasureSpec);} else {measureHorizontal(widthMeasureSpec, heightMeasureSpec);}}查看measureVertical方法内容较多我们一点点分析下面这段代码会循环遍历所有的子View,然后做出相应的判断如果hasDividerBeforeChildAt方法返回truemTotalLength会加上分割线的高度这个方法我们前面已经看过他内部的逻辑然后会获取子view的LayoutParamstotalWeight用于记录Weight的总和
for (int i 0; i count; i) {final View child getVirtualChildAt(i);if (child null) {mTotalLength measureNullChild(i);continue;}if (child.getVisibility() View.GONE) {i getChildrenSkipCount(child, i);continue;}if (hasDividerBeforeChildAt(i)) {mTotalLength mDividerHeight;}LinearLayoutCompat.LayoutParams lp (LinearLayoutCompat.LayoutParams) child.getLayoutParams();totalWeight lp.weight;接下来会对heightMode进行判断跟MeasureSpec.EXACTLY等属性进行比较还会判断是否使用了权重根据heightMode的值不同会有不同的处理方式mTotalLength的值的处理是不同的同时如果不满足if语句的条件会调用 measureChildBeforeLayout方法进行一次测量
pre namecode classjava if (heightMode MeasureSpec.EXACTLY lp.height 0 lp.weight 0) {// Optimization: dont bother measuring children who are going to use// leftover space. These views will get measured again down below if// there is any leftover space.final int totalLength mTotalLength;mTotalLength Math.max(totalLength, totalLength lp.topMargin lp.bottomMargin);skippedMeasure true;} else {int oldHeight Integer.MIN_VALUE;if (lp.height 0 lp.weight 0) {// heightMode is either UNSPECIFIED or AT_MOST, and this// child wanted to stretch to fill available space.// Translate that to WRAP_CONTENT so that it does not end up// with a height of 0oldHeight 0;lp.height LayoutParams.WRAP_CONTENT;}// Determine how big this child would like to be. If this or// previous children have given a weight, then we allow it to// use all available space (and we will shrink things later// if needed).measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec,totalWeight 0 ? mTotalLength : 0);if (oldHeight ! Integer.MIN_VALUE) {lp.height oldHeight;}final int childHeight child.getMeasuredHeight();final int totalLength mTotalLength;mTotalLength Math.max(totalLength, totalLength childHeight lp.topMargin lp.bottomMargin getNextLocationOffset(child));if (useLargestChild) {largestChildHeight Math.max(childHeight, largestChildHeight);}}pre namecode classjavavoid measureChildBeforeLayout(View child, int childIndex,int widthMeasureSpec, int totalWidth, int heightMeasureSpec,int totalHeight) {measureChildWithMargins(child, widthMeasureSpec, totalWidth,heightMeasureSpec, totalHeight);}当heightMode MeasureSpec.AT_MOST || heightMode MeasureSpec.UNSPECIFIED时mTotalLength值的计算方式是不同的 if (mTotalLength 0 hasDividerBeforeChildAt(count)) {mTotalLength mDividerHeight;}if (useLargestChild (heightMode MeasureSpec.AT_MOST || heightMode MeasureSpec.UNSPECIFIED)) {mTotalLength 0;for (int i 0; i count; i) {final View child getVirtualChildAt(i);if (child null) {mTotalLength measureNullChild(i);continue;}if (child.getVisibility() GONE) {i getChildrenSkipCount(child, i);continue;}final LinearLayoutCompat.LayoutParams lp (LinearLayoutCompat.LayoutParams)child.getLayoutParams();// Account for negative marginsfinal int totalLength mTotalLength;mTotalLength Math.max(totalLength, totalLength largestChildHeight lp.topMargin lp.bottomMargin getNextLocationOffset(child));}}到最后有下面一段代码 span stylewhite-space:pre /spanmaxWidth getPaddingLeft() getPaddingRight();
span stylewhite-space:pre /span// Check against our minimum widthmaxWidth Math.max(maxWidth, getSuggestedMinimumWidth());setMeasuredDimension(ViewCompat.resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState);measureVertical方法最后是通过setMeasuredDimension方法对测量的值进行设置的至于 maxWidth的值在源码的前面有相应的判断进行赋值所以整个measure的方法基本围绕 maxWidth和mTotalLength值的确定展开的其中如果hasDividerBeforeChildAt返回的值为true,mTotalLength会加上分割线的高度最后通过setMeasuredDimension赋值。
最后我们看看onLayout方法
protected void onLayout(boolean changed, int l, int t, int r, int b) {if (mOrientation VERTICAL) {layoutVertical(l, t, r, b);} else {layoutHorizontal(l, t, r, b);}}看一下layoutVertical的逻辑里面基本围绕以下两个值展开的 int childTop;int childLeft;for (int i 0; i count; i) {final View child getVirtualChildAt(i);if (child null) {childTop measureNullChild(i);} else if (child.getVisibility() ! GONE) {final int childWidth child.getMeasuredWidth();final int childHeight child.getMeasuredHeight();final LinearLayoutCompat.LayoutParams lp (LinearLayoutCompat.LayoutParams) child.getLayoutParams();int gravity lp.gravity;if (gravity 0) {gravity minorGravity;}final int layoutDirection ViewCompat.getLayoutDirection(this);final int absoluteGravity GravityCompat.getAbsoluteGravity(gravity,layoutDirection);switch (absoluteGravity Gravity.HORIZONTAL_GRAVITY_MASK) {case Gravity.CENTER_HORIZONTAL:childLeft paddingLeft ((childSpace - childWidth) / 2) lp.leftMargin - lp.rightMargin;break;case Gravity.RIGHT:childLeft childRight - childWidth - lp.rightMargin;break;case Gravity.LEFT:default:childLeft paddingLeft lp.leftMargin;break;}if (hasDividerBeforeChildAt(i)) {childTop mDividerHeight;}childTop lp.topMargin;setChildFrame(child, childLeft, childTop getLocationOffset(child),childWidth, childHeight);childTop childHeight lp.bottomMargin getNextLocationOffset(child);i getChildrenSkipCount(child, i);}}循环遍历子View根据不同的gravity对childLeft和childTop进行赋值如果存在分割线childTop会加上分割线的高度mDividerHeight最后是通过setChildFrame方法进行layout的完成的可以查看这个方法内部调用了child的layout方法 private void setChildFrame(View child, int left, int top, int width, int height) {child.layout(left, top, left width, top height);}到这里所有的LinearLayoutCompat的源码分析就结束了为什么要看分割线绘制的源码因为在很多控件中并没有分割线我们可以通过学习谷歌的源码仿照着进行分割线的绘制比如recyclerView就没有分割线但我们可以自己写一个分割线对于 recyclerView分割线设置有很多大神的博客都有描述这里就不在赘述了以后的博文会陆续给大家带来Material Design其他控件的博客。 更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》 1.Android车载应用开发系统学习指南附项目实战
2.Android Framework学习指南助力成为系统级开发高手
3.2023最新Android中高级面试题汇总解析告别零offer
4.企业级Android音视频开发学习路线项目实战附源码
5.Android Jetpack从入门到精通构建高质量UI界面
6.Flutter技术解析与实战跨平台首要之选
7.Kotlin从入门到实战全方面提升架构基础
8.高级Android插件化与组件化含实战教程和源码
9.Android 性能优化实战360°全方面性能调优
10.Android零基础入门到精通高手进阶之路
敲代码不易关注一下吧。ღ( ´ᴗ )