关于view的visible和gone动画的坑

作者: 肉团先生 | 来源:发表于2016-06-13 08:23 被阅读6652次

title: "关于view的visible和gone动画的坑"
date: 2016-05-31 00:38:33
categories: android
tags:[android,Animator]


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

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

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

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

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

效果: enter image description here
<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>

对应的绑定代码:

@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群英传

效果图:


下面提供关键代码:

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)
  • 正常的动画,从下移动到上出现。

效果图:

实现代码:

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一直执行,迫使之前实现的效果一直都不正确。动画总是移动回来再移动回去。
  1. 关于坐标系的问题,发现使用屏幕的高度-llLayoutBottom.getHeight()不靠谱,对应的view只出现一半。暂时不明,所以直接使用getBottom、getTop来设置。可以发现动画的移动并没有其真实的坐标:getBottom、getTop来时原来的
  2. log+debug会加快调试的时间

相关文章

网友评论

  • f7e5061a6234:谢谢 应用里面需要简单处理一个文本框折叠 懒得用第三方控件了 就用了您的代码,效果挺好 请问 为什么没有设置动画的延迟时间 执行依旧有延迟的效果, 我还希望有一个小图标同步旋转 两个动画的持续时间尽量要同步 ,这不会是直接通过系统不断刷新layout控件造成的延迟吧............也就是性能好的设备实现快,性能差的设备实现慢
    在createDropAnim方法里面为ValueAnimator对象设置了一下延迟时间 比如1000毫秒 但是在接近播放完成的时候会有一个小的跳跃.....
    好似每次更新控件的layoutParams都会导致整个界面所有控件重新被执行layout方法吧 貌似很耗费资源 但是要实现缓冲折叠的效果 是不是也只有这中方式了
  • 83f96cc39060::smirk: 特意上来谢谢楼主;
    我根据楼主的东西,说下我的实现吧
    1.首先就是,楼上很多人问的mHiddenViewMeasuredHeight
    `public static void animOpen(final View view ,int mHiddenViewMeasuredHeight){
    view.setVisibility(View.VISIBLE);
    // TODO: 2017/3/10 测试该view的高_结论,得从外部传进来,正常并不能取到Gone的view的值
    //http://stackoverflow.com/questions/18277028/getheight-for-view-which-has-visibility-gone
    ValueAnimator va = createDropAnim(view,0,mHiddenViewMeasuredHeight);
    va.start();
    }`

    我干脆从外部传进来了,因为view是gone的时候,view.getHeight是不能取到实际的值的;

    2.在Activity中

    private SparseIntArray heightMap = new SparseIntArray();

    `private void setVisibleAfterGettingHeight(final View view , final boolean isGone){
    view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){
    @Override
    public void onGlobalLayout() {
    // gets called after layout has been done but before display
    // so we can get the height then hide the view
    int height = view.getHeight();
    heightMap.put(view.getId(),height);
    view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    if (isGone)
    view.setVisibility(View.GONE);
    }

    });
    }

    我用了这样的一个方法来取得view的height

    3.最后
    setVisibleAfterGettingHeight(R.id.view,false);
    int height = heightMap.get(R.id.view);
    MyAnim.animOpen(view,height );
    只需要这样用即可
    肉团先生:条条大路同罗马
  • 蓝冰海域:同问 mHiddenViewMeasuredHeight
    肉团先生:@装了个逼好酷喔 根据单词顾名思义,是该view的高,需要自行等到。如果已经写好工具类的话,直接调用得到
  • 8070869b33b7:你好 mHiddenViewMeasuredHeight 这个是传什么呢
    肉团先生:@别这样嘛 根据单词顾名思义,是该view的高,需要自行等到。如果已经写好工具类的话,直接调用得到

本文标题:关于view的visible和gone动画的坑

本文链接:https://www.haomeiwen.com/subject/dsiedttx.html