关于view的visible和gone动画的坑

需求:如图所示,我需要做一个View的显示和消失的动画。看似简单,但是坑还是不少。消失的动画很简单,难点在visible的动画。因为动画是发生在visible之后,所以导致会生硬的将其他的view移到右边,然后在进行动画.

从传统动画到属性动画,各种尝试,结果失败告终。

  • 直接让整个view进行左移让checkbox移动到屏幕外,然后再需要的时候进行移动回来,结果还是很生硬,不知道为什么

跟同事讨论下,他的建议是:visible快速的一个动画左移,然后再一个动画缓慢的右移回来。实践下效果能够缓慢出来,但是view会一闪而过,体验十分不好。

按着这个思路,我们可以移动其他的view,通过遮住这个checkbox,来达到效果,那么只需要改变布局即可。

效果:enter image description here

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/bg_white"
android:minHeight="@dimen/dp_90">
<CheckBox
android:id="@+id/cb_select"
android:layout_width="@dimen/dp_36"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/dp_12"
android:button="@null"
android:checked="@{data.isSelected}"
android:drawableLeft="@drawable/selector_checkbox_red"
android:maxHeight="@dimen/dp_90"
android:onClick="@{data.onClick()}"
bind:visibility="@{data.isEditMode}" />
<LinearLayout
bind:move="@{data.isEditMode}"
android:layout_centerVertical="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dp_9">

<ImageView
android:id="@+id/iv_goods_pic"
android:layout_width="@dimen/dp_61"
android:layout_height="@dimen/dp_61"
android:layout_centerVertical="true"
android:layout_gravity="center_vertical"
android:background="@drawable/shape_imageview_bg"
android:src="@{data.img}" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dp_13"
android:layout_marginTop="@dimen/dp_21"
android:layout_weight="1"
android:text="@{data.name}"
android:textColor="@color/black_43"
android:textSize="@dimen/font_11" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginBottom="@dimen/dp_12"
android:layout_marginTop="@dimen/dp_14"
android:text="@{XmlUtil.formatPrice(data.skuPrice)}"
android:textColor="@color/font_red_41"
android:textSize="@dimen/font_11" />
</LinearLayout>
</RelativeLayout>

对应的绑定代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@BindingAdapter("bind:visibility")
public static void showVisibility(final View view, boolean visible) {
if (view.getTag() == null) {
view.setTag(true);
}
if (visible) {
view.animate().alpha(1);
} else {
view.animate().alpha(0);
}
}
@BindingAdapter("bind:move")
public static void animMove(final ViewGroup viewGroup, boolean visible) {
if (viewGroup.getTag() == null) {
viewGroup.setTag(true);
}
if (visible) {
viewGroup.animate().translationX(Systems.dpToPx(viewGroup.getContext(), 36));
} else {
viewGroup.animate().translationX(0);
}
}

另一种实现方式思路—通过改变高度达到要求

通过改变将要显示view的高度/宽度来达到要求。即:宽度/高度 从0开始逐步变成指定的高度。因为visible的时候,高度为0所以看不到一闪而过的情况。
来源:android群英传

效果图:

下面提供关键代码:

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
31
32
33
34
35
36
37
38
private void animOpen(final  View view){
view.setVisibility(View.VISIBLE);
ValueAnimator va = createDropAnim(view,0,mHiddenViewMeasuredHeight);
va.start();
}

private void animClose(final View view){
int origHeight = view.getHeight();
ValueAnimator va = createDropAnim(view,origHeight,0);
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setVisibility(View.GONE);
}
});
va.start();
}

/**
* 使用动画的方式来改变高度解决visible不一闪而过出现
* @param view
* @param start 初始状态值
* @param end 结束状态值
* @return
*/

private ValueAnimator createDropAnim(final View view,int start,int end) {
ValueAnimator va = ValueAnimator.ofInt(start, end);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();//根据时间因子的变化系数进行设置高度
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
layoutParams.height = value;
view.setLayoutParams(layoutParams);//设置高度
}
});
return va;
}

这种方式的缺点很明显:只能从上而下出现,为什么?

因为android的view默认的坐标系是左上角,也就意味着高度只能从上而下扩展。

当遇到从下而上出现则束手无策。开发还真的遇到这样的需求。但有之前的经验+转场动画setAlpha的思路,想到以下方式:

  • xml设置view为gone
  • 当设置为visible之前,将view设置为setAlpha(0)
  • 快速的动画(setDuration(1)),将view移动到当前显示屏幕之外并设置setAlpha(1)
  • 正常的动画,从下移动到上出现。

效果图:

实现代码:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
binding.btnShow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//bottom layout的出现于消失
if(isFirst){
binding.llLayoutBottom.setAlpha(0);
binding.llLayoutBottom.setVisibility(View.VISIBLE);
}
Tasks.handler().postDelayed(new Runnable() {
@Override
public void run() {
if(isEdit&&isFirst){
logger.e("bottom="+binding.llLayoutBottom.getBottom());
binding.llLayoutBottom.animate()
.y(binding.llLayoutBottom.getBottom())
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if(isFirst){
binding.llLayoutBottom.setAlpha(1);
logger.e("getTop="+binding.llLayoutBottom.getTop());
binding.llLayoutBottom.animate().y(binding.llLayoutBottom.getTop()).setDuration(1000);
isFirst=false;
}
}
})
.setDuration(1);
}else if(isEdit){//非第一次的情况执行
logger.e("getTop="+binding.llLayoutBottom.getTop());
binding.llLayoutBottom.animate()
.y(binding.llLayoutBottom.getTop()).setDuration(1000);
}
else {
logger.e("getBottom="+binding.llLayoutBottom.getBottom()+"getHeight"+binding.llLayoutBottom.getHeight());
binding.llLayoutBottom.animate()
.y(binding.llLayoutBottom.getBottom()).setDuration(1000);
}

}
}, 500);

}
});

上述代码总结:

  1. 为了偷懒直接使用animate()方法,却忘记了一旦进行监听AnimatorListener后,只要每执行一次动画,对应的方法就会执行一次,导致在打log中onAnimationEnd一直执行,迫使之前实现的效果一直都不正确。动画总是移动回来再移动回去。
  2. 关于坐标系的问题,发现使用屏幕的高度-llLayoutBottom.getHeight()不靠谱,对应的view只出现一半。暂时不明,所以直接使用getBottom、getTop来设置。可以发现动画的移动并没有其真实的坐标:getBottom、getTop来时原来的
  3. log+debug会加快调试的时间