需求
在自定义ViewGroup时,其中包含一个TextView,设置Marqueue,当文本长度最大宽度时,开始滚动显示。
问题
最开始使用固定宽度为最大宽度,然后文字居中,无问题。
后来由于需求变更,加入了drawable,所以宽度调整为自适应,文字居左。这时候问题出现了。
文字一直会滚动,哪怕文字很短,已经展示完全的时候
分析
首先确认问题原因是居左、drawable及drawable padding、自适应宽度导致的,因为就修改了这几个地方。
依次回调居左、drawable及drawable padding的代码,问题还在,确认问题是自适应宽度导致的。
首先考虑是onMeasure测量导致的
onMeasure代码
final TextView child = (TextView) getChildAt(i);
final LayoutParams lp = child.getLayoutParams();
//获取子控件的宽高约束规则
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
0, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
0, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
首先以为是在测量时没计算padding及drawable padding导致的,所以加上了相关padding(其实这步可以排除,因为我View没有设置padding,而且去除drawble相关设置后还是存在这个问题,所以可以排除“测量时没计算padding及drawable padding导致”)。
final TextView child = (TextView) getChildAt(i);
final LayoutParams lp = child.getLayoutParams();
//获取子控件的宽高约束规则
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
child.getCompoundPaddingLeft() + child.getCompoundPaddingRight() + dp4 * 2, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
0, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
问题还在,考虑是否测量代码本身存在问题。
更改onMeasure代码
measureChildren(widthMeasureSpec, heightMeasureSpec);
问题还在,非逼我debug源码。
debug源码
debug入口肯定是在TextView.startMarquee()
private void startMarquee() {
// Do not ellipsize EditText
if (getKeyListener() != null) return;
if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
return;
}
if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
&& getLineCount() == 1 && canMarquee()) {
if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
final Layout tmp = mLayout;
mLayout = mSavedMarqueeModeLayout;
mSavedMarqueeModeLayout = tmp;
setHorizontalFadingEdgeEnabled(true);
requestLayout();
invalidate();
}
if (mMarquee == null) mMarquee = new Marquee(this);
mMarquee.start(mMarqueeRepeatLimit);
}
}
发现canMarquee是检查TextView宽度是否足够的判定。
private boolean canMarquee() {
int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
return width > 0 && (mLayout.getLineWidth(0) > width
|| (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
&& mSavedMarqueeModeLayout.getLineWidth(0) > width));
}
debug发现一个奇怪的问题:
width是591,mLayout.getLineWidth(0)是592,导致mLayout.getLineWidth(0) > width成立,判定为宽度不够。
在这里尝试获取了一下getWidth和getMeasureWidth发现getWidth要小1。
看来就是这里出问题了,实际宽度比测量宽度小了,为什么宽度会小了呢。
debug Textview的onMeasure发现以下这段代码:
width += getCompoundPaddingLeft() + getCompoundPaddingRight();
发现测量出来的宽度是592 + getCompoundPaddingLeft() + getCompoundPaddingRight();
说明测量出来是没问题的,为啥getMeasureWidth到getWidth减少了1呢?
发现问题
百度一下,你就知道:Android开发之getMeasuredWidth和getWidth区别从源码分析
宽度是mRight - mLeft,而mRight、mLeft是在setFrame中赋值的,而在layout中调用了setFrame。
而view的layout是在ViewGroup的onLayout中调用的,看看我是咋写的:
child.layout(getWidth() / 2 - child.getMeasuredWidth() / 2,xxx, getWidth() / 2 + child.getMeasuredWidth() / 2,xxx)
一猜就猜到了是因为整数是奇数除以2时,导致宽度丢了1。
解决问题
child.layout(getWidth() / 2 - child.getMeasuredWidth() / 2, xxx, getWidth() / 2 - child.getMeasuredWidth() / 2 + child.getMeasuredWidth(), xxx);
编译,运行,问题解决!