Android-踩坑系列-ViewGroup的子View真正实现Margin属性
2018-03-20 09:04
1001 查看
楼主最近在复习自定义View,在复习到自定义ViewGroup这个知识点时,发现了一个问题–就是我们之前的定义ViewGroup在考虑Margin属性可能有问题。本文在解决该问题给出建议性的意见,但是不一定是正确的,如果有错误或者不当的地方,希望指正。
本文参考文章:
1.Android 手把手教您自定义ViewGroup(一)
2.你的自定义View是否真的支持Margin
这里我先贴出一个常规的写法:
在代码中,我们考虑到了padding属性和Margin属性,同时我们可以在xml代码测试一下效果
xml中这样写:
模拟器上展示的效果图:
看上去似乎是没有问题的,我们给TextView设置了marginLeft为20dp,在手机上也能正常显示出来margin属性。但是,如果TextView的layout_width设置为match_parent会怎么样呢?
xml代码:
此时我们在Android studio右侧的预览界面来看看此时效果:
我们发现虽然TextVeiw向左移动了20dp,但是我们发现了一个问题,就是TextView右侧超出了屏幕,也就是说,TextView的layout_marginLeft 属性根本没有影响到它的width,只是单纯将TextView向右移动了20dp。这个是有问题的,我们去看看系统的LinearLayout布局,margin属性会影响View的宽和高的。从而得知,我们这里支持的Margin属性是假的!那怎么才能真正的支持Margin属性呢?
我们先来看看measureChildren方法的源码:
这个方法表达的意思非常简单,就是循环测量每个子View。然后我们再来看看measureChild方法:
在measureChild方法里面,先利用父布局的XXXXMeasureSpec、padding值和子View向父布局申请的大小来生成子View的宽和高。这里我们就看出问题了,我们发现系统在测量子View的width和height时,只是考虑了padding的影响,没有考虑Margin对View的width和height的影响。
看到这里,我们明白了,为什么之前我们给TextView设置了marginLeft,同时设置TextView的layout_width为match_parent时,TextView只是单纯的向右移动了,而没有调整TextView的大小。因为我们通过measureChild方法来测量每个子View是不会考虑Margin属性对View的大小的影响。
知道的问题所在,解决问题就非常的容易。解决的问题的办法就是重写measureChildren方法,在测量每个View时,考虑到margin的影响。其实在ViewGroup还有一个方法那就是measureChildWidthMargins方法,这个方法测量每个View时,考虑到了每个View的margin属性的影响。我们来看看measureChildWidthMargins方法的源代码:
我们发现在这个方法里面,将Margin属性的影响也考虑到的。那么我们就来重写measureChildren方法:
在这个重写的代码中,我们需要主要两点:
1.在原来的measureChildren方法的if判断条件是:(child.mViewFlags & VISIBILITY_MASK) != GONE,而我们这里是:view != null && view.getVisibility() != GONE。我们这里的依据是LinearLayout,系统的LinearLayout也重写了measureChildren方法的,它的判断条件就是:view != null && view.getVisibility() != GONE。
2.measureChildrenWithMargins方法多出两个参数,分别是:widthUsed,heightUsed,这里传入的是两个0,这里的依据还是LinearLayout,LinearLayout调用measureChildrenWithMargins传入就是两个0。
重写之后,我们来看看之前的match_parent的情况(记得Rebuild一下工程):
这下就变得正常得多了!
本文参考文章:
1.Android 手把手教您自定义ViewGroup(一)
2.你的自定义View是否真的支持Margin
1.提出问题
这里我举一个简单的例子来说,假设我们需要定义一个ViewGroup放置一个子View,同时这个子View支持Padding和Margin属性。这里我先贴出一个常规的写法:
public class CustomViewGroup02 extends ViewGroup { public CustomViewGroup02(Context context) { super(context); } public CustomViewGroup02(Context context, AttributeSet attrs) { super(context, attrs); } public CustomViewGroup02(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); //这里假设只有一个子View measureChildren(widthMeasureSpec, heightMeasureSpec); View view = getChildAt(0); MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams(); int width = view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + getPaddingLeft() + getPaddingRight(); int height = view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin + getPaddingTop() + getPaddingBottom(); setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize:width, (heightMode == MeasureSpec.EXACTLY) ? heightSize:height); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { View view = getChildAt(0); MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams(); int left = getPaddingLeft() + lp.leftMargin; int top = getPaddingTop() + lp.topMargin; view.layout(left, top, left + view.getMeasuredWidth(), top + view.getMeasuredHeight()); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } }
在代码中,我们考虑到了padding属性和Margin属性,同时我们可以在xml代码测试一下效果
xml中这样写:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.apple.android_demo08.CustomViewGroup02 android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="50dp" android:layout_height="50dp" android:layout_marginLeft="20dp" android:background="#FFDAB9" /> </com.example.apple.android_demo08.CustomViewGroup02> </android.support.constraint.ConstraintLayout>
模拟器上展示的效果图:
看上去似乎是没有问题的,我们给TextView设置了marginLeft为20dp,在手机上也能正常显示出来margin属性。但是,如果TextView的layout_width设置为match_parent会怎么样呢?
xml代码:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.apple.android_demo08.CustomViewGroup02 android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:layout_height="50dp" android:layout_marginLeft="20dp" android:background="#FFDAB9" /> </com.example.apple.android_demo08.CustomViewGroup02> </android.support.constraint.ConstraintLayout>
此时我们在Android studio右侧的预览界面来看看此时效果:
我们发现虽然TextVeiw向左移动了20dp,但是我们发现了一个问题,就是TextView右侧超出了屏幕,也就是说,TextView的layout_marginLeft 属性根本没有影响到它的width,只是单纯将TextView向右移动了20dp。这个是有问题的,我们去看看系统的LinearLayout布局,margin属性会影响View的宽和高的。从而得知,我们这里支持的Margin属性是假的!那怎么才能真正的支持Margin属性呢?
2.解决问题
要想解决问题,必须先知道问题出现在哪里。这个问题就出现在onMeasure方法中measureChildren方法。我们先来看看measureChildren方法的源码:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
这个方法表达的意思非常简单,就是循环测量每个子View。然后我们再来看看measureChild方法:
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
在measureChild方法里面,先利用父布局的XXXXMeasureSpec、padding值和子View向父布局申请的大小来生成子View的宽和高。这里我们就看出问题了,我们发现系统在测量子View的width和height时,只是考虑了padding的影响,没有考虑Margin对View的width和height的影响。
看到这里,我们明白了,为什么之前我们给TextView设置了marginLeft,同时设置TextView的layout_width为match_parent时,TextView只是单纯的向右移动了,而没有调整TextView的大小。因为我们通过measureChild方法来测量每个子View是不会考虑Margin属性对View的大小的影响。
知道的问题所在,解决问题就非常的容易。解决的问题的办法就是重写measureChildren方法,在测量每个View时,考虑到margin的影响。其实在ViewGroup还有一个方法那就是measureChildWidthMargins方法,这个方法测量每个View时,考虑到了每个View的margin属性的影响。我们来看看measureChildWidthMargins方法的源代码:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
我们发现在这个方法里面,将Margin属性的影响也考虑到的。那么我们就来重写measureChildren方法:
@Override protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); for (int i = 0; i < count; i++) { final View view = getChildAt(i); if (view != null && view.getVisibility() != GONE){ meas ae53 ureChildWithMargins(view, widthMeasureSpec, 0, heightMeasureSpec, 0); } } }
在这个重写的代码中,我们需要主要两点:
1.在原来的measureChildren方法的if判断条件是:(child.mViewFlags & VISIBILITY_MASK) != GONE,而我们这里是:view != null && view.getVisibility() != GONE。我们这里的依据是LinearLayout,系统的LinearLayout也重写了measureChildren方法的,它的判断条件就是:view != null && view.getVisibility() != GONE。
2.measureChildrenWithMargins方法多出两个参数,分别是:widthUsed,heightUsed,这里传入的是两个0,这里的依据还是LinearLayout,LinearLayout调用measureChildrenWithMargins传入就是两个0。
重写之后,我们来看看之前的match_parent的情况(记得Rebuild一下工程):
这下就变得正常得多了!
相关文章推荐
- Android自定义组件系列【4】——自定义ViewGroup实现双侧滑动
- Android自定义组件系列【3】自定义ViewGroup实现侧滑
- Android自定义组件系列【3】——自定义ViewGroup实现侧滑
- Android自定义组件系列【3】——自定义ViewGroup实现侧滑
- Android自定义控件系列六:自定义ViewGroup(一)实现ViewPager效果
- android自定义控件系列教程----继承ViewGroup实现带阻力效果的可回弹的SrollView
- Android自定义组件系列【4】——自定义ViewGroup实现双侧滑动
- Android UI设计系列之自定义TextView属性实现带下划线的文本框(4)
- Android自己定义组件系列【4】——自己定义ViewGroup实现双側滑动
- Android自己定义组件系列【3】——自己定义ViewGroup实现側滑
- Android自定义控件系列五:自定义ViewGroup(一)实现ViewPager效果
- Android自定义控件系列六:自定义ViewGroup(一)实现ViewPager效果
- Android自定义控件系列六:自定义ViewGroup(一)实现ViewPager效果
- Android自定义组件系列【3】——自定义ViewGroup实现侧滑
- Android自定义组件系列【4】——自定义ViewGroup实现双侧滑动
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
- Android ViewGroup实现页面滑动效果并实现不同的动画效果(转载)
- android之自定义ViewGroup和自动换行的布局的实现
- Android ViewPager真正的实现无限循环滚动
- Android仿人人客户端(v5.7.1)——采用ViewGroup做父容器,实现左侧滑动菜单(三)