Imitation shake upside down paging video

Catalog Introduction

  • 01. First look at the requirements< /li>
  • 02. There are several ways to implement
    • 2.1 Use ViewPager
    • 2.2 Use RecyclerView
  • 03. Use ViewPager implementation
    • 3.1 Customize ViewPager
    • 3.2 ViewPager and Fragment
    • 3.3 Modify the sliding distance to turn pages
    • 3.4 Modify the sliding speed
  • 04. Realize with RecyclerView
    • 4.1 Custom LayoutManager
    • 4.2 Add sliding monitor
    • 4.3 Monitor whether the page scrolls
    • 4.4 attach and Detached
  • 05. More optimization points
    • 5.1 ViewPager changes the sliding rate
    • 5.2 PagerSnapHelper attention points
    • 5.3 Custom LayoutManager attention points
    • 5.4 Video playback logic optimization
    • 5.5 Video logic fully decoupling
    • 5.6 Page turning jam optimization analysis
    • 5.7 The page turning black screen soon after the pull-up

01. Let’s take a look at the requirements first

  • The video playback in the project requires the vertical orientation of Douyin The effect of sliding one page at a time. The sliding should be smooth and not stuck, and when the manual touch sliding exceeds 1/2, release it to slide the next page, and return to the original page without exceeding 1/2.
  • Drag the page with your finger to slide. As long as you don’t switch to another page, the video is playing. The page is switched, the previous video is destroyed, and the page starts to initialize playback.
  • When switching pages, the transition effect should be natural to avoid splash screens. For specific sliding effects, you can directly refer to Douyin…

02. There are several ways to implement< /h3>

2.1 Use ViewPager

  • Use ViewPager to achieve vertical Method to switch video analysis up and down
    • 1. In recent project requirements, it is useful to play video in ViewPager, which is the vertical method to slide up and down to switch the video. The video is a network video. Initialize the SurfaceView at the item location, and remove the SurfaceView according to the item location when it is destroyed.
    • 2. The above method is indeed achievable, but there are two problems. First, the life cycle of MediaPlayer is not easy to control and there is a memory leak problem. Second, when three consecutive items are all videos, a bug in the last frame of the previous video will appear when sliding back and forth.
    • 3. The user experience is not improved. Before the video player is initialized, the first frame of the video will be overlaid, but it is found that there is a situation where the first frame of the image does not match the first frame of the video. The solution will be given through the code.
  • The general idea of ​​implementation is this
    • 1. You need to customize a ViewPager that slides vertically. It is particularly important to pay attention to this.
    • 2. Sliding one page at a time, it is recommended to use ViewPager+FragmentStatePagerAdapter+Fragment, which will be described in detail later.
    • 3. Handle the logic of video initialization, playback and destruction logic in the fragment.
    • 4. As a page needs to create a fragment, pay attention to the performance and sliding smoothness need to be analyzed and discussed.
  • It is not recommended to use ViewPager
    • 1. The sliding effect of ViewPager fully meets the scene, and it supports UI binding such as Fragment and View. With some modifications to the layout and touch events, you can change the horizontal ViewPager to vertical.
    • 2. But no reuse is the most fatal problem. In the onLayout method, all child Views will be instantiated and lined up on the layout. When the number of items is large, it will be a great performance waste.
    • 3. Next is the issue of visibility judgment. Many people think that Fragment is visible when onResume, and Fragment in ViewPager is a counterexample, especially when multiple ViewPagers are nested, there will be multiple parent Fragments and multiple child Fragments in the onResume state, but only one of them Is visible. Unless you give up ViewPager’s preloading mechanism. When reporting important data such as page content exposure, many conditions need to be judged: onResumed, setUserVisibleHint, setOnPageChangeListener, etc.

2.2 Use RecyclerView

  • Using RecyclerView to realize the video analysis of switching up and down in the direction of the branch
    • 1. First of all, it is very simple to set the vertical sliding in RecyclerView. At the same time, the four-level cache of the item has also been processed. , And the effect of sliding is better than ViewPager.
    • 2. Sliding event processing is better than viewPager. Even if you nest the layout of pull-down refresh and pull-up loading on the outer layer, it will not affect the later event conflict handling. For details, you can see the demo case.
  • The general idea is this
    • 1. Customize a LinearLayoutManager, override the onScrollStateChanged method, and pay attention to get the sliding state.
    • 2. Swipe to switch one page at a time, you can use PagerSnapHelper to achieve it, which is very convenient and simple.
    • 3. In the adapter corresponding to the recyclerView, initialize the video operation in onCreateViewHolder, and destroy the video resource when onViewRecycled.
    • 4. Add a custom callback interface, define initialization, page destruction and other methods when scrolling the page, attch, and detach, and expose it to the developer.

03.Realize with ViewPager

3.1 Customize ViewPager

  • The code is as follows, and a lot of it is omitted here Code, you can see the code in the project for details.
    /** * 
     * @author Yang Chong* blog: https://github.com/yangchong211 * time: 2019/6/20 * desc: Customize ViewPager , Mainly to deal with extreme boundary conditions * revise: * 

    */ public class < span class="hljs-title">VerticalViewPager extends ViewPager {private boolean isVertical = false; private long mRecentTouchTime; public VerticalViewPager(@NonNull Context context) {super(context);} public VerticalViewP ager(@NonNull Context context, @Nullable AttributeSet attrs) {super(context, attrs);} private void init() {setPageTransformer(< span class="hljs-keyword">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 < span class="hljs-keyword">if (position <= 1) {page.setAlpha(1); // Counteract the default slide transition float xPosit ion = 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(< span class="hljs-number">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);}}}} /** * Exchange xaxle and ymovement distance of axis* @param event Get the package class MotionEvent */ private MotionEvent swapXY(MotionEvent event) {//Get Width and heightfloat width = getWidth(); float height = getHeight(); //Convert Ythe moving distance of the axis into Xthe moving distance of the axisfloat swappedX = (event.getY() / height) * width; //will X The moving distance of the axis is transformed into Ythe moving distance of the axisfloat swappedY = (event.getX() / width) * height; //Reset the location of event event.setLocation(swappedX, swapped Y); 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);}}} < /span>< /span>< /span>< /span>< /span>< /span>< /span> < /li>

3.2 ViewPager and Fragment

  • Use ViewPager+FragmentStatePagerAdapter+Fragment to process. The reason why you choose to use FragmentStatePagerAdapter is mainly because using FragmentStatePagerAdapter saves memory, but it takes time to create a new one after destruction. In general, if you are using the ViewPager to display a particularly large number of items, then it is recommended to use FragmentStatePagerAdapter. Regarding the in-depth analysis of PagerAdapter, please refer to this article: PagerAdapter in-depth analysis and practical optimization
  • The code in the activity is as follows
    private void initViewPager() {List
  • So how to deal with it in the fragment? Regarding the video player, you can see the library I packaged here, the video lib
    public class VideoFragment extends Fragment{ public VideoPlayer videoPlayer; private String url ; private int index; @Override public 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;} @O verride public void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState); Bundle arguments = getArguments(); if (arguments != null) {url = arguments.getString("url");}} @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, < span class="hljs-meta">@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("Initialization operation","------"+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.imageVie w());}} < /span>< /span>

3.3 Modifying the sliding distance to turn pages

  • The requirement requires manual touch and sliding when it exceeds 1/2. Release it to slide down One page, no more than 1/2 to return to the original page, the first thing is to rewrite the viewpager, you can only start from the source code. After analysis, the logic processing of the source code sliding is here, and the attribute of truncator represents the ratio value of judgment!
    • This method will redirect Page when the page is cut, such as sliding from the first page, but instead of sliding to the second page, it returns to the first page. That page will have Redirection function
    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; targetPa ge = 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;}  span> 
    • The method of determineTargetPage is to calculate which page to slide to next. This method is called in the MotionEvent.ACTION_UP event, let’s first talk about the meaning of the parameters:
      • currentPage: the page displayed by the current ViewPager
      • pageOffset: the page offset of the user sliding
      • velocity: sliding rate
      • deltaX: distance moved in the X direction
    • After debugging, the problem was found to be 0.4f and 0.6 f on this parameter. The analysis shows that: 0.6f represents the offset that the user can swipe to turn the page, so it is not difficult to understand why it is necessary to swipe half the screen or more.
  • You can also modify the Touch event
    • Controlling the ViewPager’s Touch event is basically omnipotent, after all, it starts from the root cause. You can make logical judgments in onTouchEvent and onInterceptTouchEvent. But it is more troublesome.

3.4 Modify sliding speed

  • When using viewPager to slide, if you use a finger to slide, you can implement it according to the distance of the finger slide, but if you use the setCurrentItem function to implement it, you will find that it flashes directly, There will be a refresh. If you want to use the setCurrentItem function to slide the viewpager, and you need to have an over-swiping animation, how do you do it?
  • Specifically, you can analyze the logic of the setCurrentItem source code, and then you will see the scrollToItem method. This is particularly important, mainly for the logic in the scrolling process. The main concern is also the smoothScrollTo function. In this function, you can see that the specific implementation of sliding is actually just one sentence, which is mScroller.startScroll(sx,sy,dx,dy,duration), you can see that it is the object of mScroller. Sliding. Then if you want to change its properties, you can do it through reflection.
  • The code is shown below. If it is a finger touch sliding, you can speed up the sliding rate a little bit. Of course, you can set the sliding duration yourself. By customizing the sliding time, you can control the speed of the sliding.
    @TargetApi(Build.VERSION_CODES.KITKAT) public void setAnimationDuration(final int during){ try {// viewPagerhorizontal motion painting event Field mField = ViewPager.class.getDeclaredField("mScroller"); mField.setAccessible(true); / / The animation effect is consistent with that of ViewPager Interpolator interpolator = new Interpolator() {@Override public 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; @Override public void startScroll(int x, int y, int dx, int dy, int duration) {// If you manually scroll,accelerate scrollingif (System.currentTimeMillis()-mRecentTouchTime> time) {duration = during; } else {duration /= 2;} super.startScroll(x, y, dx, dy , duration);} @Override public 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();}} < /span>< /span>< /span>

04. Realize with RecyclerView

4.1 Custom LayoutManager

  • Customize LayoutManager and inherit LinearLayoutManager, so that you can get a layout strategy that can be arranged horizontally or vertically. If you have been in contact with SnapHelper, you should know about the two subclasses of LinearSnapHelper and PagerSnapHelper. LinearSnapHelper can achieve the effect of centering the items in the list, and PagerSnapHelper can achieve the effect of scrolling one item at a time.
  • Rewrite the onChildViewAttachedToWindow method. In RecyclerView, this method is called when an Item is added. This method is equivalent to being called when the view is added to the window, which means it is executed before the draw method and can do some initialization-related operations.
    /** * This method must be called* @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);}   span>

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 */ @Override public void onChildViewAttachedToWindow(@NonNull View view) { if (mOnViewPagerListener != null && getChildCount() == 1) { mOnViewPagerListener.onInitComplete(); } } /** * 页面销毁的时候调用该方法,可以做销毁方面的操作 * @param view view */ @Override public 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()); //默认值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(); } } 

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
  • 邮箱:[email protected]
  • 阿里云博客: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

Leave a Comment

Your email address will not be published.