前言
前面我提到了这个抖音的视频滚动切换实现方案,在文章最后提到了需要对这个饺子播放器进行修改。
饺子播放器自带的一些东西并不能满足我们的需求,那么这个时间我们就需要继承饺子播放器去做一些自定义的操作,来实现模仿抖音的这种效果。
明确需求
我们注意到抖音是可以点击视频页面任何一个部位使得视频暂停,以及切换视图后视频暂停,但是保留视频播放位置,当返回后继续播放,并且抖音还有双击支持,双击后对视频进行点赞,这个后期也需要实现。
- 点击界面使视频暂停
- 切换视图保留视频播放位置
- 保留双击操作
OK,以上是我们需要继承和实现的功能。
具体实现
首先我们需要去做点击视频界面就使得视频暂停,而且不去影响我们的视频进度切换,和暂停点击事件。
那么这个情况就比较有意思了,我们先来看看饺子播放器的基础布局。
我们来看看JzvdStd这个类,我们发现它还继承了一个类,当然这是必然,继承了Jzvd这个抽象类,当然它的父类暂时不是重点,我们翻阅代码,能发现这样的方法——getLayoutId,在Jzvd,这是个抽象方法,这个方法里边return了一个布局,也就是R.layout.jz_layout_std。
学习到小细节
这里有个小细节,JzvdStd不是抽象类,但是它有了父类的抽象方法,这个是不行的,所以JzvdStd必须覆盖这个方法。(现学现卖,我对于抽象类的了解甚少。)我很好奇接口类的方法集合都需要被实现,那么这种抽象类的抽象方法又是个什么情况呢?
一篇文章告诉了我->Java中abstract的基本使用与详解,大家有兴趣了可以自己看看,前提是你也不太清楚这个抽象类。写这里也是给自己记录一下,如果以后忘记了就回来看看。
这个布局代码太多了,就不贴了,放个图片吧
简单的观察这个布局,发现如果要点击布局的任何一个部分来让视频暂停,我们只能依赖这个FrameLayout控件,OK,那么我们就对这个东西改一改。
继承饺子播放器
为了去更好的自定义,我们得去继承下JzvdStd,方便我们后期改动,推荐大家去把这个布局搬到自己的项目里,后面可以改布局。记得重写getLayoutId。
那么我们想一想,继承了要做什么?当然是要设置下这个铺满全界面FrameLayout的点击事件,但是对于饺子播放器肯定是有自己的实现方案的,因为什么,对于初始化好的视频而言,需要我们点击播放,这个点击是点击哪里都可以的,所以我们得看看饺子播放器的代码。
首当其冲的就是检查下JzvdStd,我们看看,最先被我考虑到的就是onClick,我们来看看这个代码。
@Override public void onClick(View v) { super.onClick(v); int i = v.getId(); if (i == R.id.poster) { clickPoster(); } else if (i == R.id.surface_container) { clickSurfaceContainer(); if (clarityPopWindow != null) { clarityPopWindow.dismiss(); } } else if (i == R.id.back) { clickBack(); } else if (i == R.id.back_tiny) { clickBackTiny(); } else if (i == R.id.clarity) { clickClarity(); } else if (i == R.id.retry_btn) { clickRetryBtn(); } }
选读:并不会影响核心内容
我们仔细阅读一下,发现当视频初始化后,点击任意位置可以播放是因为clickPoster这个方法,出于好奇心,我看了下这个东西为什么可以使得视频播放。
化繁为简,第一个判断看起来是判断这个地址或者说资源是不是为空,第二个判断首先是判断了下当前状态是不是正常的,也就是准备就绪状态,然后里边的判断可能是判断现在是不是WiFi啥的吧,不太清楚,但是它执行了startVideo,这个就是播放视频的方法,再上一篇文档应该已经出现过了。
我们注意到R.id.surface_container,这个正好是,FrameLayout的ID,那么我们仔细看看,从里边的方法来看,clickSurfaceContainer,是现实播放器的这个界面展示的,如果你不点击屏幕,会隐藏一些东西,点击后就显示一会后消失。
那么我们在这里插入,如果当前视频是播放状态就把视频暂停了,好,想法很不错,但是有个问题,我们怎么知道现在的状态哪里来,假设你没有看上面的选读,是不是还得找?我们现在来看看,视频需要点击播放和暂停,这玩意怎么实现?我们是不是可以猜测,播放或者暂停的那里会判断播放状态。有了这个想法我们就去找找。
这里我们可以看到播放按钮的ID,那么就找找看R.id.start,显然JzvdStd搜索无结果,那么我们来看看它继承的类,Jzvd,我们在Jzvd里找到了它。控件变量是startButton,可以发现,它把点击监听器也是设置在了类里。
我们直接来看看代码,发现clickStart方法,去看看,这个大家如果用都有,我就不贴了。
发现里边有state变量在比较,通过代码我们发现STATE_PAUSE就是暂停状态,STATE_PLAYING是播放状态,先不着急,我们发现如果是播放状态点击播放按钮就会暂停视频,同时显示暂停UI。
mediaInterface.pause(); onStatePause();
现在判断状态有了,而且暂停视频的方法也有了(我不知道饺子播放器带了暂停方法没有,我只找到了释放全部视频的方法,如果大家找到了请留言一下。)
现在我们需要创建一个类来继承JzvdStd,我们把onClick覆写进来。
@Override public void onClick(View v) { super.onClick(v); int i = v.getId(); if (i == R.id.poster) { clickPoster(); } else if (i == R.id.surface_container) { if (state == STATE_PLAYING) { //暂停视频 mediaInterface.pause(); //更新状态 state = STATE_PAUSE; } clickSurfaceContainer(); if (clarityPopWindow != null) { clarityPopWindow.dismiss(); } } else if (i == R.id.back) { clickBack(); } else if (i == R.id.back_tiny) { clickBackTiny(); } else if (i == R.id.clarity) { clickClarity(); } else if (i == R.id.retry_btn) { clickRetryBtn(); } }
如果你和我一样也是初学者,肯定好奇,为什么只贴了这一个,我之前去网上查资料也就困惑住了
public class JZPlay extends JzvdStd
这个方法是在上面这样的类里的,它需要实现一些接口,你按快捷键实现就可以。
那么我们来看看上面代码,我们加入了一段判断,首先确定用户是不是点击了FrameLayout,如果是就判断播放状态,如果正在播放,就执行暂停,这样就实现了点击任意位置暂停视频。而且不影响其他UI功能。
现在我们运行,但是有个新问题了,我尝试双击,发现有卡顿了,我突然意识到饺子播放器默认双击是会暂停,我当时就麻了,但是不能被这个问题打倒,当然如果你不需要双击事件问题不大,但是我们这里是需要实现的,现在来看看接下来怎么解决。
单双击监听
通过不断检查,发现JzvdStd里有一个手势监听器。onDoubleTap双击监听,下面一个单击监听。
现在好了,我们只需要把上面的代码办下来放这里就行了,当我正想去我们继承的类里覆盖这个变量时发现出大问题,protected修饰后不能在不同包的子类里继承,这就尴尬了呀,当我正准备换其他方案时我发现下面有一个事件onTouch,这个里边有一行代码。
gestureDetector.onTouchEvent(event);
我发现了个新办法,这个手势监听器被保护了无法覆写,但是我们可以覆写onTouch,onTouchEvent,这个是安卓事件分发机制里的一个东西,我之前看过一点,但是没怎么弄懂,这里就不阐述了。
那么我们来看看新的代码
/** * 双击 * TODO 这里搬了过来,它的父类把这个变量保护了起来,导致无法去自定义点击事件,但是我吗需要自定义,就必须把他搬下来 */ private GestureDetector myGestureDetector = new GestureDetector(getContext().getApplicationContext(), new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDoubleTap(MotionEvent e) { if (state == STATE_PLAYING || state == STATE_PAUSE) { Log.d(TAG, "doublClick [" + this.hashCode() + "] "); startButton.performClick(); } return super.onDoubleTap(e); } @Override public boolean onSingleTapConfirmed(MotionEvent e) { if (!mChangePosition && !mChangeVolume) { //这个东西不能放在FrameLayout点击开机 if (state == STATE_PLAYING) { //暂停视频 mediaInterface.pause(); //更新状态 state = STATE_PAUSE; } onClickUiToggle(); } return super.onSingleTapConfirmed(e); } @Override public void onLongPress(MotionEvent e) { super.onLongPress(e); } }); //同理,因为被保护,这里得改下触发器的变量名使其使用我们自定义的 @Override public boolean onTouch(View v, MotionEvent event) { int id = v.getId(); if (id == R.id.surface_container) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: startDismissControlViewTimer(); if (mChangePosition) { long duration = getDuration(); int progress = (int) (mSeekTimePosition * 100 / (duration == 0 ? 1 : duration)); bottomProgressBar.setProgress(progress); } break; } myGestureDetector.onTouchEvent(event); } else if (id == R.id.bottom_seek_progress) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: cancelDismissControlViewTimer(); break; case MotionEvent.ACTION_UP: startDismissControlViewTimer(); break; } } //TODO 将触摸消息拦截下来 不向下传递了,如果不这样做父类会执行它的点击事件,这样就冲突了 return false; }
但是我们没有照搬,首先我们把前面的代码换到onSingleTapConfirmed里,这样双击事件也就能执行了。
同时我们的return直接返回了flase,而不是super.onTouch(v, event);,因为现在它的父类也有onTouch,照抄会出现一个问题,那就是父类似乎执行了同样的代码,这样就有问题了,但是现在这样改虽然使得点击事件触控都正常了,但是这样可能导致Jzvd收不到,导致一些滑动操作挂了,这个我没有测试,不敢断言,大家可以自行测试,这样单击和双击功能就兼容了。
我现在还没有测试这个问题,下来我会使用手机单独打包测试下,填这个坑。
后期实现
现在仅仅是实现兼容了单机和双击行为,后期还得解决一下切换视图暂停视频,切换回来继续播放的功能,这个下次讲吧很晚了。
发表回复