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

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

下图为实现的效果:

504f583e9535

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 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 接口所描述方法的类。

项目源码

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值