仿抖音上下滑动分页视频

article/2025/9/21 15:28:55

如果要是不想看这么代码的话,我整理了一下项目,下载到本地直接进行运行也可以;

代码下载地址

目录介绍

  • 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 上拉很快翻页黑屏

00.先看一下效果图

01.先来看一下需求

  • 项目中的视频播放,要求实现抖音那种竖直方向一次滑动一页的效果。滑动要流畅不卡顿,并且手动触摸滑动超过1/2的时候松开可以滑动下一页,没有超过1/2返回原页。
  • 手指拖动页面滑动,只要没有切换到其他的页面,视频都是在播放的。切换了页面,上一个视频销毁,该页面则开始初始化播放。
  • 切换页面的时候过渡效果要自然,避免出现闪屏。具体的滑动效果,可以直接参考抖音……
  • 开源库地址:github.com/yangchong21…

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会有重定向的功能

 

  • determineTargetPage这个方法就是计算接下来要滑到哪一页。这个方法调用是在MotionEvent.ACTION_UP这个事件下,先说下参数意思:
    • deltaX: X方向移动的距离
    • velocity: 滑动速率
    • pageOffset:用户滑动的页面偏移量
    • currentPage:当前ViewPager显示的页面
    • 进行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实现抖音的效果:www.jianshu.com/p/34a0ef2d8…
  • ViewPager不为人知的秘密:www.jianshu.com/p/80891d018…

 

 

 

 

 


http://chatgpt.dhexx.cn/article/mO164T0g.shtml

相关文章

java抖音字符视频_抖音流行的字符视频如何实现

前几天&#xff0c;有个朋友给我分享了一个抖音短视频链接&#xff0c;是以代古拉k跳舞视频为原型的字符视频&#xff0c;朋友问我&#xff1a;“这个你知道怎么做吗&#xff1f;”。 我们都知道视频是由一帧一帧的静态图片组合而成的&#xff0c;所以当我们把原视频的每一帧变…

抖音上传视频显示转成mp4怎么设置_详细的mp4转换成mp3格式的方法,不看后悔系列!...

详细的mp4转换成mp3格式的方法&#xff0c;不看后悔系列&#xff01; 安利安利安利安利 话说&#xff0c;视频格式转换你们会&#xff0c;音频格式转换你们也会&#xff0c;你们就没有不会的吗&#xff1f; 对了&#xff0c;视频转音频你们会吗&#xff1f;知道怎么将MP4转成MP…

抖音短视频 产品需求文档

抖音短视频 产品需求文档 一、 文档综述1.1 文档属性1.2 产品简介1.3 产品用户1.4 需求总结 二、 产品结构2.1 产品功能结构图2.2 产品信息架构图 三、 全局说明3.1 登录页面3.2 网络环境3.3 键盘输入3.4 评论框3.5 分享框 四、 产品流程图4.1 前端流程4.1.1 登录注册流程4.1.2…

html视频怎么转换成图片,WPS演示如何将图片转换成视频?

将图片转换成视频你是这么做的呢&#xff1f;WPS演示如何将图片转换成视频&#xff1f;现在的人们对于会动的事物都充满着好奇&#xff0c;就连好看的图片也需要转换成视频了。利用WPS演示&#xff0c;将图片转化成视频。本质上就是制作一个PPT文档&#xff0c;然后将文档保存在…

用Python全自动下载抖音视频!

为什么写这篇文章&#xff0c;主要也是因为看了网易云课堂的一篇软广。 「用Python在抖音扒了这些高颜值女神后&#xff0c;突然成了人生赢家」&#xff0c;文中简述了一名工程师利用PythonADB鹅厂的AI&#xff0c;一晚上关注了一千多个漂亮小姐姐。 充分体现了厂子里的大学生和…

Javacv 音视频小工具 - 下载抖音视频

一、前言 大家好&#xff0c;俗话说的好&#xff0c;学习新的知识后要学以致用&#xff0c;在学习音视频的过程中&#xff0c;你有没有疑问&#xff0c;不知道音视频可以用来做什么。下面举几个例子&#xff0c;比较耳熟能详&#xff0c;被吹到风口的一些场景有&#xff1a;AI…

java抖音字符视频_代码生成抖音文字视频

天天刷抖音,发现一些 好玩的 快闪文字视频, 自己尝试着做了几个,用了很多 的 工具做了一下,发下需要收费, 于是,用Python整了个 工具来合成 文字视频,效果如下:PFinal社区,快闪文字视频https://www.zhihu.com/video/1127196631308853248 目录结构如下: 工作流程如下: data.txt …

抖音上传视频显示转成mp4怎么设置_如何剪辑抖音短视频?视频剪辑软件推荐及操作流程(一)...

随着抖音的爆火,大家都想在这个风口分一杯羹,但是呢,很多人都跟小编一样,既不喜欢露脸也没有能秀的才艺,所以才一直迟迟没有行动。 不过我在刷抖音的时候,也发现同样没有露脸没有才艺的人能上热门爆红,因为他们在视频拍摄和剪辑上很出色。 私下也有很多同学发来一段视频…

【Python 字符视频】Python 实现将抖音视频转换成字符视频

以前就在抖音上看到过字符视频&#xff0c;直到昨天才突然想自己动手做一个&#xff0c;然后就利用各种博客&#xff0c;自己总结 兼 借鉴&#xff0c;终于完成了字符视频的制作 一、思路&#xff1a; 众所周知&#xff0c;视频是一帧一帧的图片组成的&#xff0c;所以我想的就…

抖音个人主页背景放视频?详解来了

大家都知道抖音个人主页可以设置图片的&#xff0c;那么如何设置成视频形式的呢&#xff1f;一起来看下吧 抖音背景视频设置门槛 抖音品牌号才能设置&#xff0c;普通抖音账户无法设置。 品牌号 品牌号必须是抖音年框客户或是一年品牌消耗100万以上&#xff0c;抖音才会赠送…

html怎么做成锁屏壁纸,抖音怎么把视频做成壁纸 抖音短视频怎么弄成锁屏壁纸-站长资讯中心...

抖音怎么把视频做成壁纸&#xff1f;最近&#xff0c;很多小伙伴都喜欢将抖音的短视频设置成动态壁纸&#xff0c;不过还有些小伙伴们不知道要怎么设置&#xff0c;那么快随小编一起来看看下面的教程吧1 抖音怎么把视频做成壁纸&#xff1f; 1、打开软件后随便刷视频&#xff0…

怎么把抖音的视频转成gif格式?视频转gif的具体方法

当我们遇到有趣的视频想做成表情包时&#xff0c;都会在想这些图片是怎么制作的呢?相信有很多小伙伴都有同样的疑问&#xff0c;有哪些视频转gif工具可以把视频改成gif图片呢?GIF中文网的在线视频转gif功能用起来既简单又方便&#xff0c;支持手机、电脑两种操作模式&#xf…

怎么让抖音视频当做铃声android,把抖音短视频做成手机铃声,原来这么简单!...

原标题&#xff1a;把抖音短视频做成手机铃声&#xff0c;原来这么简单&#xff01; “一日不抖&#xff0c;如隔三秋” 相信十个人里面 有九个都中了抖音的毒 刷起来完全控制不住自己 最近大火的抖音短视频炒火了不少民谣音乐人及一些翻唱歌曲的素人&#xff0c;不知道大家有没…

如何把照片做成视频?抖音爆款的图片视频切换教程,快速上手!

现在都流行用照片随手记录生活。而把照片做成视频&#xff0c;又是现在刷爆抖音、朋友圈的形式。不仅有酷炫的图片切换效果&#xff0c;还带有动听的背景音乐&#xff0c;这样精美又吸睛的照片视频&#xff0c;肯定能让你获得超多赞。今天就教大家用数码大师快速把照片做成视频…

抖音怎么把照片做成视频?超火的照片切换教程,3分钟就能搞定

抖音怎么把照片做成视频?怎么上传照片视频到抖音?抖音上很吸赞的照片切换效果怎么做? 今天教大家3分钟做出一个抖音上比较热门的照片视频,用到的工具是数码大师。 第一步:把照片一次性添加进来 打开数码大师后,选择“视频相册”,点击“添加相片”导入照片。点击“修改…

Adobe Flash cs4 下载安装

Adobe Flash cs4 下载安装 Adobe Flash cs4 下载Adobe Flash cs4 安装Flash Cs4安装之后打不开&#xff08;启动界面一闪而过&#xff09; Adobe Flash cs4 下载 Adobe Flash cs4 下载地址(百度网盘): https://pan.baidu.com/s/1KXTvIlQoa4cDGG16FdP-Vg Adobe Flash cs4 下载…

pdf数据查找网站

PDF Drive - Search and download PDF files for free.https://www.pdfdrive.com/ Library Genesis Proxy Mirror Links: Libgen Io, Libgen rs, Libgen nl, Libgen ishttps://libgen.onl/library-genesis/ Zlibrary、sci-hub、文献、书籍 | 药研导航 (drugx.cn)https://drug…

网站收录查询,常用的2种网站收录查询方法

在网站优化过程中&#xff0c;SEO人员通常都会非常关心网站的收录、索引情况&#xff0c;因为网站建立索引之后&#xff0c;才具有排名的条件。如何查询网站收录情况&#xff1f;今天我们聊聊常用的2种网站收录查询方法。第一种方法&#xff1a;通过site指令在搜索框查询 众多S…

r语言 网站数据查找

工作需要爬一个网站的数据&#xff0c;可是找的真的好累&#xff0c;不想重复劳作了&#xff0c;想起来原来学过的r语言有网站数据查找&#xff0c;就试一试。 library(XML) url<-"https://www.izaiwen.cn/" tbls<-readHTMLTable(url) sapply(tbls,nrow) libr…

如何通过数据进行网站分析

网站的监测指标有很多&#xff0c;一般的统计产品都包括大约20—30个指标。这些指标可以分成五个类别:用户规模、用户粘性、用户来源、网站受众属性、网站内容属性&#xff1b; 用户规模主要是通过PV、UV和独立IP三个指标衡量&#xff1b; 用户粘性主要通过回访率、访问频率和…