美文网首页
Android 视频控制器出入逻辑及动画的封装

Android 视频控制器出入逻辑及动画的封装

作者: neverwoods | 来源:发表于2016-08-29 14:32 被阅读0次

    最近有朋友在做视频播放器,跟我提到了在做控制器的时候感觉逻辑和动画有一定嗯复杂性,让我一下子有了兴趣。
    下图为实现的效果:

    video-controller-animation.gif

    做这么一个功能,要考虑的内容大概分为以下几种:
    1.界面上能看到的视图;
    2.动画效果的实现;
    3.控制器的收起、弹出相关的控制逻辑。

    分析

    1.demo 中虽然只有上下两个控制器,但实际应用中也许左右也有,甚至中间也有,但无论如何他们都有一个共性:弹出、收起;

    2.我朋友遇到的困难大概在于他所使用的 view 动画在频繁的点击事件下很难保证流畅性与连续性。并且熟悉 Android 动画的朋友应该知道,view 动画不会真正移动 view 的位置,也就是说他不得不对动画加上监听,去动态控制 view 上子 view 的点击事件是否有效。综上,在此处采用属性动画应该会更合适;

    3.在 demo 中点击一次屏幕会弹出控制器,再点击会收起,弹出两秒左右之后还会自动收起。并且在动画执行过程中再次点击,会取消当前动画并反向执行。这个逻辑也许会根据业务的需求发生变化,所以应当尽可能只写在一处。

    代码

    /**
     * Controller 的定义
     * 就是说你至少得可以 显示/隐藏 才能称为 Controller 对吧?
     */
    public interface Controller {
        void show();
        void hide();
    }
    
    /**
     * 采用 ValueAnimator 实现动画效果的基类 Controller
     */
    public abstract class ValueAnimatorController implements Controller {
    
        private static final int DURATION = 250;
    
        /**
         * 子类提供显示时的目标 value
         * @return
         */
        protected abstract int getShowTarget();
    
        /**
         * 子类提供隐藏时的目标 value
         * @return
         */
        protected abstract int getHideTarget();
    
        /**
         * 子类处理动态计算出的 value 以实现动画效果
         * @param shift
         */
        protected abstract void onShiftChanged(int shift);
    
        protected View view;
        protected ValueAnimator va;
        protected int shift;
    
        public ValueAnimatorController(View view) {
            this.view = view;
        }
    
        @Override
        public void show() {
            stop();
            makeAnimation(getShowTarget());
        }
    
        @Override
        public void hide() {
            stop();
            makeAnimation(getHideTarget());
        }
    
        private void stop() {
            if (va != null) {
                va.cancel();
            }
        }
    
        protected void makeAnimation(int target) {
            //这里采用当前状态的 shift 而不是初始值,
            //是为了动画被停止后,朝反方向移动更平滑
            va = ValueAnimator.ofInt(shift, target);
            va.setDuration(DURATION);
            va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    shift = (int) animation.getAnimatedValue();
                    onShiftChanged(shift);
                }
            });
            va.start();
        }
    }
    
    /**
     * 出入事件分发及逻辑控制
     */
    public class ControllerManager {
    
        /**
         * true 当前处于显示状态,或正在执行显示动画
         * false 当前处于隐藏状态,或正在执行隐藏动画
         */
        private boolean showing = true;
    
        private List<Controller> controllerList = new ArrayList<>();
        private CountDownTimer countDownTimer;
    
        public ControllerManager addController(Controller controller) {
            controllerList.add(controller);
            return this;
        }
    
        /**
         * 初始化完成后 开始倒计时隐藏 controllers
         * @return
         */
        public ControllerManager startWorking() {
            startCount();
            return this;
        }
    
        public boolean isShowing() {
            return showing;
        }
    
        /**
         * 切换状态
         * 同时取消倒计时
         */
        public void switchState() {
            stopCount();
            showing = !showing;
            if (showing) {
                show();
            } else {
                hide();
            }
        }
    
        /**
         * 分发 show 事件至所有 controller
         * 同时开始倒计时
         */
        private void show() {
            for (Controller controller : controllerList) {
                controller.show();
            }
            startCount();
        }
    
        private void hide() {
            for (Controller controller : controllerList) {
                controller.hide();
            }
        }
    
        /**
         * 倒计时结束时切换状态
         */
        private void startCount() {
            countDownTimer = new CountDownTimer(2500, 2500) {
                @Override
                public void onTick(long millisUntilFinished) {
    
                }
    
                @Override
                public void onFinish() {
                    switchState();
                }
            }.start();
        }
    
        private void stopCount() {
            if (countDownTimer != null) {
                countDownTimer.cancel();
                countDownTimer = null;
            }
        }
    }
    

    以上代码分别对应问题1、2、3。

    使用

    1.先继承 ValueAnimatorController 实现 Top 和 Bottom 两种动画
    public class TopController extends ValueAnimatorController {
    
        public TopController(View view) {
            super(view);
        }
    
        @Override
        protected int getShowTarget() {
            return 0;
        }
    
        @Override
        protected int getHideTarget() {
            return view.getHeight();
        }
    
        @Override
        protected void onShiftChanged(int shift) {
            LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams();
            params.setMargins(params.leftMargin, -shift, params.rightMargin, params.bottomMargin);
            view.setLayoutParams(params);
        }
    }
    
    public class BottomController extends ValueAnimatorController {
    
        public BottomController(View view) {
            super(view);
        }
    
        @Override
        protected int getShowTarget() {
            return 0;
        }
    
        @Override
        protected int getHideTarget() {
            return view.getHeight();
        }
    
        @Override
        protected void onShiftChanged(int shift) {
            LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams();
            params.setMargins(params.leftMargin, params.topMargin, params.rightMargin, -shift);
            view.setLayoutParams(params);
        }
    }
    
    2.将实际的 view 传入至 controller 中,再将 controller 统一交由 ControllerManager 统一处理事件
    public class ControllersView extends RelativeLayout {
    
        private Context context;
    
        private View topView;
        private View bottomView;
    
        private ControllerManager controllerManager;
    
        public ControllersView(Context context) {
            super(context);
            this.context = context;
            initView();
        }
    
        private void initView() {
            View rootView = LayoutInflater.from(context).inflate(R.layout.layout_control, this);
    
            topView = rootView.findViewById(R.id.ll_top);
            bottomView = rootView.findViewById(R.id.rl_bottom);
    
            controllerManager = new ControllerManager();
            controllerManager.addController(new TopController(topView))
                    .addController(new BottomController(bottomView))
                    .startWorking();
    
            rootView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    controllerManager.switchState();
                }
            });
        }
    }
    

    扩展思路

    1.假如界面正中会出现一个按钮,出现时一边旋转一边变大,消失时反之,则在 ValueAnimator 的基础上新建一个 CenterController,重写 getShowTarget, getHideTarget, onShiftChange 三个方法,再将按钮的 view 和此 controller 绑定再提交至 controllerManager 即可;

    2.如果界面上有多种不同的逻辑出现,比如说上述1中按钮需要双击才能消失,那么需要重写一种 ControllerManager,实现不同的逻辑。即在自定义的View中,根据逻辑的不同,会同时存在多个不同的 ControllerManager,用来管理逻辑不同的 view;

    3.如果不想使用 ValueAnimator 来实现动画效果,可以自行写一个实现了 Controller 接口所描述方法的类。

    项目源码

    https://github.com/neverwoodsS/VideoController

    相关文章

      网友评论

          本文标题:Android 视频控制器出入逻辑及动画的封装

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