0、效果图
1、先来看一下需求
1\.
项目中的视频回放要求Tik Tok在垂直方向上一次滑动一页。滑动要平稳不卡,手动触摸滑动超过1/2时松开即可滑动下一页,不超过1/2即可返回原页面。
2\.手指拖动页面滑动,只要不切换到其他页面,视频就在播放。切换页面时,之前的视频被破坏,页面开始初始化播放。
3\.切换页面时,过渡效果要自然,避免屏幕闪烁。具体滑动效果请直接参考Tik Tok….
公开来源地址:.
_ https://github.com/yang chong 211/ycscroll pager _
2、有几种实现方式
2.1 使用ViewPager
使用ViewPager实现竖直方法上下切换视频分析
1.最近需要在ViewPager中播放视频,也就是上下垂直滑动切换视频,视频就是网络视频。最初的实现思路是根据ViewPager中当前的项目位置初始化SurfaceView,销毁时根据项目位置移除SurfaceView。
2.上面的方法可以实现,但是有两个问题。第一,MediaPlayer的生命周期不易控制,存在内存泄漏问题。第二,当连续三个项目都是视频时,发现最后一个视频的最后一帧的bug会在来回滑动的过程中出现。
3.用户体验没有得到改善。在视频播放器初始化完成之前,视频的第一帧画面会被覆盖在上面。但是发现第一帧图片与视频的第一帧信息不一致,后面会通过代码给出解决方案。
大概的实现思路是这样
1.有必要定制一个在垂直方向滑动的可视寻呼机。注意这一点尤为重要。
2.一次滑动一页,建议使用查看寻呼机片段状态寻呼机适配器片段,这将在后面详细描述。
3.在片段中处理视频初始化、回放和销毁的逻辑。
4.由于一个页面需要创建一个片段,所以要注意性能和滑动流畅度,这需要分析和讨论。
不太建议使用ViewPager
1.1的滑动效果。ViewPager完全符合场景,支持片段、视图等UI绑定。只要修改布局和触摸事件,水平就可以改变。
视图寻呼机更改为垂直。
2.但是没有复用才是最致命的问题。在onLayout方法中,所有子视图都被实例化并排列在布局上。当项目数量很大时,将会极大地浪费性能。
3.其次,可见性判断的问题。许多人会认为片段在电子邮件上是可见的,但是片段在可视寻呼机上。
是一个反例,尤其是当多个ViewPager嵌套时,onresume中同时存在多个父片段和多个子片段。
状态,但只能看到其中一个。
除非放弃ViewPager的预加载机制。当报告页面内容曝光等重要数据时,需要判断许多条件:在电子邮件上,.
SetUserVisibleHint、setOnPageChangeListener等。
2.2 使用RecyclerView
使用RecyclerView实现树枝方向上下切换视频分析
1.首先RecyclerView它设置竖直方向滑动是十分简单的,同时关于item的四级缓存也做好了处理,而且滑动的效果相比ViewPager要好一些。
2.滑动事件处理比viewPager好,即使你外层嵌套了下拉刷新上拉加载的布局,也不影响后期事件冲突处理,详细可以看demo案例。
大概的实现思路是这样
1.自定义一个LinearLayoutManager,重写onScrollStateChanged方法,注意是拿到滑动状态。
2.一次滑动切换一个页面,可以使用PagerSnapHelper来实现,十分方便简单。
3.在recyclerView对应的adapter中,在onCreateViewHolder初始化视频操作,同时当onViewRecycled时,销毁视频资源。
4.添加自定义回调接口,在滚动页面和attch,detach的时候,定义初始化,页面销毁等方法,暴露给开发者。
3、用ViewPager实现
3.1 自定义ViewPager
代码如下所示,这里省略了不少的代码,具体可以看项目中的代码。
/ * * @author 杨充 * blog : https://github.com/yangchong211 * time : 2019/6/20 * desc : 自定义ViewPager,主要是处理边界极端情况 * revise: *
*/public class VerticalViewPager extends ViewPager { private boolean isVertical = false; private long mRecentTouchTime; public VerticalViewPager(@NonNull Context context) { super(context); } public VerticalViewPager(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); } private void init() { setPageTransformer(true, new HorizontalVerticalPageTransformer()); setOverScrollMode(OVER_SCROLL_NEVER); } public boolean isVertical() { return isVertical; } public void setVertical(boolean vertical) { isVertical = vertical; init(); } private class HorizontalVerticalPageTransformer implements PageTransformer { private static final float MIN_SCALE = 0.25f; @Override public void transformPage(@NonNull View page, float position) { if (isVertical) { if (position < -1) { page.setAlpha(0); } else if (position <= 1) { page.setAlpha(1); // Counteract the default slide transition float xPosition = page.getWidth() * -position; page.setTranslationX(xPosition); //set Y position to swipe in from top float yPosition = position * page.getHeight(); page.setTranslationY(yPosition); } else { page.setAlpha(0); } } else { int pageWidth = page.getWidth(); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. page.setAlpha(0); } else if (position <= 0) { // [-1,0] // Use the default slide transition when moving to the left page page.setAlpha(1); page.setTranslationX(0); page.setScaleX(1); page.setScaleY(1); } else if (position <= 1) { // (0,1] // Fade the page out. page.setAlpha(1 - position); // Counteract the default slide transition page.setTranslationX(pageWidth * -position); page.setTranslationY(0); // Scale the page down (between MIN_SCALE and 1) float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position)); page.setScaleX(scaleFactor); page.setScaleY(scaleFactor); } else { // (1,+Infinity] // This page is way off-screen to the right. page.setAlpha(0); } } } } / * 交换x轴和y轴的移动距离 * @param event 获取事件类型的封装类MotionEvent */ private MotionEvent swapXY(MotionEvent event) { //获取宽高 float width = getWidth(); float height = getHeight(); //将Y轴的移动距离转变成X轴的移动距离 float swappedX = (event.getY() / height) * width; //将X轴的移动距离转变成Y轴的移动距离 float swappedY = (event.getX() / width) * height; //重设event的位置 event.setLocation(swappedX, swappedY); return event; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { mRecentTouchTime = System.currentTimeMillis(); if (getCurrentItem() == 0 && getChildCount() == 0) { return false; } if (isVertical) { boolean intercepted = super.onInterceptTouchEvent(swapXY(ev)); swapXY(ev); // return touch coordinates to original reference frame for any child views return intercepted; } else { return super.onInterceptTouchEvent(ev); } } @Override public boolean onTouchEvent(MotionEvent ev) { if (getCurrentItem() == 0 && getChildCount() == 0) { return false; } if (isVertical) { return super.onTouchEvent(swapXY(ev)); } else { return super.onTouchEvent(ev); } }}
3.2 ViewPager和Fragment
采用了ViewPager+FragmentStatePagerAdapter+Fragment来处理。为何选择使用FragmentStatePagerAdapter,主要是因为使用
FragmentStatePagerAdapter更省内存,但是销毁后新建也是需要时间的。
一般情况下,如果你是用于ViewPager展示数量特别多的条目时,那么建议使用FragmentStatePagerAdapter。关于PagerAdapter的深度解析,可以我这篇文章:
PagerAdapter深度解析和实践优化
_https://juejin.im/post/5d401cabf265da03a53a12fe_
在activity中的代码如下所示
private void initViewPager() { List