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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容