YCScrollPager
项目地址:yangchong211/YCScrollPager
简介: 竖直方向,一次滚动一个页面的封装库。目前支持 ViewPager 做法,也支持 RecyclerView 做法……使用 ViewPager 则只是修改滑动速率,以及滚动翻页过渡时间;使用 recyclerView 打造丝滑切换视频的功能,更多内容可以看 demo
标签:
目录介绍
- 01.先来看一下需求
- 02.有几种实现方式
- 2.1 使用 ViewPager
- 2.2 使用 RecyclerView
- 03.具体使用
- 3.0 如何引用该 lib
- 3.1 ViewPager 实现方式
- 3.2 RecyclerView 实现方式
- 04.部分优化点
- 4.1 ViewPager 改变滑动速率
- 4.2 PagerSnapHelper 注意点
- 4.3 自定义 LayoutManager 注意点
- 06.其他说明介绍
00.先来看一下效果图
01.先来看一下需求
- 项目中的视频播放,要求实现抖音那种竖直方向一次滑动一页的效果。滑动要流畅不卡顿,并且手动触摸滑动超过 1/2 的时候松开可以滑动下一页,没有超过 1/2 返回原页。
- 手指拖动页面滑动,只要没有切换到其他的页面,视频都是在播放的。切换了页面,上一个视频销毁,该页面则开始初始化播放。
- 切换页面的时候过渡效果要自然,避免出现闪屏。具体的滑动效果,可以直接参考抖音……
02.有几种实现方式
2.1 使用 ViewPager
- 使用 ViewPager 实现竖直方法上下切换视频分析
- 1.最近项目需求中有用到需要在 ViewPager 中播放视频,就是竖直方法上下滑动切换视频,视频是网络视频,最开始的实现思路是 ViewPager 中根据当前 item 位置去初始化 SurfaceView,同时销毁时根据 item 的位置移除 SurfaceView。
- 2.上面那种方式确实是可以实现的,但是存在 2 个问题,第一,MediaPlayer 的生命周期不容易控制并且存在内存泄漏问题。第二,连续三个 item 都是视频时,来回滑动的过程中发现会出现上个视频的最后一帧画面的 bug。
- 3.未提升用户体验,视频播放器初始化完成前上面会覆盖有该视频的第一帧图片,但是发现存在第一帧图片与视频第一帧信息不符的情况,后面会通过代码给出解决方案。
- 大概的实现思路是这样
- 1.需要自定义一个竖直方向滑动的 ViewPager,注意这个特别重要。
- 2.一次滑动一页,建议采用 ViewPager+FragmentStatePagerAdapter+Fragment 方式来做,后面会详细说。
- 3.视频的初始化逻辑和销毁逻辑优化,性能和滑动流畅度分析和探讨。
- 不太建议使用 ViewPager
- 1.ViewPager 自带的滑动效果完全满足场景,而且支持 Fragment 和 View 等 UI 绑定,只要对布局和触摸事件部分作一些修改,就可以把横向的 ViewPager 改成竖向。
- 2.但是没有复用是个最致命的问题。在 onLayout 方法中,所有子 View 会实例化并一字排开在布局上。当 Item 数量很大时,将会是很大的性能浪费。
- 3.其次是可见性判断的问题。很多人会以为 Fragment 在 onResume 的时候就是可见的,而 ViewPager 中的 Fragment 就是个反例,尤其是多个 ViewPager 嵌套时,会同时有多个父 Fragment 多个子 Fragment 处于 onResume 的状态,却只有其中一个是可见的。除非放弃 ViewPager 的预加载机制。在页面内容曝光等重要的数据上报时,就需要判断很多条件:onResumed 、 setUserVisibleHint 、 setOnPageChangeListener 等。
2.2 使用 RecyclerView
- 使用 RecyclerView 实现树枝方向上下切换视频分析
- 1.首先 RecyclerView 它设置竖直方向滑动是十分简单的,同时关于 item 的四级缓存也做好了处理,而且滑动的效果相比 ViewPager 要好一些。
- 2.滑动事件处理比 viewPager 好,即使你外层潜逃了下拉刷新上拉加载的布局,也不影响后期事件冲突处理,详细可以看 demo 案例。
- 大概的实现思路是这样
- 1.自定义一个 LinearLayoutManager,重写 onScrollStateChanged 方法,注意是拿到滑动状态。
- 2.一次滑动切换一个页面,可以使用 PagerSnapHelper 来实现,十分方便简单。
- 3.在 recyclerView 对应的 adapter 中,在 onCreateViewHolder 初始化视频操作,同时当 onViewRecycled 时,销毁视频资源。
03.具体使用
3.0 如何引用该 lib
- 直接添加依赖即可
implementation 'com.yc:PagerLib:1.0.1'
3.1 ViewPager 实现方式
3.1.1 使用 VerticalViewPager 的方式如下所示
- 在布局中
<com.yc.pagerlib.pager.VerticalViewPager android:id="@+id/vp" android:layout_width="match_parent" android:layout_height="match_parent"/>
- 在 activity 中,这里是 Viewpager+FragmentStatePagerAdapter+Fragment ``` private void initViewPager() {
}List<Video> list = new ArrayList<>(); ArrayList<Fragment> fragments = new ArrayList<>(); //数据源注意,使用的时候替换成你的,这里设置一些假数据 for (int a = 0; a< DataProvider.VideoPlayerList.length ; a++){ Video video = new Video(DataProvider.VideoPlayerTitle[a], 10,"",DataProvider.VideoPlayerList[a]); list.add(video); fragments.add(VideoFragment.newInstant(DataProvider.VideoPlayerList[a])); } vp.setOffscreenPageLimit(1); vp.setCurrentItem(0); vp.setOrientation(DirectionalViewPager.VERTICAL); FragmentManager supportFragmentManager = getSupportFragmentManager(); MyPagerAdapter myPagerAdapter = new MyPagerAdapter(fragments, supportFragmentManager); vp.setAdapter(myPagerAdapter);
class MyPagerAdapter extends FragmentStatePagerAdapter{
private ArrayList<Fragment> list;
public MyPagerAdapter(ArrayList<Fragment> list , FragmentManager fm){
super(fm);
this.list = list;
}
@Override
public Fragment getItem(int i) {
return list.get(i);
}
@Override
public int getCount() {
return list!=null ? list.size() : 0;
}
}
```
-
在 fragment 中做视频播放相关操作
@Override public void onStop() { super.onStop(); VideoPlayerManager.instance().releaseVideoPlayer(); } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_video, container, false); return view; } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); videoPlayer = view.findViewById(R.id.video_player); } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Log.d("初始化操作","------"+index++); VideoPlayerController controller = new VideoPlayerController(getActivity()); videoPlayer.setUp(url,null); videoPlayer.setPlayerType(ConstantKeys.IjkPlayerType.TYPE_IJK); videoPlayer.setController(controller); ImageUtils.loadImgByPicasso(getActivity(),"", R.drawable.image_default,controller.imageView()); }
3.2 RecyclerView 实现方式
- 代码中直接使用 PagerLayoutManager,如下所示
PagerLayoutManager viewPagerLayoutManager = new PagerLayoutManager( this, OrientationHelper.VERTICAL); recyclerView.setLayoutManager(viewPagerLayoutManager);
-
设置 layoutManager 初始化和销毁相关的监听
viewPagerLayoutManager.setOnViewPagerListener(new OnPagerListener() { @Override public void onInitComplete() { System.out.println("OnPagerListener---onInitComplete--"+"初始化完成"); } @Override public void onPageRelease(boolean isNext, int position) { System.out.println("OnPagerListener---onPageRelease--"+position+"-----"+isNext); } @Override public void onPageSelected(int position, boolean isBottom) { System.out.println("OnPagerListener---onPageSelected--"+position+"-----"+isBottom); } });
04.部分优化点
4.1 ViewPager 改变滑动速率
-
可以通过反射修改属性,注意,使用反射的时候,建议手动 try-catch,避免异常导致崩溃。代码如下所示:
/** * 修改滑动灵敏度 * @param flingDistance 滑动惯性,默认是 75 * @param minimumVelocity 最小滑动值,默认是 1200 */ public void setScrollFling(int flingDistance , int minimumVelocity){ try { Field mFlingDistance = ViewPager.class.getDeclaredField("mFlingDistance"); mFlingDistance.setAccessible(true); Object o = mFlingDistance.get(this); Log.d("setScrollFling",o.toString()); //默认值 75 mFlingDistance.set(this, flingDistance); Field mMinimumVelocity = ViewPager.class.getDeclaredField("mMinimumVelocity"); mMinimumVelocity.setAccessible(true); Object o1 = mMinimumVelocity.get(this); Log.d("setScrollFling",o1.toString()); //默认值 1200 mMinimumVelocity.set(this,minimumVelocity); } catch (Exception e){ e.printStackTrace(); } }
4.2 PagerSnapHelper 注意点
- 好多时候会抛出一个异常"illegalstateexception an instance of onflinglistener already set".
- 看 SnapHelper 源码 attachToRecyclerView(xxx)方法时,可以看到如果 recyclerView 不为 null,则先 destoryCallback(),它作用在于取消之前的 RecyclerView 的监听接口。然后通过 setupCallbacks()设置监听器,如果当前 RecyclerView 已经设置了 OnFlingListener,会抛出一个状态异常。那么这个如何复现了,很容易,你初始化多次就可以看到这个 bug。
- 建议手动捕获一下该异常,代码设置如下所示。源码中判断了,如果 onFlingListener 已经存在的话,再次设置就直接抛出异常,那么这里可以增强一下逻辑判断,ok,那么问题便解决呢!
try { //attachToRecyclerView 源码上的方法可能会抛出 IllegalStateException 异常,这里手动捕获一下 RecyclerView.OnFlingListener onFlingListener = mRecyclerView.getOnFlingListener(); //源码中判断了,如果 onFlingListener 已经存在的话,再次设置就直接抛出异常,那么这里可以判断一下 if (onFlingListener==null){ mPagerSnapHelper.attachToRecyclerView(mRecyclerView); } } catch (IllegalStateException e){ e.printStackTrace(); }
4.3 自定义 LayoutManager 注意点
- 网上有人已经写了一篇自定义 LayoutManager 实现抖音的效果的博客,我自己也很仔细看了这篇文章。不过我觉得有几个注意要点,因为要用到线上 app,则一定要尽可能减少崩溃率……
- 通过 SnapHelper 调用 findSnapView 方法,得到的 view,一定要增加非空判断逻辑,否则很容易造成崩溃。
- 在监听滚动位移 scrollVerticallyBy 的时候,注意要增加判断,就是 getChildCount()如果为 0 时,则需要返回 0。
- 在 onDetachedFromWindow 调用的时候,可以把 listener 监听事件给 remove 掉。
06.其他说明介绍
关于其他内容介绍
关于博客汇总链接
其他推荐
- 博客笔记大汇总【15 年 10 月到至今】,包括 Java 基础及深入知识点,Android 技术博客,Python 学习笔记等等,还包括平时开发中遇到的 bug 汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是 markdown 格式的!同时也开源了生活博客,从 12 年起,积累共计 47 篇[近 20 万字],转载请注明出处,谢谢!
- 链接地址:https://github.com/yangchong211/YCBlogs
- 如果觉得好,可以 star 一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!