仿抖音上下滑動分頁視頻

轉自

http://m.itdecent.cn/p/53e4a1c0bd62

仿抖音上下滑動分頁視頻

目錄介紹

01.先來看一下需求

02.有幾種實現(xiàn)方式

2.1 使用ViewPager

2.2 使用RecyclerView

03.用ViewPager實現(xiàn)

3.1 自定義ViewPager

3.2 ViewPager和Fragment

3.3 修改滑動距離翻頁

3.4 修改滑動速度

04.用RecyclerView實現(xiàn)

4.1 自定義LayoutManager

4.2 添加滑動監(jiān)聽

4.3 監(jiān)聽頁面是否滾動

4.4 attach和Detached

05.優(yōu)化點詳談

5.1 ViewPager改變滑動速率

5.2 PagerSnapHelper注意點

5.3 自定義LayoutManager注意點

5.4 視頻播放邏輯優(yōu)化

5.5 視頻邏輯充分解藕

5.6 翻頁卡頓優(yōu)化分析

5.7 上拉很快翻頁黑屏

00.先看一下效果圖

01.先來看一下需求

項目中的視頻播放,要求實現(xiàn)抖音那種豎直方向一次滑動一頁的效果?;瑒右鲿巢豢D,并且手動觸摸滑動超過1/2的時候松開可以滑動下一頁,沒有超過1/2返回原頁。

手指拖動頁面滑動,只要沒有切換到其他的頁面,視頻都是在播放的。切換了頁面,上一個視頻銷毀,該頁面則開始初始化播放。

切換頁面的時候過渡效果要自然,避免出現(xiàn)閃屏。具體的滑動效果,可以直接參考抖音……

開源庫地址:github.com/yangchong21…

02.有幾種實現(xiàn)方式

2.1 使用ViewPager

使用ViewPager實現(xiàn)豎直方法上下切換視頻分析

1.最近項目需求中有用到需要在ViewPager中播放視頻,就是豎直方法上下滑動切換視頻,視頻是網(wǎng)絡視頻,最開始的實現(xiàn)思路是ViewPager中根據(jù)當前item位置去初始化SurfaceView,同時銷毀時根據(jù)item的位置移除SurfaceView。

2.上面那種方式確實是可以實現(xiàn)的,但是存在2個問題,第一,MediaPlayer的生命周期不容易控制并且存在內存泄漏問題。第二,連續(xù)三個item都是視頻時,來回滑動的過程中發(fā)現(xiàn)會出現(xiàn)上個視頻的最后一幀畫面的bug。

3.未提升用戶體驗,視頻播放器初始化完成前上面會覆蓋有該視頻的第一幀圖片,但是發(fā)現(xiàn)存在第一幀圖片與視頻第一幀信息不符的情況,后面會通過代碼給出解決方案。

大概的實現(xiàn)思路是這樣

1.需要自定義一個豎直方向滑動的ViewPager,注意這個特別重要。

2.一次滑動一頁,建議采用ViewPager+FragmentStatePagerAdapter+Fragment方式來做,后面會詳細說。

3.在fragment中處理視頻的初始化,播放和銷毀邏輯等邏輯。

4.由于一個頁面需要創(chuàng)建一個fragment,注意性能和滑動流暢度這塊需要分析和探討。

不太建議使用ViewPager

1.ViewPager 自帶的滑動效果完全滿足場景,而且支持Fragment和View等UI綁定,只要對布局和觸摸事件部分作一些修改,就可以把橫向的 ViewPager 改成豎向。

2.但是沒有復用是個最致命的問題。在onLayout方法中,所有子View會實例化并一字排開在布局上。當Item數(shù)量很大時,將會是很大的性能浪費。

3.其次是可見性判斷的問題。很多人會以為 Fragment 在 onResume 的時候就是可見的,而 ViewPager 中的 Fragment 就是個反例,尤其是多個 ViewPager 嵌套時,會同時有多個父 Fragment 多個子 Fragment 處于 onResume 的狀態(tài),卻只有其中一個是可見的。除非放棄 ViewPager 的預加載機制。在頁面內容曝光等重要的數(shù)據(jù)上報時,就需要判斷很多條件:onResumed 、 setUserVisibleHint 、 setOnPageChangeListener 等。

2.2 使用RecyclerView

使用RecyclerView實現(xiàn)樹枝方向上下切換視頻分析

1.首先RecyclerView它設置豎直方向滑動是十分簡單的,同時關于item的四級緩存也做好了處理,而且滑動的效果相比ViewPager要好一些。

2.滑動事件處理比viewPager好,即使你外層嵌套了下拉刷新上拉加載的布局,也不影響后期事件沖突處理,詳細可以看demo案例。

大概的實現(xiàn)思路是這樣

1.自定義一個LinearLayoutManager,重寫onScrollStateChanged方法,注意是拿到滑動狀態(tài)。

2.一次滑動切換一個頁面,可以使用PagerSnapHelper來實現(xiàn),十分方便簡單。

3.在recyclerView對應的adapter中,在onCreateViewHolder初始化視頻操作,同時當onViewRecycled時,銷毀視頻資源。

4.添加自定義回調接口,在滾動頁面和attch,detach的時候,定義初始化,頁面銷毀等方法,暴露給開發(fā)者。

03.用ViewPager實現(xiàn)

3.1 自定義ViewPager

代碼如下所示,這里省略了不少的代碼,具體可以看項目中的代碼。

/**

* <pre>

*? ? @author 楊充

*? ? blog? : https://github.com/yangchong211

*? ? time? : 2019/6/20

*? ? desc? : 自定義ViewPager,主要是處理邊界極端情況

*? ? revise:

* </pre>

*/publicclassVerticalViewPagerextendsViewPager{privatebooleanisVertical=false;privatelongmRecentTouchTime;publicVerticalViewPager(@NonNullContextcontext){super(context);}publicVerticalViewPager(@NonNullContextcontext,@NullableAttributeSetattrs){super(context,attrs);}privatevoidinit(){setPageTransformer(true,newHorizontalVerticalPageTransformer());setOverScrollMode(OVER_SCROLL_NEVER);}publicbooleanisVertical(){returnisVertical;}publicvoidsetVertical(booleanvertical){isVertical=vertical;init();}privateclassHorizontalVerticalPageTransformerimplementsPageTransformer{privatestaticfinalfloatMIN_SCALE=0.25f;@OverridepublicvoidtransformPage(@NonNullViewpage,floatposition){if(isVertical){if(position<-1){page.setAlpha(0);}elseif(position<=1){page.setAlpha(1);// Counteract the default slide transitionfloatxPosition=page.getWidth()*-position;page.setTranslationX(xPosition);//set Y position to swipe in from topfloatyPosition=position*page.getHeight();page.setTranslationY(yPosition);}else{page.setAlpha(0);}}else{intpageWidth=page.getWidth();if(position<-1){// [-Infinity,-1)// This page is way off-screen to the left.page.setAlpha(0);}elseif(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);}elseif(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)floatscaleFactor=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

? ? */privateMotionEventswapXY(MotionEventevent){//獲取寬高floatwidth=getWidth();floatheight=getHeight();//將Y軸的移動距離轉變成X軸的移動距離floatswappedX=(event.getY()/height)*width;//將X軸的移動距離轉變成Y軸的移動距離floatswappedY=(event.getX()/width)*height;//重設event的位置event.setLocation(swappedX,swappedY);returnevent;}@OverridepublicbooleanonInterceptTouchEvent(MotionEventev){mRecentTouchTime=System.currentTimeMillis();if(getCurrentItem()==0&&getChildCount()==0){returnfalse;}if(isVertical){booleanintercepted=super.onInterceptTouchEvent(swapXY(ev));swapXY(ev);// return touch coordinates to original reference frame for any child viewsreturnintercepted;}else{returnsuper.onInterceptTouchEvent(ev);}}@OverridepublicbooleanonTouchEvent(MotionEventev){if(getCurrentItem()==0&&getChildCount()==0){returnfalse;}if(isVertical){returnsuper.onTouchEvent(swapXY(ev));}else{returnsuper.onTouchEvent(ev);}}}

3.2 ViewPager和Fragment

采用了ViewPager+FragmentStatePagerAdapter+Fragment來處理。為何選擇使用FragmentStatePagerAdapter,主要是因為使用 FragmentStatePagerAdapter更省內存,但是銷毀后新建也是需要時間的。一般情況下,如果你是用于ViewPager展示數(shù)量特別多的條目時,那么建議使用FragmentStatePagerAdapter。關于PagerAdapter的深度解析,可以我這篇文章:PagerAdapter深度解析和實踐優(yōu)化

在activity中的代碼如下所示

privatevoidinitViewPager(){List<Video>list=newArrayList<>();ArrayList<Fragment>fragments=newArrayList<>();for(inta=0;a<DataProvider.VideoPlayerList.length;a++){Video video=newVideo(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=newMyPagerAdapter(fragments,supportFragmentManager);vp.setAdapter(myPagerAdapter);}classMyPagerAdapterextends FragmentStatePagerAdapter{privateArrayList<Fragment>list;publicMyPagerAdapter(ArrayList<Fragment>list,FragmentManager fm){super(fm);this.list=list;}@OverridepublicFragmentgetItem(inti){returnlist.get(i);}@OverridepublicintgetCount(){returnlist!=null?list.size():0;}}

那么在fragment中如何處理呢?關于視頻播放器,這里可以看我封裝的庫,視頻lib

publicclassVideoFragmentextendsFragment{publicVideoPlayer videoPlayer;privateString url;privateint index;@OverridepublicvoidonStop(){super.onStop();VideoPlayerManager.instance().releaseVideoPlayer();}publicstaticFragmentnewInstant(String url){VideoFragment videoFragment=newVideoFragment();Bundle bundle=newBundle();bundle.putString("url",url);videoFragment.setArguments(bundle);returnvideoFragment;}@OverridepublicvoidonCreate(@Nullable Bundle savedInstanceState){super.onCreate(savedInstanceState);Bundle arguments=getArguments();if(arguments!=null){url=arguments.getString("url");}}@Nullable? ? @OverridepublicViewonCreateView(@NonNull LayoutInflater inflater,@Nullable ViewGroup container,@Nullable Bundle savedInstanceState){View view=inflater.inflate(R.layout.fragment_video,container,false);returnview;}@OverridepublicvoidonViewCreated(@NonNull View view,@Nullable Bundle savedInstanceState){super.onViewCreated(view,savedInstanceState);videoPlayer=view.findViewById(R.id.video_player);}@OverridepublicvoidonActivityCreated(@Nullable Bundle savedInstanceState){super.onActivityCreated(savedInstanceState);Log.d("初始化操作","------"+index++);VideoPlayerController controller=newVideoPlayerController(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,只能從源碼下手。經(jīng)過分析,源碼滑動的邏輯處理在此處,truncator的屬性代表判斷的比例值!

這個方法會在切頁的時候重定向Page,比如從第一個頁面滑動,結果沒有滑動到第二個頁面,而是又返回到第一個頁面,那個這個page會有重定向的功能

privateintdetermineTargetPage(intcurrentPage,floatpageOffset,intvelocity,intdeltaX){inttargetPage;if(Math.abs(deltaX)>this.mFlingDistance&&Math.abs(velocity)>this.mMinimumVelocity){targetPage=velocity>0?currentPage:currentPage+1;}else{floattruncator=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));}returntargetPage;}

determineTargetPage這個方法就是計算接下來要滑到哪一頁。這個方法調用是在MotionEvent.ACTION_UP這個事件下,先說下參數(shù)意思:

currentPage:當前ViewPager顯示的頁面

pageOffset:用戶滑動的頁面偏移量

velocity: 滑動速率

deltaX: X方向移動的距離

進行debug調試之后,發(fā)現(xiàn)問題就在0.4f和0.6f這個參數(shù)上。分析得出:0.6f表示用戶滑動能夠翻頁的偏移量,所以不難理解,為啥要滑動半屏或者以上了。

也可以修改Touch事件

控制ViewPager的Touch事件,這個基本是萬能的,畢竟是從根源上入手的。你可以在onTouchEvent和onInterceptTouchEvent中做邏輯的判斷。但是比較麻煩。

3.4 修改滑動速度

使用viewPager進行滑動時,如果通過手指滑動來進行的話,可以根據(jù)手指滑動的距離來實現(xiàn),但是如果通過setCurrentItem函數(shù)來實現(xiàn)的話,則會發(fā)現(xiàn)直接閃過去的,會出現(xiàn)一下刷屏。想要通過使用setCurrentItem函數(shù)來進行viewpager的滑動,并且需要有過度滑動的動畫,那么,該如何做呢?

具體可以分析setCurrentItem源碼的邏輯,然后會看到scrollToItem方法,這個特別重要,主要是處理滾動過程中的邏輯。最主要關心的也是smoothScrollTo函數(shù),這個函數(shù)中,可以看到具體執(zhí)行滑動的其實就一句話,就是mScroller.startScroll(sx,sy,dx,dy,duration),則可以看到,是mScroller這個對象進行滑動的。那么想要改變它的屬性,則可以通過反射來實現(xiàn)。

代碼如下所示,如果是手指觸摸滑動,則可以加快一點滑動速率,當然滑動持續(xù)時間你可以自己設置。通過自己自定義滑動的時間,就可以控制滑動的速度。

@TargetApi(Build.VERSION_CODES.KITKAT)publicvoidsetAnimationDuration(finalintduring){try{// viewPager平移動畫事件Field mField=ViewPager.class.getDeclaredField("mScroller");mField.setAccessible(true);// 動畫效果與ViewPager的一致Interpolator interpolator=newInterpolator(){@OverridepublicfloatgetInterpolation(floatt){t-=1.0f;returnt*t*t*t*t+1.0f;}};Scroller mScroller=newScroller(getContext(),interpolator){finalinttime=2000;@OverridepublicvoidstartScroll(intx,inty,intdx,intdy,intduration){// 如果手工滾動,則加速滾動if(System.currentTimeMillis()-mRecentTouchTime>time){duration=during;}else{duration/=2;}super.startScroll(x,y,dx,dy,duration);}@OverridepublicvoidstartScroll(intx,inty,intdx,intdy){super.startScroll(x,y,dx,dy,during);}};mField.set(this,mScroller);}catch(NoSuchFieldException|IllegalAccessException|IllegalArgumentException e){e.printStackTrace();}}

04.用RecyclerView實現(xiàn)

4.1 自定義LayoutManager

自定義LayoutManager,并且繼承LinearLayoutManager,這樣就得到一個可以水平排向或者豎向排向的布局策略。如果你接觸過SnapHelper應該了解一下LinearSnapHelper和PagerSnapHelper這兩個子類類,LinearSnapHelper可以實現(xiàn)讓列表的Item居中顯示的效果,PagerSnapHelper就可以做到一次滾動一個item顯示的效果。

重寫onChildViewAttachedToWindow方法,在RecyclerView中,當Item添加進來了調用這個方法。這個方法相當于是把view添加到window時候調用的,也就是說它比draw方法先執(zhí)行,可以做一些初始化相關的操作。

/**

* 該方法必須調用

* @param recyclerView? ? ? ? ? ? ? ? ? ? ? ? ? recyclerView

*/@OverridepublicvoidonAttachedToWindow(RecyclerViewrecyclerView){if(recyclerView==null){thrownewIllegalArgumentException("The attach RecycleView must not null!!");}super.onAttachedToWindow(recyclerView);this.mRecyclerView=recyclerView;if(mPagerSnapHelper==null){init();}mPagerSnapHelper.attachToRecyclerView(mRecyclerView);mRecyclerView.addOnChildAttachStateChangeListener(mChildAttachStateChangeListener);}

4.2 添加滑動監(jiān)聽

涉及到一次滑動一頁視頻,那么肯定會有視頻初始化和釋放的功能。那么思考一下哪里來開始播放視頻和在哪里釋放視頻?不要著急,要監(jiān)聽滑動到哪頁,需要我們重寫onScrollStateChanged()函數(shù),這里面有三種狀態(tài):SCROLL_STATE_IDLE(空閑),SCROLL_STATE_DRAGGING(拖動),SCROLL_STATE_SETTLING(要移動到最后位置時)。

我們需要的就是RecyclerView停止時的狀態(tài),我們就可以拿到這個View的Position,注意這里還有一個問題,當你通過這個position去拿Item會報錯,這里涉及到RecyclerView的緩存機制,自己去腦補~~。打印Log,你會發(fā)現(xiàn)RecyclerView.getChildCount()一直為1或者會出現(xiàn)為2的情況。來實現(xiàn)一個接口然后通過接口把狀態(tài)傳遞出去。

自定義監(jiān)聽listener事件

publicinterfaceOnPagerListener{/**

? ? * 初始化完成

? ? */voidonInitComplete();/**

? ? * 釋放的監(jiān)聽

? ? * @param isNext? ? ? ? ? ? ? ? ? ? 是否下一個

? ? * @param position? ? ? ? ? ? ? ? ? 索引

? ? */voidonPageRelease(booleanisNext,intposition);/***

? ? * 選中的監(jiān)聽以及判斷是否滑動到底部

? ? * @param position? ? ? ? ? ? ? ? ? 索引

? ? * @param isBottom? ? ? ? ? ? ? ? ? 是否到了底部

? ? */voidonPageSelected(intposition,booleanisBottom);}

獲取到RecyclerView空閑時選中的Item,重寫LinearLayoutManager的onScrollStateChanged方法

/**

* 滑動狀態(tài)的改變

* 緩慢拖拽-> SCROLL_STATE_DRAGGING

* 快速滾動-> SCROLL_STATE_SETTLING

* 空閑狀態(tài)-> SCROLL_STATE_IDLE

* @param state? ? ? ? ? ? ? ? ? ? ? ? 狀態(tài)

*/@OverridepublicvoidonScrollStateChanged(int state){switch(state){caseRecyclerView.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;caseRecyclerView.SCROLL_STATE_DRAGGING:View viewDrag=mPagerSnapHelper.findSnapView(this);if(viewDrag!=null){int positionDrag=getPosition(viewDrag);}break;caseRecyclerView.SCROLL_STATE_SETTLING:View viewSettling=mPagerSnapHelper.findSnapView(this);if(viewSettling!=null){int positionSettling=getPosition(viewSettling);}break;default:break;}}

4.3 監(jiān)聽頁面是否滾動

這里有兩個方法scrollHorizontallyBy()和scrollVerticallyBy()可以拿到滑動偏移量,可以判斷滑動方向。

/**

* 監(jiān)聽豎直方向的相對偏移量

* @param dy? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? y軸滾動值

* @param recycler? ? ? ? ? ? ? ? ? ? ? ? ? recycler

* @param state? ? ? ? ? ? ? ? ? ? ? ? ? ? state滾動狀態(tài)

* @return? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int值

*/@OverridepublicintscrollVerticallyBy(intdy,RecyclerView.Recyclerrecycler,RecyclerView.Statestate){if(getChildCount()==0||dy==0){return0;}this.mDrift=dy;returnsuper.scrollVerticallyBy(dy,recycler,state);}/**

* 監(jiān)聽水平方向的相對偏移量

* @param dx? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? x軸滾動值

* @param recycler? ? ? ? ? ? ? ? ? ? ? ? ? recycler

* @param state? ? ? ? ? ? ? ? ? ? ? ? ? ? state滾動狀態(tài)

* @return? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int值

*/@OverridepublicintscrollHorizontallyBy(intdx,RecyclerView.Recyclerrecycler,RecyclerView.Statestate){if(getChildCount()==0||dx==0){return0;}this.mDrift=dx;returnsuper.scrollHorizontallyBy(dx,recycler,state);}

4.4 attach和Detached

列表的選中監(jiān)聽好了,我們就看看什么時候釋放視頻的資源,第二步中的三種狀態(tài),去打印getChildCount()的日志,你會發(fā)現(xiàn)getChildCount()在SCROLL_STATE_DRAGGING會為1,SCROLL_STATE_SETTLING為2,SCROLL_STATE_IDLE有時為1,有時為2,還是RecyclerView的緩存機制O(∩∩)O,這里不會去贅述緩存機制,要做的是要知道在什么時候去做釋放視頻的操作,還要分清是釋放上一頁還是下一頁。

privateRecyclerView.OnChildAttachStateChangeListenermChildAttachStateChangeListener=newRecyclerView.OnChildAttachStateChangeListener(){/**

? ? * 第一次進入界面的監(jiān)聽,可以做初始化方面的操作

? ? * @param view? ? ? ? ? ? ? ? ? ? ? view

? ? */@OverridepublicvoidonChildViewAttachedToWindow(@NonNullViewview){if(mOnViewPagerListener!=null&&getChildCount()==1){mOnViewPagerListener.onInitComplete();}}/**

? ? * 頁面銷毀的時候調用該方法,可以做銷毀方面的操作

? ? * @param view? ? ? ? ? ? ? ? ? ? ? view

? ? */@OverridepublicvoidonChildViewDetachedFromWindow(@NonNullViewview){if(mDrift>=0){if(mOnViewPagerListener!=null){mOnViewPagerListener.onPageRelease(true,getPosition(view));}}else{if(mOnViewPagerListener!=null){mOnViewPagerListener.onPageRelease(false,getPosition(view));}}}};

哪里添加該listener監(jiān)聽事件,如下所示。這里注意需要在頁面銷毀的時候移除listener監(jiān)聽事件。

/**

* attach到window窗口時,該方法必須調用

* @param recyclerView? ? ? ? ? ? ? ? ? ? ? ? ? recyclerView

*/@OverridepublicvoidonAttachedToWindow(RecyclerViewrecyclerView){//這里省略部分代碼mRecyclerView.addOnChildAttachStateChangeListener(mChildAttachStateChangeListener);}/**

* 銷毀的時候調用該方法,需要移除監(jiān)聽事件

* @param view? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? view

* @param recycler? ? ? ? ? ? ? ? ? ? ? ? ? ? ? recycler

*/@OverridepublicvoidonDetachedFromWindow(RecyclerViewview,RecyclerView.Recyclerrecycler){super.onDetachedFromWindow(view,recycler);if(mRecyclerView!=null){mRecyclerView.removeOnChildAttachStateChangeListener(mChildAttachStateChangeListener);}}

05.優(yōu)化點詳談

5.1 ViewPager改變滑動速率

可以通過反射修改屬性,注意,使用反射的時候,建議手動try-catch,避免異常導致崩潰。代碼如下所示:

/**

* 修改滑動靈敏度

* @param flingDistance? ? ? ? ? ? ? ? ? ? 滑動慣性,默認是75

* @param minimumVelocity? ? ? ? ? ? ? ? ? 最小滑動值,默認是1200

*/publicvoidsetScrollFling(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的監(jiān)聽接口。然后通過setupCallbacks()設置監(jiān)聽器,如果當前RecyclerView已經(jīng)設置了OnFlingListener,會拋出一個狀態(tài)異常。那么這個如何復現(xiàn)了,很容易,你初始化多次就可以看到這個bug。

建議手動捕獲一下該異常,代碼設置如下所示。源碼中判斷了,如果onFlingListener已經(jīng)存在的話,再次設置就直接拋出異常,那么這里可以增強一下邏輯判斷,ok,那么問題便解決呢!

try{//attachToRecyclerView源碼上的方法可能會拋出IllegalStateException異常,這里手動捕獲一下RecyclerView.OnFlingListeneronFlingListener=mRecyclerView.getOnFlingListener();//源碼中判斷了,如果onFlingListener已經(jīng)存在的話,再次設置就直接拋出異常,那么這里可以判斷一下if(onFlingListener==null){mPagerSnapHelper.attachToRecyclerView(mRecyclerView);}}catch(IllegalStateExceptione){e.printStackTrace();}

5.3 自定義LayoutManager注意點

網(wǎng)上有人已經(jīng)寫了一篇自定義LayoutManager實現(xiàn)抖音的效果的博客,我自己也很仔細看了這篇文章。不過我覺得有幾個注意要點,因為要用到線上app,則一定要盡可能減少崩潰率……

通過SnapHelper調用findSnapView方法,得到的view,一定要增加非空判斷邏輯,否則很容易造成崩潰。

在監(jiān)聽滾動位移scrollVerticallyBy的時候,注意要增加判斷,就是getChildCount()如果為0時,則需要返回0。

在onDetachedFromWindow調用的時候,可以把listener監(jiān)聽事件給remove掉。

5.4 視頻播放邏輯優(yōu)化

從前臺切到后臺,當視頻正在播放或者正在緩沖時,調用方法可以設置暫停視頻。銷毀頁面,釋放,內部的播放器被釋放掉,同時如果在全屏、小窗口模式下都會退出。從后臺切換到前臺,當視頻暫停時或者緩沖暫停時,調用該方法重新開啟視頻播放。具體視頻播放代碼設置如下,具體更加詳細內容可以看我封裝的視頻播放器lib

@OverrideprotectedvoidonStop(){super.onStop();//從前臺切到后臺,當視頻正在播放或者正在緩沖時,調用該方法暫停視頻VideoPlayerManager.instance().suspendVideoPlayer();}@OverrideprotectedvoidonDestroy(){super.onDestroy();//銷毀頁面,釋放,內部的播放器被釋放掉,同時如果在全屏、小窗口模式下都會退出VideoPlayerManager.instance().releaseVideoPlayer();}@OverridepublicvoidonBackPressed(){//處理返回鍵邏輯;如果是全屏,則退出全屏;如果是小窗口,則退出小窗口if(VideoPlayerManager.instance().onBackPressed()){return;}else{//銷毀頁面VideoPlayerManager.instance().releaseVideoPlayer();}super.onBackPressed();}@OverrideprotectedvoidonRestart(){super.onRestart();//從后臺切換到前臺,當視頻暫停時或者緩沖暫停時,調用該方法重新開啟視頻播放VideoPlayerManager.instance().resumeVideoPlayer();}

5.5 視頻邏輯充分解藕

實際開發(fā)中,翻頁肯定會涉及到視頻的初始化和銷毀的邏輯。首先要保證視頻只有唯一一個播放,滑動到分頁一半,總不可能讓兩個頁面都播放視頻吧,所以需要保證視頻VideoPlayer是一個單利對象,這樣就可以保證唯一性呢!接著,不管是在recyclerView還是ViewPager中,當頁面處于不可見被銷毀或者view被回收的階段,這個時候需要把視頻資源銷毀,盡量視頻播放功能封裝起來,然后在頁面不同狀態(tài)調用方法即可。

當然,實際app中,視頻播放頁面,還有一些點贊,評論,分享,查看作者等等很多其他功能。那么這些都是要請求接口的,還有滑動分頁的功能,當滑動到最后某一頁時候拉取下一個視頻集合數(shù)據(jù)等業(yè)務邏輯。視頻播放功能這塊,因為功能比較復雜,因此封裝一下比較好。盡量做到視頻功能解藕!關于視頻封裝庫,可以看我之前寫的一個庫,視頻播放器

5.6 翻頁卡頓優(yōu)化分析

如果是使用recyclerView實現(xiàn)滑動翻頁效果,那么為了提高使用體驗效果。則可以注意:1.在onBindViewHolder中不要做耗時操作,2.視頻滑動翻頁的布局固定高度,避免重復計算高度RecyclerView.setHasFixedSize(true),3.關于分頁拉取數(shù)據(jù)注意,建議一次拉下10條數(shù)據(jù)(這個也可以和服務端協(xié)定自定義數(shù)量),而不要滑動一頁加載下一頁的數(shù)據(jù)。

5.7 上拉很快翻頁黑屏

因為設置視頻的背景顏色為黑色,我看了好多播放器初始化的時候,都是這樣的。因為最簡單的解決辦法,就是給它加個封面,設置封面的背景即可。

作者:飛魚_9d08

鏈接:http://m.itdecent.cn/p/53e4a1c0bd62

來源:簡書

著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容