取消ObjectAnimator动画引起的一个小问题

需求背景

需求背景,取消当前的动画,重新开始倒计时动画。倒计时动画布局:

<ProgressBar
                    android:id="@+id/progress_voice"
                    ...."/>

倒计时动画监听器:

			 mAnimator = ObjectAnimator
			                    .ofInt(viewHolder.progressBar, PROGRESS_PROPERTY, viewHolder.progressBar.getMax())
			                    .setDuration(EntertainVoicePreference.MILLIS_IN_FUTURE); 
            mAnimator.setInterpolator(new LinearInterpolator());
            mAnimator.start();
            mAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation, boolean isReverse) {
                    viewHolder.voiceOpenProgressBar.setVisibility(View.INVISIBLE);
                    viewHolder.voiceOpenCountDownTv.setVisibility(View.INVISIBLE);
                    raiseAction(R.id.anim_end); //回调给Fragment的一个方法
                }
            });

正常流程如下:


	//该方法在Fragment中,如果在Adapter中执行了raiseAction(上文会有调用)就会回调
 	mPresenter.registerDelegate(R.id.anim_end, new ActionListener<Object>() {
            @Override
            public void call(Context context, int actionId, Object data, ViewObject<?> viewObject) {
                if (data instanceof XXXModel) {
                    if (TextUtils.equals(mEndId,((XXXModel)data).getTitle())) {
                        mEndId = "end";
                        updateListDatas(false);
                        updateSoundState(); //会有打点等其他操作
                        completedVoiceOpenAnim = true;
                    }
                }
            }
        });

	mCommonRecyclerViewAdapter.notifyChangedAll(EntertainHotSoonShortVideoViewObject.UPDATE_VOICE_STOP);

    @Override
    public void onBindViewHolder(EntertainHotSoonShortVideoViewObject.ViewHolder viewHolder, List<Object> payloads) {
        super.onBindViewHolder(viewHolder, payloads);
        if (payloads != null) {
            for (Object object : payloads) {
	            if (object instanceof String) {
	                if (UPDATE_VOICE_STOP.equals((String) object)) {
	                     stopVoiceAnim(viewHolder);
	                 }
	            }
	       }
	    }
	}
	
	/**
     * 停止progressbar动画
     */
    private void stopVoiceAnim(ViewHolder viewHolder) {
        if (mAnimator != null && mAnimator.isRunning()) {
            mAnimator.end();
        }
        viewHolder.progressBar.setVisibility(View.INVISIBLE);
    }
    

由于执行了mAnimator.end(); 所以会回调上文提及的onAnimationEnd方法,然后调用raiseAction方法。当然动画倒计时结束也会执行onAnimationEnd方法。

问题

如果当前正在倒计时,下拉刷新,需要重新倒计时,所以需要终止正在倒计时的动画。正如上文提到,终止mAnimator.end(),会回调上文提及的onAnimationEnd方法。导致由于主动暂停动画而不需要上报的打点而上报了。所以要过滤掉取消的动画监听。

解决办法一

在启动动画的地方获取数据第一条的title:

Bean o = mRecyclerViewAdapter.getDataList().get(0);
mEndId = o.getTitle();  //mEndId是全局变量String类型
mRecyclerViewAdapter.notifyChangedAll(XXXiewObject.VOICE_START_ANIM);

在接受的地方去过滤即可:

//该方法在Fragment中,如果在Adapter中执行了raiseAction(上文会有调用)就会回调
 	mPresenter.registerActionDelegate(R.id.anim_end, new ActionListener<Object>() {
            @Override
            public void call(Context context, int actionId, Object data, ViewObject<?> viewObject) {
                if (data instanceof HotsoonModel) {
                    if (TextUtils.equals(mEndId,((XXXModel)data).getTitle())) {
                        mEndId = "end";
                        ......
                        completedVoiceOpenAnim = true;
                    }
                }
            }
        });

如果是刷新,单面上条数据的title和当前的title不一样。
如果是多任务(进入前倒计时未结束)进入重新计时,由于上次动画设置了mVoiceAnimEndId = “end”,所以也会过滤掉。

问题根源

停止倒计时,AnimatorListenerAdapter依然会回调onAnimatorEnd.
即使用mAnimator.cancel();也会执行:

package android.animation;
public abstract class Animator implements Cloneable {
 	/**
     * Cancels the animation. Unlike {@link #end()}, <code>cancel()</code> causes the animation to
     * stop in its tracks, sending an
     * {@link android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} to
     * its listeners, followed by an
     * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} message.
     *
     * <p>This method must be called on the thread that is running the animation.</p>
     */
    public void cancel() {
    }

	/**
     * Ends the animation. This causes the animation to assign the end value of the property being
     * animated, then calling the
     * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} method on
     * its listeners.
     *
     * <p>This method must be called on the thread that is running the animation.</p>
     */
    public void end() {
    }

不明白为什么官方执行cancel,还要执行end。用户如果需要end,可以执行cancel之后,再执行end。

解决方法二

解决方法一在线上环境运行没问题,但是测试环境下,刷新之后,第一条数据的title和刷新之前的title一样,通过判断title来确定是哪次刷新就不准确了。
新版本的策略是通过计算时间来确定,比如开始动画的地方记录一个时间戳,
在动画结束回调,也就是Animation#end方法再记录一个时间戳,两个时间戳差值,也就是动画实际运行的时间,该时间和动画预定的时间值误差在200毫秒内(由于动画初始化也需要消耗一定的时间),就认为动画真正的执行完了,否则认为动画被取消了。

但该方法也有个弊端,比如动画暂停时,时间计算比较复杂,解决方法是和产品沟通,去掉了动画暂停的功能,因为动画暂停的时机比较边缘,case复现的时机比较少。

解决方法三

咱们没实践,调研如下:
停止动画,可以调用的方法有两种方式:

  • mVoiceobjectAnimator.end();
  • mVoiceobjectAnimator.cancel();

end最后调用的就是listener#onAnimationEnd;由于无法区分是主动取消还是动画执行完,所以弃用。
cancel调用的是listener#onAnimationCancel,然后是onAnimationEnd.
如果有 onAnimationCancel回调,记一次Boolean flag,在onAnimationEnd 如果flag表明是取消的,就不再继续后面的步骤了,同时flag设置false。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值