实现Android组件宽度缩减和增加的动画,以及动画完成后ListView的刷新问题

在工作过程中,遇到这样的需求。两个ListView并列,要实现的效果是左边的ListView(简称list1)从右向左宽度不断减少,右边的ListView(简称list2)向左平移。逆向动画的原理相同,只需要修改参数就行了。

下面是实现思路

  1. 右边list2的平移动画很简单,使用ObjectAnimator做一个向左平移就行了,很简单,后面有具体代码,就不多说。

  2. list1的动画也尝试使用ObjectAnimator来做,并没有成功。具体代码如下:

    ObjectAnimator anim2 = ObjectAnimator.ofFloat(ll_left, "width", 0F, -offset * dm.density);

    查了一下发现View里面没有setWidth方法,getWidth方法里面是mRight - mLeft,那么就修改尝试用mRight来做动画的参数。

    int right = rl_right.getRight();
    ObjectAnimator anim2 = ObjectAnimator.ofFloat(ll_left, "right", 0F, right-offset * dm.density);

    还是不行,看了一下setRight和setTranslationX方法的内部实现不太一样,可能这就是动画导致无效的原因。对View绘制流程的具体代码还不熟悉,看不懂。
    这个思路到这里就断了。

  3. 换一个思路,既然ObjectAnimator不能用,那就用ValueAnimator来计算width或者说right,用setLayoutParams方法来直接修改布局。

    ValueAnimator anim2 = ValueAnimator.ofInt(0, offset);
    …………
    anim2.addUpdateListener(new AnimatorUpdateListener() {
    
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // TODO Auto-generated method stub
            int value = (Integer) animation.getAnimatedValue();
            LayoutParams lp = (LayoutParams) ll_left.getLayoutParams();
            lp.width = (int) (lp.width - value * dm.density);
            ll_left.setLayoutParams(lp);
        }
    });

    出现两个问题:
    a) 动画效果很差,有明显的卡顿;
    b) 右边的ListView平移动画结束之后,会再向左平移一个offset的距离。

    原因分析:
    a) 查看setLayoutParams方法源码,在给LayoutParams重新赋值之后,调用requestLayout方法刷新组件。再看requestLayout方法里面,大概就是从子view往父view上找,最后走到ViewRootImpl然后开始一级一级往下判断是否需要刷新布局,requestLayout会重新调用measure和layout流程,不会走到draw。卡顿的原因可能是因为measure加上layout过程计算量过大,需要简化流程。
    b) 两个ListView是写在LinearLayout里面的,两者存在布局上的依存关系,所以list1宽度减小导致list2的X值要减小一个offset的值,最终出现上述结果。

    解决办法:
    a) 宽度计算在动画监听里面已经完成了,所以不需要measure过程,可以直接调用layout方法修改布局。
    b) 将父容器改为RelativeLayout,去除两个ListView位置上的依存关系。

    修改后的代码:

    ValueAnimator anim2 = ValueAnimator.ofInt(0, offset);
    …………
    final int right = rl_right.getRight();
    anim2.addUpdateListener(new AnimatorUpdateListener() {
    
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // TODO Auto-generated method stub
            int value = (Integer) animation.getAnimatedValue();
            ll_left.layout(ll_left.getLeft(), ll_left.getTop(), (int)(right - value * dm.density)
                , ll_left.getBottom());
        }
    });
  4. 上面的代码基本满足了动画效果,移动流畅。实际操作后,还有一个问题。点击list2内的item或者调用list2的setSelection方法后,list1的布局会恢复成宽度减小前的样子,多出来的部分就会从list2的透明背景部分透出来。

    查看源码后发现两者都会调用requestLayout方法,上面提到过requestLayout会调用measure和layout方法,因为之前的动画仅调用了layout,没有实际改变view的宽度,所以刷新后恢复成最开始的样子。

    anim2.addListener(new AnimatorListenerAdapter() {
    
        @Override
        public void onAnimationEnd(Animator animation) {
            // TODO Auto-generated method stub
            LayoutParams lp = (LayoutParams) ll_left.getLayoutParams();
            lp.width = (int) (lp.width - offset * dm.density);
            ll_left.setLayoutParams(lp);
            isAnimating = false;
        }
    });

    其实在动画开始的时候调用setLayoutParams应该也行,但是会导致焦点逻辑上的错误,这就是另外的问题了,跟本文讲的内容无关。

最后附上完整代码:

private int offset = 200;
private int duration = 200;
private boolean isAnimating = false;

private void startAnim() {
    //rl_right就是list2,dm是android.util.DisplayMetrics类,用来获取屏幕密度dm.density
    ObjectAnimator anim1 = ObjectAnimator.ofFloat(rl_right, "translationX", 0F, -offset * dm.density); 
    anim1.setDuration(duration);
    anim1.setInterpolator(new AccelerateDecelerateInterpolator());
    final int right = ll_left.getRight(); //ll_left就是list1
    ValueAnimator anim2 = ValueAnimator.ofInt(0, offset);
    anim2.setDuration(duration);
    anim2.setInterpolator(new AccelerateDecelerateInterpolator());
    anim2.addUpdateListener(new AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // TODO Auto-generated method stub
            int value = (Integer) animation.getAnimatedValue();
            ll_left.layout(ll_left.getLeft(), ll_left.getTop()
                , (int)(right - value * dm.density), ll_left.getBottom());
        }
    });
    anim2.addListener(new AnimatorListenerAdapter() {

        @Override
        public void onAnimationStart(Animator animation) {
            // TODO Auto-generated method stub
            isAnimating = true;
            logo.setVisibility(View.INVISIBLE);
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            // TODO Auto-generated method stub
            LayoutParams lp = (LayoutParams) ll_left.getLayoutParams();
            lp.width = (int) (lp.width - offset * dm.density);
            ll_left.setLayoutParams(lp);
            isAnimating = false;
        }
    });
    anim1.start();
    anim2.start();
}

private void closeAnim() {
    ObjectAnimator anim1 = ObjectAnimator.ofFloat(rl_right, "translationX", -offset * dm.density, 0F);
    anim1.setDuration(duration);
    anim1.setInterpolator(new AccelerateDecelerateInterpolator());
    final int right = ll_left.getRight();
    ValueAnimator anim2 = ValueAnimator.ofInt(0, offset);
    anim2.setDuration(duration);
    anim2.setInterpolator(new AccelerateDecelerateInterpolator());
    anim2.addUpdateListener(new AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // TODO Auto-generated method stub
            int value = (Integer) animation.getAnimatedValue();
            ll_left.layout(ll_left.getLeft(), ll_left.getTop()
                , (int)(right + value * dm.density), ll_left.getBottom());
        }
    });
    anim2.addListener(new AnimatorListenerAdapter() {

        @Override
        public void onAnimationStart(Animator animation) {
            // TODO Auto-generated method stub
            isAnimating = true;
            LayoutParams lp = (LayoutParams) ll_left.getLayoutParams();
            lp.width = (int) (lp.width + offset * dm.density);
            ll_left.setLayoutParams(lp);
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            // TODO Auto-generated method stub
            isAnimating = false;
            logo.setVisibility(View.VISIBLE);
        }
    });
    anim1.start();
    anim2.start();
}

你可能感兴趣的:(Android,属性动画)