TitleBar中View的测量问题

在这里插入图片描述
在这里插入图片描述

在TitleBar类中,对于View的加载有两种方式,一种是通过自定义属性的方式在布局中添加view,一种是动态的增加view。

现在对RightView分别用这两种方法进行测试,上图分别对应了这两种方式的最终显示结果。

当tittle过长时,应该是需要省略号代替多余字的,避免遮挡两侧view的显示,可以看到,由于添加view方式的不同,造成了不能接受的结果。

int rightCustomViewRes = typedArray.getResourceId(R.styleable.TitleBar_rightCustomView, 0);

public void setRightView(View view, OnClickListener listener){
rightView.removeAllViews();
rightView.addView(view, layoutParams);
setOnRightClickListener(listener);
}

通过自定义属性的方式在布局中添加view的代码执行顺序:

public TitleBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

init(context, attrs);
}

private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = getTitleBarTypedArray(context, attrs);
initViewWithAttrs(typedArray);
typedArray.recycle();
}

private void initViewWithAttrs(TypedArray typedArray) {

}

#initViewWithAttrs

设置左侧 icon、设置右侧 icon、设置右侧自定义 View、设置 Title

下面是关键,他通过测量leftview和rightView的宽度,来得到middleView的最大宽度和title的最大宽度

int widthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
leftView.measure(widthSpec, heightSpec);
int leftWidth = leftView.getMeasuredWidth();
rightView.measure(widthSpec, heightSpec);
int rightWidth = rightView.getMeasuredWidth();

int middleMaxWidth = getResources().getDisplayMetrics().widthPixels - (Math.max(leftWidth, rightWidth) << 1);

// middleview 最大宽度
((LayoutParams) middleView.getLayoutParams()).width = middleMaxWidth;

textView.setGravity(Gravity.CENTER);
// 设置 textview 截断
textView.setMaxWidth(middleMaxWidth);
textView.setEllipsize(TextUtils.TruncateAt.END);

通过log看执行:
在这里插入图片描述

假设通过动态的方式来加载RightView

结果明了,middleView的最大宽度和title的最大宽度在initViewWithAttrs方法中就已经进行了设置,之后再通过setRight方法只是单纯的进行View的add。

现在回头再看下面几行代码的意图

int widthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
rightView.measure(widthSpec, heightSpec);
int rightWidth = rightView.getMeasuredWidth();

int widthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

这个方法获取的宽度是minWidth参数设置的大小和background指定背景宽度,这两个宽度的最大值,高也是如此,也就是说如果View的xml中没有两个参数中的其中一项,那么这个方法测量的宽高也是为0的,这个方法测量的并不是获取xml中设置的android:layout_height android:layout_width的值。看图:
在这里插入图片描述

rightView.measure(widthSpec, heightSpec) -->View的measure方法–>onMeasure()方法。

#onMeasure

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

如果背景为空,那么就取mMinHeight的值,如果背景不为空就取max(mMinHeight,mBackground.getMinimumHeight())背景高度和mMinHeight最大值。

#getMinimumWidth

mMinWidth· = android:minWidth属性所指定的值;若android:minWidth没指定,则默认为0。

由源码可知:mBackground.getMinimumWidth()的大小 = 背景图Drawable的原始宽度,若无原始宽度,则为0;

public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
//返回背景图Drawable的原始宽度
return intrinsicWidth > 0 ? intrinsicWidth :0 ;
}
// 注:BitmapDrawable有原始宽度,而ShapeDrawable没有

#getDefaultSize

public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
}
return result;

}

由于我们使用的是UNSPECIFIED,result = size,size来自getSuggestedMinimumWidth(),否则result=0。

#setMeasuredDimension

这里主要是保存测量值

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;

    measuredWidth  += optical ? opticalWidth  : -opticalWidth;
    measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);

}

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;

 mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;

}

最后是int rightWidth = rightView.getMeasuredWidth();

获取的就是最终测量值。

public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}

解决:

参考android如何获取view在布局中的高度与宽度详解

Runnable 对象中的方法会在 View 的 measure、layout 等事件完成后触发。

UI 事件队列会按顺序处理事件,在 setContentView() 被调用后,事件队列中会包含一个要求重新 layout 的 message,所以任何 post 到队列中的 Runnable 对象都会在 Layout 发生变化后执行。

@Override
public void setRightView(View view, OnClickListener listener) {
    rightView.removeAllViews();
    if (view != null) {
        rightView.addView(view, layoutParams);
    }
    setOnRightClickListener(listener);
    rightView.post(new Runnable() {
        @Override
        public void run() {              
            int middleMaxWidth =
                    getResources().getDisplayMetrics().widthPixels - (rightView.getMeasuredWidth() << 1);

            middleView.getLayoutParams().width = middleMaxWidth;

            // 设置 textview 截断
            textView.setMaxWidth(middleMaxWidth);
            textView.setEllipsize(TextUtils.TruncateAt.END);
        }
    });
}

在这里插入图片描述
在这里插入图片描述

补充:

getMeasuredWidth与getWidth的区别

在measure方法结束后getMeasuredWidth方法就会有值

getWidth方法是在layout方法完成后才有的值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值