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

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

下图为实现的效果:

做这么一个功能,要考虑的内容大概分为以下几种:

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();
            }
        });
    }
}


3.demo 中写得较为简单,直接将上面的 ControllersView 添加至了 Activity 中

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new ControllersView(this));
    }
}

扩展思路:

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

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

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

项目源码:

https://github.com/neverwoodsS/VideoController

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值