目录介绍

  • 01.先来看一下需求
  • 02.有几种实现方式
    • 2.1 使用ViewPager
    • 2.2 使用RecyclerView
  • 03.用ViewPager实现
    • 3.1 自定义ViewPager
    • 3.2 ViewPager和Fragment
    • 3.3 修改滑动距离翻页
    • 3.4 修改滑动速度
  • 04.用RecyclerView实现
    • 4.1 自定义LayoutManager
    • 4.2 添加滑动监听
    • 4.3 监听页面是否滚动
    • 4.4 attach和Detached
  • 05.优化点详谈
    • 5.1 ViewPager改变滑动速率
    • 5.2 PagerSnapHelper注意点
    • 5.3 自定义LayoutManager注意点
    • 5.4 视频播放逻辑优化
    • 5.5 视频逻辑充分解藕
    • 5.6 翻页卡顿优化分析
    • 5.7 上拉很快翻页黑屏

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.在fragment中处理视频的初始化,播放和销毁逻辑等逻辑。
    • 4.由于一个页面需要创建一个fragment,注意性能和滑动流畅度这块需要分析和探讨。
  • 不太建议使用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时,销毁视频资源。
    • 4.添加自定义回调接口,在滚动页面和attch,detach的时候,定义初始化,页面销毁等方法,暴露给开发者。

03.用ViewPager实现

3.1 自定义ViewPager

  • 代码如下所示,这里省略了不少的代码,具体可以看项目中的代码。

    /*** <pre>*     @author 杨充*     blog  : https://github.com/yangchong211*     time  : 2019/6/20*     desc  : 自定义ViewPager,主要是处理边界极端情况*     revise:* </pre>*/
    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;@Overridepublic 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 transitionfloat xPosition = page.getWidth() * -position;page.setTranslationX(xPosition);//set Y position to swipe in from topfloat 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 pagepage.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 transitionpage.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;}@Overridepublic 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 viewsreturn intercepted;} else {return super.onInterceptTouchEvent(ev);}}@Overridepublic 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深度解析和实践优化
  • 在activity中的代码如下所示
    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;}@Overridepublic Fragment getItem(int i) {return list.get(i);}@Overridepublic int getCount() {return list!=null ? list.size() : 0;}
    }
    
  • 那么在fragment中如何处理呢?关于视频播放器,这里可以看我封装的库,视频lib
    public class VideoFragment extends  Fragment{public VideoPlayer videoPlayer;private String url;private int index;@Overridepublic void onStop() {super.onStop();VideoPlayerManager.instance().releaseVideoPlayer();}public static Fragment newInstant(String url){VideoFragment videoFragment = new VideoFragment();Bundle bundle = new Bundle();bundle.putString("url",url);videoFragment.setArguments(bundle);return videoFragment;}@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);Bundle arguments = getArguments();if (arguments != null) {url = arguments.getString("url");}}@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater,@Nullable ViewGroup container,@Nullable Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_video, container, false);return view;}@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);videoPlayer = view.findViewById(R.id.video_player);}@Overridepublic 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.3 修改滑动距离翻页

  • 需求要求必须手动触摸滑动超过1/2的时候松开可以滑动下一页,没有超过1/2返回原页,首先肯定是重写viewpager,只能从源码下手。经过分析,源码滑动的逻辑处理在此处,truncator的属性代表判断的比例值!

    • 这个方法会在切页的时候重定向Page,比如从第一个页面滑动,结果没有滑动到第二个页面,而是又返回到第一个页面,那个这个page会有重定向的功能
    private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {int targetPage;if (Math.abs(deltaX) > this.mFlingDistance && Math.abs(velocity) > this.mMinimumVelocity) {targetPage = velocity > 0 ? currentPage : currentPage + 1;} else {float truncator = currentPage >= this.mCurItem ? 0.4F : 0.6F;targetPage = currentPage + (int)(pageOffset + truncator);}if (this.mItems.size() > 0) {ViewPager.ItemInfo firstItem = (ViewPager.ItemInfo)this.mItems.get(0);ViewPager.ItemInfo lastItem = (ViewPager.ItemInfo)this.mItems.get(this.mItems.size() - 1);targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));}return targetPage;
    }
    
    • determineTargetPage这个方法就是计算接下来要滑到哪一页。这个方法调用是在MotionEvent.ACTION_UP这个事件下,先说下参数意思:

      • currentPage:当前ViewPager显示的页面
      • pageOffset:用户滑动的页面偏移量
      • velocity: 滑动速率
      • deltaX: X方向移动的距离
    • 进行debug调试之后,发现问题就在0.4f和0.6f这个参数上。分析得出:0.6f表示用户滑动能够翻页的偏移量,所以不难理解,为啥要滑动半屏或者以上了。
  • 也可以修改Touch事件
    • 控制ViewPager的Touch事件,这个基本是万能的,毕竟是从根源上入手的。你可以在onTouchEvent和onInterceptTouchEvent中做逻辑的判断。但是比较麻烦。

3.4 修改滑动速度

  • 使用viewPager进行滑动时,如果通过手指滑动来进行的话,可以根据手指滑动的距离来实现,但是如果通过setCurrentItem函数来实现的话,则会发现直接闪过去的,会出现一下刷屏。想要通过使用setCurrentItem函数来进行viewpager的滑动,并且需要有过度滑动的动画,那么,该如何做呢?
  • 具体可以分析setCurrentItem源码的逻辑,然后会看到scrollToItem方法,这个特别重要,主要是处理滚动过程中的逻辑。最主要关心的也是smoothScrollTo函数,这个函数中,可以看到具体执行滑动的其实就一句话,就是mScroller.startScroll(sx,sy,dx,dy,duration),则可以看到,是mScroller这个对象进行滑动的。那么想要改变它的属性,则可以通过反射来实现。
  • 代码如下所示,如果是手指触摸滑动,则可以加快一点滑动速率,当然滑动持续时间你可以自己设置。通过自己自定义滑动的时间,就可以控制滑动的速度。
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public void setAnimationDuration(final int during){try {// viewPager平移动画事件Field mField = ViewPager.class.getDeclaredField("mScroller");mField.setAccessible(true);// 动画效果与ViewPager的一致Interpolator interpolator = new Interpolator() {@Overridepublic float getInterpolation(float t) {t -= 1.0f;return t * t * t * t * t + 1.0f;}};Scroller mScroller = new Scroller(getContext(),interpolator){final int time = 2000;@Overridepublic void startScroll(int x, int y, int dx, int dy, int duration) {// 如果手工滚动,则加速滚动if (System.currentTimeMillis() - mRecentTouchTime > time) {duration = during;} else {duration /= 2;}super.startScroll(x, y, dx, dy, duration);}@Overridepublic void startScroll(int x, int y, int dx, int dy) {super.startScroll(x, y, dx, dy,during);}};mField.set(this, mScroller);} catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) {e.printStackTrace();}
    }
    

04.用RecyclerView实现

4.1 自定义LayoutManager

  • 自定义LayoutManager,并且继承LinearLayoutManager,这样就得到一个可以水平排向或者竖向排向的布局策略。如果你接触过SnapHelper应该了解一下LinearSnapHelper和PagerSnapHelper这两个子类类,LinearSnapHelper可以实现让列表的Item居中显示的效果,PagerSnapHelper就可以做到一次滚动一个item显示的效果。
  • 重写onChildViewAttachedToWindow方法,在RecyclerView中,当Item添加进来了调用这个方法。这个方法相当于是把view添加到window时候调用的,也就是说它比draw方法先执行,可以做一些初始化相关的操作。
    /*** 该方法必须调用* @param recyclerView                          recyclerView*/
    @Override
    public void onAttachedToWindow(RecyclerView recyclerView) {if (recyclerView == null) {throw new IllegalArgumentException("The attach RecycleView must not null!!");}super.onAttachedToWindow(recyclerView);this.mRecyclerView = recyclerView;if (mPagerSnapHelper==null){init();}mPagerSnapHelper.attachToRecyclerView(mRecyclerView);mRecyclerView.addOnChildAttachStateChangeListener(mChildAttachStateChangeListener);
    }
    

4.2 添加滑动监听

  • 涉及到一次滑动一页视频,那么肯定会有视频初始化和释放的功能。那么思考一下哪里来开始播放视频和在哪里释放视频?不要着急,要监听滑动到哪页,需要我们重写onScrollStateChanged()函数,这里面有三种状态:SCROLL_STATE_IDLE(空闲),SCROLL_STATE_DRAGGING(拖动),SCROLL_STATE_SETTLING(要移动到最后位置时)。
  • 我们需要的就是RecyclerView停止时的状态,我们就可以拿到这个View的Position,注意这里还有一个问题,当你通过这个position去拿Item会报错,这里涉及到RecyclerView的缓存机制,自己去脑补~~。打印Log,你会发现RecyclerView.getChildCount()一直为1或者会出现为2的情况。来实现一个接口然后通过接口把状态传递出去。
  • 自定义监听listener事件
    public interface OnPagerListener {/*** 初始化完成*/void onInitComplete();/*** 释放的监听* @param isNext                    是否下一个* @param position                  索引*/void onPageRelease(boolean isNext,int position);/**** 选中的监听以及判断是否滑动到底部* @param position                  索引* @param isBottom                  是否到了底部*/void onPageSelected(int position,boolean isBottom);
    }
    
  • 获取到RecyclerView空闲时选中的Item,重写LinearLayoutManager的onScrollStateChanged方法
    /*** 滑动状态的改变* 缓慢拖拽-> SCROLL_STATE_DRAGGING* 快速滚动-> SCROLL_STATE_SETTLING* 空闲状态-> SCROLL_STATE_IDLE* @param state                         状态*/
    @Override
    public void onScrollStateChanged(int state) {switch (state) {case RecyclerView.SCROLL_STATE_IDLE:View viewIdle = mPagerSnapHelper.findSnapView(this);int positionIdle = 0;if (viewIdle != null) {positionIdle = getPosition(viewIdle);}if (mOnViewPagerListener != null && getChildCount() == 1) {mOnViewPagerListener.onPageSelected(positionIdle,positionIdle == getItemCount() - 1);}break;case RecyclerView.SCROLL_STATE_DRAGGING:View viewDrag = mPagerSnapHelper.findSnapView(this);if (viewDrag != null) {int positionDrag = getPosition(viewDrag);}break;case RecyclerView.SCROLL_STATE_SETTLING:View viewSettling = mPagerSnapHelper.findSnapView(this);if (viewSettling != null) {int positionSettling = getPosition(viewSettling);}break;default:break;}
    }
    

4.3 监听页面是否滚动

  • 这里有两个方法scrollHorizontallyBy()和scrollVerticallyBy()可以拿到滑动偏移量,可以判断滑动方向。

    /*** 监听竖直方向的相对偏移量* @param dy                                y轴滚动值* @param recycler                          recycler* @param state                             state滚动状态* @return                                  int值*/
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {if (getChildCount() == 0 || dy == 0) {return 0;}this.mDrift = dy;return super.scrollVerticallyBy(dy, recycler, state);
    }/*** 监听水平方向的相对偏移量* @param dx                                x轴滚动值* @param recycler                          recycler* @param state                             state滚动状态* @return                                  int值*/
    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {if (getChildCount() == 0 || dx == 0) {return 0;}this.mDrift = dx;return super.scrollHorizontallyBy(dx, recycler, state);
    }
    

4.4 attach和Detached

  • 列表的选中监听好了,我们就看看什么时候释放视频的资源,第二步中的三种状态,去打印getChildCount()的日志,你会发现getChildCount()在SCROLL_STATE_DRAGGING会为1,SCROLL_STATE_SETTLING为2,SCROLL_STATE_IDLE有时为1,有时为2,还是RecyclerView的缓存机制O(∩∩)O,这里不会去赘述缓存机制,要做的是要知道在什么时候去做释放视频的操作,还要分清是释放上一页还是下一页。

    private RecyclerView.OnChildAttachStateChangeListener mChildAttachStateChangeListener =new RecyclerView.OnChildAttachStateChangeListener() {/*** 第一次进入界面的监听,可以做初始化方面的操作* @param view                      view*/@Overridepublic void onChildViewAttachedToWindow(@NonNull View view) {if (mOnViewPagerListener != null && getChildCount() == 1) {mOnViewPagerListener.onInitComplete();}}/*** 页面销毁的时候调用该方法,可以做销毁方面的操作* @param view                      view*/@Overridepublic void onChildViewDetachedFromWindow(@NonNull View view) {if (mDrift >= 0){if (mOnViewPagerListener != null) {mOnViewPagerListener.onPageRelease(true , getPosition(view));}}else {if (mOnViewPagerListener != null) {mOnViewPagerListener.onPageRelease(false , getPosition(view));}}}
    };
    
  • 哪里添加该listener监听事件,如下所示。这里注意需要在页面销毁的时候移除listener监听事件。
    /*** attach到window窗口时,该方法必须调用* @param recyclerView                          recyclerView*/
    @Override
    public void onAttachedToWindow(RecyclerView recyclerView) {//这里省略部分代码mRecyclerView.addOnChildAttachStateChangeListener(mChildAttachStateChangeListener);
    }/*** 销毁的时候调用该方法,需要移除监听事件* @param view                                  view* @param recycler                              recycler*/
    @Override
    public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {super.onDetachedFromWindow(view, recycler);if (mRecyclerView!=null){mRecyclerView.removeOnChildAttachStateChangeListener(mChildAttachStateChangeListener);}
    }
    

05.优化点详谈

5.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());//默认值75mFlingDistance.set(this, flingDistance);Field mMinimumVelocity = ViewPager.class.getDeclaredField("mMinimumVelocity");mMinimumVelocity.setAccessible(true);Object o1 = mMinimumVelocity.get(this);Log.d("setScrollFling",o1.toString());//默认值1200mMinimumVelocity.set(this,minimumVelocity);} catch (Exception e){e.printStackTrace();}
    }
    

5.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();
    }
    

5.3 自定义LayoutManager注意点

  • 网上有人已经写了一篇自定义LayoutManager实现抖音的效果的博客,我自己也很仔细看了这篇文章。不过我觉得有几个注意要点,因为要用到线上app,则一定要尽可能减少崩溃率……
  • 通过SnapHelper调用findSnapView方法,得到的view,一定要增加非空判断逻辑,否则很容易造成崩溃。
  • 在监听滚动位移scrollVerticallyBy的时候,注意要增加判断,就是getChildCount()如果为0时,则需要返回0。
  • 在onDetachedFromWindow调用的时候,可以把listener监听事件给remove掉。

5.4 视频播放逻辑优化

  • 从前台切到后台,当视频正在播放或者正在缓冲时,调用方法可以设置暂停视频。销毁页面,释放,内部的播放器被释放掉,同时如果在全屏、小窗口模式下都会退出。从后台切换到前台,当视频暂停时或者缓冲暂停时,调用该方法重新开启视频播放。具体视频播放代码设置如下,具体更加详细内容可以看我封装的视频播放器lib:

    @Override
    protected void onStop() {super.onStop();//从前台切到后台,当视频正在播放或者正在缓冲时,调用该方法暂停视频VideoPlayerManager.instance().suspendVideoPlayer();
    }@Override
    protected void onDestroy() {super.onDestroy();//销毁页面,释放,内部的播放器被释放掉,同时如果在全屏、小窗口模式下都会退出VideoPlayerManager.instance().releaseVideoPlayer();
    }@Override
    public void onBackPressed() {//处理返回键逻辑;如果是全屏,则退出全屏;如果是小窗口,则退出小窗口if (VideoPlayerManager.instance().onBackPressed()){return;}else {//销毁页面VideoPlayerManager.instance().releaseVideoPlayer();}super.onBackPressed();
    }@Override
    protected void onRestart() {super.onRestart();//从后台切换到前台,当视频暂停时或者缓冲暂停时,调用该方法重新开启视频播放VideoPlayerManager.instance().resumeVideoPlayer();
    }
    

5.5 视频逻辑充分解藕

  • 实际开发中,翻页肯定会涉及到视频的初始化和销毁的逻辑。首先要保证视频只有唯一一个播放,滑动到分页一半,总不可能让两个页面都播放视频吧,所以需要保证视频VideoPlayer是一个单利对象,这样就可以保证唯一性呢!接着,不管是在recyclerView还是ViewPager中,当页面处于不可见被销毁或者view被回收的阶段,这个时候需要把视频资源销毁,尽量视频播放功能封装起来,然后在页面不同状态调用方法即可。
  • 当然,实际app中,视频播放页面,还有一些点赞,评论,分享,查看作者等等很多其他功能。那么这些都是要请求接口的,还有滑动分页的功能,当滑动到最后某一页时候拉取下一个视频集合数据等业务逻辑。视频播放功能这块,因为功能比较复杂,因此封装一下比较好。尽量做到视频功能解藕!关于视频封装库,可以看我之前写的一个库,视频播放器。

5.6 翻页卡顿优化分析

  • 如果是使用recyclerView实现滑动翻页效果,那么为了提高使用体验效果。则可以注意:1.在onBindViewHolder中不要做耗时操作,2.视频滑动翻页的布局固定高度,避免重复计算高度RecyclerView.setHasFixedSize(true),3.关于分页拉取数据注意,建议一次拉下10条数据(这个也可以和服务端协定自定义数量),而不要滑动一页加载下一页的数据。

5.7 上拉很快翻页黑屏

  • 因为设置视频的背景颜色为黑色,我看了好多播放器初始化的时候,都是这样的。因为最简单的解决办法,就是给它加个封面,设置封面的背景即可。

其他介绍

参考博客

  • 自定义LayoutManager实现抖音的效果:https://www.jianshu.com/p/34a0ef2d806d
  • ViewPager不为人知的秘密:https://www.jianshu.com/p/80891d0185f7

01.关于博客汇总链接

  • 1.技术博客汇总
  • 2.开源项目汇总
  • 3.生活博客汇总
  • 4.喜马拉雅音频汇总
  • 5.其他汇总

02.关于我的博客

  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/yczbj/activities
  • 简书:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
  • 开源中国:https://my.oschina.net/zbj1618/blog
  • 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 邮箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
  • segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles
  • 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e

滑动翻页开源库:https://github.com/yangchong211/YCScrollPager

视频播放器:https://github.com/yangchong211/YCVideoPlayer

仿抖音上下滑动分页视频相关推荐

  1. Android仿抖音上下滑动切换视频

    Android仿抖音上下滑动切换视频 https://www.jianshu.com/p/af9c0e46725d  自从各大直播平台可以滑动切换直播间后,公司就出了一大波需求,还要配合各种收费,各种 ...

  2. iOS仿抖音上下滑动播放视频

    首先看下效果图 前言 上一篇文章仿写了抖音的左右滑动效果-iOS之仿抖音左右滑动效果,有兴趣的可以去GKNavigationBarViewController的demo中查看. 这篇文章主要是对抖音的 ...

  3. 仿抖音上下滑动播放视频 demo+爱心点赞特效

    DouyinDemo 项目地址:PangHaHa12138/DouyinDemo  简介:仿抖音上下滑动播放视频 demo+爱心点赞特效 更多:作者   提 Bug 标签: 仿抖音上下滑动播放视频 d ...

  4. 抖音只能上下滑动吗_iOS仿抖音—上下滑动播放视频

    iOS仿抖音短视频 图做的不太好,将就看吧 2018.12.12 添加了tabbar,数据写死到了本地(接口过一段时间就会访问没数据) dy_tabbar.gif 前言 上一篇文章仿写了抖音的左右滑动 ...

  5. 抖音只能上下滑动吗_仿抖音上下滑动播放视频

    太多朋友对短视频,上下滚动播放视频效果比较比较研究,今天看看这个案例. 讲下大概思路,使用Recycleview配合自定义LinearLayoutManager来实现这个功能,这里着重说下自定义Lin ...

  6. 微信小程序仿抖音上下滑动整屏切换视频

    微信小程序仿抖音上下滑动整屏切换视频 使用官网上面的扩展组件 官方使用的方式: 可结合自己业务修改: 使用官网上面的扩展组件 https://developers.weixin.qq.com/mini ...

  7. 仿抖音上下滑动列表播放短视频解决方案

    因为公司需求需要搞一个像抖音一样的上下滑动的播放列表,寻找了很多方案,最终觉得这个方案还是比较可行的. 1.播放器选择阿里云播放器 阿里云播放器对接文档地址 https://helpcdn.aliyu ...

  8. 仿抖音网页版自定义视频进度条

    先上一个完成后的效果图: 在将视频垂直居中布局后: 1.删除视频原有的控制组件,去掉controls属性,html如下: <video id="video" :src=&qu ...

  9. iOS多种刷新样式、音乐播放器、仿抖音视频、旅游App等源码

    iOS精选源码 企业级开源项目,模仿艺龙旅行App 3D立体相册,可以旋转的立方体 横竖屏切换工具,使用陀螺仪检测手机设备方向,锁屏状- Swift版Refresh(可以自定义多种样式)架构方面有所优 ...

最新文章

  1. hibernate 高级映射 --张国亮总结第一季
  2. 用typescript完成倒计时_「干货」将数十万行CoffeeScript代码迁移到TypeScript
  3. qdialog 返回值_QDialog exec()并获取结果值
  4. 如何在 Internet Explorer 中禁用和使用 ADODB.Stream 对象
  5. 1041: 谭浩强C语言(第三版)习题5.5
  6. 【音效处理】Reverb 混响算法简介
  7. book118免费下载文档方法
  8. 手机APP测试需要注意的问题
  9. 2019年南京大学计算机考研复试机试真题
  10. java eml_java读取eml文件 | 学步园
  11. 静态化freemarker,分布式文件系统minIO
  12. 基于端到端深度学习方法的语音唤醒(Keyword Spotting)模型和论文
  13. java jit技术_JVM之JIT
  14. 带上CSDN一起游国庆
  15. 学习日记-Adobe 卸载美工软件
  16. 华为认证考试怎么预约?这本指南书教你一次到位!
  17. 安装MeadCo ScriptX的ActiveX控件
  18. 淘宝顶部导航——待解决的问题——已解决
  19. 利用AT89C52定时器输出可调PWM
  20. ssm经济信息门户网站 毕业设计源码141634

热门文章

  1. OSChina 双十一乱弹 ——来自单身狗的哀鸣
  2. 【Python语言程序设计】20级实验参考合集
  3. SSLContext
  4. Java线程同步的几种方式
  5. 笨方法学Python笔记(9)
  6. Eclipse使用Maven插件的介绍
  7. 关于js拿shell的尝试
  8. android app 点击跳过,【惊奇软件】Android 自动跳过 v3.4.5
  9. qq群文件无法打开显示内容?
  10. 解决Mac电脑下Sublime Text3快捷键html:5+Tab没有反应