前几天爆出来一个线上bug:处于“已售完”状态的菜品,依然可以点击加菜。好在提交订单时有校验,对于这种订单会直接打回。
究其原因,是加减控件中的代码出了问题

问题回顾

在加减控件中,提供了enlargeEmptyAddBtnClickArea方法,用来扩大第一次加菜按钮的点击区域(PM提出的渣渣,更渣渣的是UX居然通过了这个需求)。

Talk is cheap, read the code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 将初始加菜按钮点击矩形区域向外扩大
* @param parentView 包含更大点击区域的父View
* @param unit 扩大的dp值
*/
public void enlargeEmptyAddBtnClickArea(final View parentView, final int unit) {
parentView.post(new Runnable() {
@Override
public void run() {
if (emptyAddBtn.getVisibility() != VISIBLE) {
return;
}
checkContext();
Context context = getContext();
int[] emptyAddBtnCoord = new int[2];
emptyAddBtn.getLocationOnScreen(emptyAddBtnCoord);
int[] parentViewCoord = new int[2];
parentView.getLocationOnScreen(parentViewCoord);
int[] relativeCoord = {emptyAddBtnCoord[0] - parentViewCoord[0], emptyAddBtnCoord[1] - parentViewCoord[1]};
Rect delegateArea = new Rect(relativeCoord[0], relativeCoord[1], relativeCoord[0] + emptyAddBtn.getMeasuredWidth(), relativeCoord[1] + emptyAddBtn.getMeasuredHeight());
int enlargeUnit = ViewUtils.dip2px(context, unit);
delegateArea.left -= enlargeUnit;
delegateArea.top -= enlargeUnit;
delegateArea.right += enlargeUnit;
delegateArea.bottom += enlargeUnit;
CustomTouchDelegate touchDelegate = new CustomTouchDelegate(delegateArea, emptyAddBtn);
parentView.setTouchDelegate(touchDelegate);
}
});
}

这样通过直接operateBtn.enlargeEmptyAddBtnClickArea就可以扩大点击区域。

乍看起来没什么问题对吧?代码中同样考虑到了如果加菜按钮当前不可见,就不会主动扩大其区域。

但是为什么,还会出现文章开头提到的“已售完的菜品仍然可以添加至购物车”问题!!!


原因分析

在debug后,发现问题出在这段代码中的判断

1
2
3
if (emptyAddBtn.getVisibility() != VISIBLE) {
return;
}

当整个加减控件(OperateButton)的Visibility == INVISIBLE or GONE时,其内部emptyAddBtn的Visibility居!然!还!是!Visible!

写个Demo验证一下


Talk is cheap, just code

demo功能很简单,外层的ViewGroup & 内层的View,当outer置为不可见(INVISIBLE or GONE)时,输出inner的Visibility。界面如下:

验证后发现一个惊人的事实,Outer的Visibility不会影响Inner的Visibility!也就是说,不论Outer设置为Gone还是Invisible,只要Inner之前是Visible,那么调用inner.getVisibility()后,都会返回Visible!


正确的判断方法

View.java中为我们提供了isShown()方法,从注释中即可看出它会将外层ViewGroup可见性计算进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Returns the visibility of this view and all of its ancestors
*
* @return True if this view and all of its ancestors are {@link #VISIBLE}
*/
public boolean isShown() {
View current = this;
//noinspection ConstantConditions
do {
if ((current.mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
ViewParent parent = current.mParent;
if (parent == null) {
return false; // We are not attached to the view root
}
if (!(parent instanceof View)) {
return true;
}
current = (View) parent;
} while (current != null);

return false;
}

反思

起初编码的时候,谁能想到,外层设置Invisibile后,内层View居然还是Visible的状态呢?

出现这个问题的根本原因在于自己对View的机制研究不够深入,引以为戒。

更应该注意到View本身提供了isShown()方法来判断可见性,根本不需要使用Visibility来重复造轮子,更何况还是错误的轮子。


=========END=========