android studio 组件外边距,Android 扩展-ViewGroup的子View真正实现Margin属性

楼主最近在复习自定义View,在复习到自定义ViewGroup这个知识点时,发现了一个问题--就是我们之前的定义ViewGroup在考虑Margin属性可能有问题。本文在解决该问题给出建议性的意见,但是不一定是正确的,如果有错误或者不当的地方,希望指正。

本文参考文章:

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:layout_width="match_parent"

android:layout_height="match_parent">

android:layout_width="match_parent"

android:layout_height="match_parent">

android:layout_width="50dp"

android:layout_height="50dp"

android:layout_marginLeft="20dp"

android:background="#FFDAB9" />

模拟器上展示的效果图:

496d5e2d7e27

看上去似乎是没有问题的,我们给TextView设置了marginLeft为20dp,在手机上也能正常显示出来margin属性。但是,如果TextView的layout_width设置为match_parent会怎么样呢?

xml代码:

android:layout_width="match_parent"

android:layout_height="match_parent">

android:layout_width="match_parent"

android:layout_height="match_parent">

android:layout_width="match_parent"

android:layout_height="50dp"

android:layout_marginLeft="20dp"

android:background="#FFDAB9" />

此时我们在Android studio右侧的预览界面来看看此时效果:

496d5e2d7e27

我们发现虽然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){

measureChildWithMargins(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一下工程):

496d5e2d7e27

这下就变得正常得多了!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值