解决RecyclerView无法onItemClick问题的两种方法
对于RecyclerView的使用,大家可以查看将替代ListView的RecyclerView的使用详解(一),单单从代码结构来说RecyclerView确实比ListView优化了很多,也简化了我们编写代码量,但是有一个问题会导致开发者不会去用它,更比说替换ListView了,我不知道使用过RecyclerView的人有没有进一步查看,RecyclerView没有提供Item的点击事件,我们使用列表不仅仅为了显示数据,同时也可以能会交互,所以RecyclerView这个问题导致基本没有人用它,我清楚谷歌是怎么想的,不过RecyclerView也并没有把所有的路给堵死,需要我们写代码来实现Item的点击事件,我们都知道RecyclerView里面新加了ViewHolder这个静态抽象类,这个类里面有一个方法getPosition()可以返回当前ViewHolder实例的位置,实现onItemClick就是使用它来做的,下面有两种方法来实现:
第一种:不修改源码
这种方法不修改源码,问题是只能在RecyclerView.Adapter中实现ItemClick事件
publicstaticclassViewHolderextendsRecyclerView.ViewHolder{ publicViewHolder(ViewitemView){ super(itemView); itemView.setOnClickListener(newOnClickListener(){ @Override publicvoidonClick(Viewv){ Log.e("jwzhangjie","当前点击的位置:"+getPosition()); } }); } }
这种方式直观上看起来不太好,不过也可以实现ItemClick事件。
第二种方法:修改RecyclerView源码
1、把在RecyClerView类里面定义OnItemClickListener接口
/** *Interfacedefinitionforacallbacktobeinvokedwhenaniteminthis *RecyclerView.Adapterhasbeenclicked. */ publicinterfaceOnItemClickListener{ /** *CallbackmethodtobeinvokedwhenaniteminthisRecyclerView.Adapterhas *beenclicked. *<p> *ImplementerscancallgetPosition(position)iftheyneed *toaccessthedataassociatedwiththeselecteditem. * *@paramviewTheviewwithintheRecyclerView.Adapterthatwasclicked(this *willbeaviewprovidedbytheadapter) *@parampositionThepositionoftheviewintheadapter. */ voidonItemClick(Viewview,intposition); } publicstaticOnItemClickListenermOnItemClickListener=null; /** *RegisteracallbacktobeinvokedwhenaniteminthisAdapterViewhas *beenclicked. * *@paramlistenerThecallbackthatwillbeinvoked. */ publicvoidsetOnItemClickListener(OnItemClickListenerlistener){ mOnItemClickListener=listener; } /** *@returnThecallbacktobeinvokedwithaniteminthisAdapterViewhas *beenclicked,ornullidnocallbackhasbeenset. */ publicfinalOnItemClickListenergetOnItemClickListener(){ returnmOnItemClickListener; }
2、在RecyclerView中的抽象类ViewHolder中添加View的点击事件
publicstaticabstractclassViewHolderimplementsOnClickListener{ publicfinalViewitemView; intmPosition=NO_POSITION; intmOldPosition=NO_POSITION; longmItemId=NO_ID; intmItemViewType=INVALID_TYPE; /** *ThisViewHolderhasbeenboundtoaposition;mPosition,mItemIdandmItemViewType *areallvalid. */ staticfinalintFLAG_BOUND=1<<0; /** *ThedatathisViewHolder'sviewreflectsisstaleandneedstoberebound *bytheadapter.mPositionandmItemIdareconsistent. */ staticfinalintFLAG_UPDATE=1<<1; /** *ThisViewHolder'sdataisinvalid.TheidentityimpliedbymPositionandmItemId *arenottobetrustedandmaynolongermatchtheitemviewtype. *ThisViewHoldermustbefullyreboundtodifferentdata. */ staticfinalintFLAG_INVALID=1<<2; /** *ThisViewHolderpointsatdatathatrepresentsanitempreviouslyremovedfromthe *dataset.Itsviewmaystillbeusedforthingslikeoutgoinganimations. */ staticfinalintFLAG_REMOVED=1<<3; /** *ThisViewHoldershouldnotberecycled.ThisflagissetviasetIsRecyclable() *andisintendedtokeepviewsaroundduringanimations. */ staticfinalintFLAG_NOT_RECYCLABLE=1<<4; privateintmFlags; privateintmIsRecyclableCount=0; //Ifnon-null,viewiscurrentlyconsideredscrapandmaybereusedforotherdatabythe //scrapcontainer. privateRecyclermScrapContainer=null; @Override publicvoidonClick(Viewv){ if(mOnItemClickListener!=null){ mOnItemClickListener.onItemClick(itemView,getPosition()); } } publicViewHolder(ViewitemView){ if(itemView==null){ thrownewIllegalArgumentException("itemViewmaynotbenull"); } this.itemView=itemView; this.itemView.setOnClickListener(this); } voidoffsetPosition(intoffset){ if(mOldPosition==NO_POSITION){ mOldPosition=mPosition; } mPosition+=offset; } voidclearOldPosition(){ mOldPosition=NO_POSITION; } publicfinalintgetPosition(){ returnmOldPosition==NO_POSITION?mPosition:mOldPosition; } publicfinallonggetItemId(){ returnmItemId; } publicfinalintgetItemViewType(){ returnmItemViewType; } booleanisScrap(){ returnmScrapContainer!=null; } voidunScrap(){ mScrapContainer.unscrapView(this); mScrapContainer=null; } voidsetScrapContainer(Recyclerrecycler){ mScrapContainer=recycler; } booleanisInvalid(){ return(mFlags&FLAG_INVALID)!=0; } booleanneedsUpdate(){ return(mFlags&FLAG_UPDATE)!=0; } booleanisBound(){ return(mFlags&FLAG_BOUND)!=0; } booleanisRemoved(){ return(mFlags&FLAG_REMOVED)!=0; } voidsetFlags(intflags,intmask){ mFlags=(mFlags&~mask)|(flags&mask); } voidaddFlags(intflags){ mFlags|=flags; } voidclearFlagsForSharedPool(){ mFlags=0; } @Override publicStringtoString(){ finalStringBuildersb=newStringBuilder("ViewHolder{"+ Integer.toHexString(hashCode())+"position="+mPosition+"id="+mItemId); if(isScrap())sb.append("scrap"); if(isInvalid())sb.append("invalid"); if(!isBound())sb.append("unbound"); if(needsUpdate())sb.append("update"); if(isRemoved())sb.append("removed"); sb.append("}"); returnsb.toString(); }
3、完成上面的步骤,就可以使用RecyclerView来完成itemClick事件了
cashAccountList.setOnItemClickListener(newOnItemClickListener(){ @Override publicvoidonItemClick(Viewview,intposition){ AppLog.e("position:"+position); } });
下面是完整的RecyclerView源码:
/* *Copyright(C)2013TheAndroidOpenSourceProject * *LicensedundertheApacheLicense,Version2.0(the"License"); *youmaynotusethisfileexceptincompliancewiththeLicense. *YoumayobtainacopyoftheLicenseat * *http://www.apache.org/licenses/LICENSE-2.0 * *Unlessrequiredbyapplicablelaworagreedtoinwriting,software *distributedundertheLicenseisdistributedonan"ASIS"BASIS, *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied. *SeetheLicenseforthespecificlanguagegoverningpermissionsand *limitationsundertheLicense. */ packageandroid.support.v7.widget; importandroid.content.Context; importandroid.database.Observable; importandroid.graphics.Canvas; importandroid.graphics.PointF; importandroid.graphics.Rect; importandroid.os.Build; importandroid.os.Parcel; importandroid.os.Parcelable; importandroid.support.annotation.Nullable; importandroid.support.v4.util.ArrayMap; importandroid.support.v4.util.Pools; importandroid.support.v4.view.MotionEventCompat; importandroid.support.v4.view.VelocityTrackerCompat; importandroid.support.v4.view.ViewCompat; importandroid.support.v4.widget.EdgeEffectCompat; importandroid.support.v4.widget.ScrollerCompat; importandroid.util.AttributeSet; importandroid.util.Log; importandroid.util.SparseArray; importandroid.util.SparseIntArray; importandroid.view.FocusFinder; importandroid.view.MotionEvent; importandroid.view.VelocityTracker; importandroid.view.View; importandroid.view.ViewConfiguration; importandroid.view.ViewGroup; importandroid.view.ViewParent; importandroid.view.animation.Interpolator; importjava.util.ArrayList; importjava.util.Collections; importjava.util.List; /** *Aflexibleviewforprovidingalimitedwindowintoalargedataset. * *<h3>Glossaryofterms:</h3> * *<ul> *<li><em>Adapter:</em>Asubclassof{@linkAdapter}responsibleforprovidingviews *thatrepresentitemsinadataset.</li> *<li><em>Position:</em>Thepositionofadataitemwithinan<em>Adapter</em>.</li> *<li><em>Index:</em>Theindexofanattachedchildviewasusedinacallto *{@linkViewGroup#getChildAt}.Contrastwith<em>Position.</em></li> *<li><em>Binding:</em>Theprocessofpreparingachildviewtodisplaydatacorresponding *toa<em>position</em>withintheadapter.</li> *<li><em>Recycle(view):</em>Aviewpreviouslyusedtodisplaydataforaspecificadapter *positionmaybeplacedinacacheforlaterreusetodisplaythesametypeofdataagain *later.Thiscandrasticallyimproveperformancebyskippinginitiallayoutinflation *orconstruction.</li> *<li><em>Scrap(view):</em>Achildviewthathasenteredintoatemporarilydetached *stateduringlayout.Scrapviewsmaybereusedwithoutbecomingfullydetached *fromtheparentRecyclerView,eitherunmodifiedifnorebindingisrequiredormodified *bytheadapteriftheviewwasconsidered<em>dirty</em>.</li> *<li><em>Dirty(view):</em>Achildviewthatmustbereboundbytheadapterbefore *beingdisplayed.</li> *</ul> */ publicclassRecyclerViewextendsViewGroup{ privatestaticfinalStringTAG="RecyclerView"; privatestaticfinalbooleanDEBUG=false; privatestaticfinalbooleanENABLE_PREDICTIVE_ANIMATIONS=false; privatestaticfinalbooleanDISPATCH_TEMP_DETACH=false; publicstaticfinalintHORIZONTAL=0; publicstaticfinalintVERTICAL=1; publicstaticfinalintNO_POSITION=-1; publicstaticfinallongNO_ID=-1; publicstaticfinalintINVALID_TYPE=-1; privatestaticfinalintMAX_SCROLL_DURATION=2000; privatefinalRecyclerViewDataObservermObserver=newRecyclerViewDataObserver(); privatefinalRecyclermRecycler=newRecycler(); privateSavedStatemPendingSavedState; /** *Note:thisRunnableisonlyeverpostedif: *1)We'vebeenthroughfirstlayout *2)Weknowwehaveafixedsize(mHasFixedSize) *3)We'reattached */ privatefinalRunnablemUpdateChildViewsRunnable=newRunnable(){ publicvoidrun(){ if(mPendingUpdates.isEmpty()){ return; } eatRequestLayout(); updateChildViews(); resumeRequestLayout(true); } }; privatefinalRectmTempRect=newRect(); privatefinalArrayList<UpdateOp>mPendingUpdates=newArrayList<UpdateOp>(); privatefinalArrayList<UpdateOp>mPendingLayoutUpdates=newArrayList<UpdateOp>(); privatePools.Pool<UpdateOp>mUpdateOpPool=newPools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE); privateAdaptermAdapter; privateLayoutManagermLayout; privateRecyclerListenermRecyclerListener; privatefinalArrayList<ItemDecoration>mItemDecorations=newArrayList<ItemDecoration>(); privatefinalArrayList<OnItemTouchListener>mOnItemTouchListeners= newArrayList<OnItemTouchListener>(); privateOnItemTouchListenermActiveOnItemTouchListener; privatebooleanmIsAttached; privatebooleanmHasFixedSize; privatebooleanmFirstLayoutComplete; privatebooleanmEatRequestLayout; privatebooleanmLayoutRequestEaten; privatebooleanmAdapterUpdateDuringMeasure; privatefinalbooleanmPostUpdatesOnAnimation; privateEdgeEffectCompatmLeftGlow,mTopGlow,mRightGlow,mBottomGlow; ItemAnimatormItemAnimator=newDefaultItemAnimator(); privatestaticfinalintINVALID_POINTER=-1; /** *TheRecyclerViewisnotcurrentlyscrolling. *@see#getScrollState() */ publicstaticfinalintSCROLL_STATE_IDLE=0; /** *TheRecyclerViewiscurrentlybeingdraggedbyoutsideinputsuchasusertouchinput. *@see#getScrollState() */ publicstaticfinalintSCROLL_STATE_DRAGGING=1; /** *TheRecyclerViewiscurrentlyanimatingtoafinalpositionwhilenotunder *outsidecontrol. *@see#getScrollState() */ publicstaticfinalintSCROLL_STATE_SETTLING=2; //Touch/scrollinghandling privateintmScrollState=SCROLL_STATE_IDLE; privateintmScrollPointerId=INVALID_POINTER; privateVelocityTrackermVelocityTracker; privateintmInitialTouchX; privateintmInitialTouchY; privateintmLastTouchX; privateintmLastTouchY; privatefinalintmTouchSlop; privatefinalintmMinFlingVelocity; privatefinalintmMaxFlingVelocity; privatefinalViewFlingermViewFlinger=newViewFlinger(); privatefinalStatemState=newState(); privateOnScrollListenermScrollListener; //Foruseinitemanimations booleanmItemsAddedOrRemoved=false; booleanmItemsChanged=false; intmAnimatingViewIndex=-1; intmNumAnimatingViews=0; booleanmInPreLayout=false; privateItemAnimator.ItemAnimatorListenermItemAnimatorListener= newItemAnimatorRestoreListener(); privatebooleanmPostedAnimatorRunner=false; privateRunnablemItemAnimatorRunner=newRunnable(){ @Override publicvoidrun(){ if(mItemAnimator!=null){ mItemAnimator.runPendingAnimations(); } mPostedAnimatorRunner=false; } }; privatestaticfinalInterpolatorsQuinticInterpolator=newInterpolator(){ publicfloatgetInterpolation(floatt){ t-=1.0f; returnt*t*t*t*t+1.0f; } }; publicRecyclerView(Contextcontext){ this(context,null); } publicRecyclerView(Contextcontext,AttributeSetattrs){ this(context,attrs,0); } publicRecyclerView(Contextcontext,AttributeSetattrs,intdefStyle){ super(context,attrs,defStyle); finalintversion=Build.VERSION.SDK_INT; mPostUpdatesOnAnimation=version>=16; finalViewConfigurationvc=ViewConfiguration.get(context); mTouchSlop=vc.getScaledTouchSlop(); mMinFlingVelocity=vc.getScaledMinimumFlingVelocity(); mMaxFlingVelocity=vc.getScaledMaximumFlingVelocity(); setWillNotDraw(ViewCompat.getOverScrollMode(this)==ViewCompat.OVER_SCROLL_NEVER); mItemAnimator.setListener(mItemAnimatorListener); } /** *RecyclerViewcanperformseveraloptimizationsifitcanknowinadvancethatchangesin *adaptercontentcannotchangethesizeoftheRecyclerViewitself. *IfyouruseofRecyclerViewfallsintothiscategory,setthistotrue. * *@paramhasFixedSizetrueifadapterchangescannotaffectthesizeoftheRecyclerView. */ publicvoidsetHasFixedSize(booleanhasFixedSize){ mHasFixedSize=hasFixedSize; } /** *@returntrueiftheapphasspecifiedthatchangesinadaptercontentcannotchange *thesizeoftheRecyclerViewitself. */ publicbooleanhasFixedSize(){ returnmHasFixedSize; } /** *Setanewadaptertoprovidechildviewsondemand. * *@paramadapterThenewadaptertoset,ornulltosetnoadapter. */ publicvoidsetAdapter(Adapteradapter){ if(mAdapter!=null){ mAdapter.unregisterAdapterDataObserver(mObserver); } //endallrunninganimations if(mItemAnimator!=null){ mItemAnimator.endAnimations(); } //Sinceanimationsareended,mLayout.childrenshouldbeequaltorecyclerView.children. //Thismaynotbetrueifitemanimator'senddoesnotworkasexpected.(e.g.notrelease //childreninstantly).ItissafertousemLayout'schildcount. if(mLayout!=null){ mLayout.removeAndRecycleAllViews(mRecycler); mLayout.removeAndRecycleScrapInt(mRecycler,true); } finalAdapteroldAdapter=mAdapter; mAdapter=adapter; if(adapter!=null){ adapter.registerAdapterDataObserver(mObserver); } if(mLayout!=null){ mLayout.onAdapterChanged(oldAdapter,mAdapter); } mRecycler.onAdapterChanged(oldAdapter,mAdapter); mState.mStructureChanged=true; markKnownViewsInvalid(); requestLayout(); } /** *Retrievesthepreviouslysetadapterornullifnoadapterisset. * *@returnThepreviouslysetadapter *@see#setAdapter(Adapter) */ publicAdaptergetAdapter(){ returnmAdapter; } /** *Registeralistenerthatwillbenotifiedwheneverachildviewisrecycled. * *<p>ThislistenerwillbecalledwhenaLayoutManagerortheRecyclerViewdecides *thatachildviewisnolongerneeded.Ifanapplicationassociatesexpensive *orheavyweightdatawithitemviews,thismaybeagoodplacetorelease *orfreethoseresources.</p> * *@paramlistenerListenertoregister,ornulltoclear */ publicvoidsetRecyclerListener(RecyclerListenerlistener){ mRecyclerListener=listener; } /** *Setthe{@linkLayoutManager}thatthisRecyclerViewwilluse. * *<p>Incontrasttootheradapter-backedviewssuchas{@linkandroid.widget.ListView} *or{@linkandroid.widget.GridView},RecyclerViewallowsclientcodetoprovidecustom *layoutarrangementsforchildviews.Thesearrangementsarecontrolledbythe *{@linkLayoutManager}.ALayoutManagermustbeprovidedforRecyclerViewtofunction.</p> * *<p>Severaldefaultstrategiesareprovidedforcommonusessuchaslistsandgrids.</p> * *@paramlayoutLayoutManagertouse */ publicvoidsetLayoutManager(LayoutManagerlayout){ if(layout==mLayout){ return; } mRecycler.clear(); removeAllViews(); if(mLayout!=null){ if(mIsAttached){ mLayout.onDetachedFromWindow(this); } mLayout.mRecyclerView=null; } mLayout=layout; if(layout!=null){ if(layout.mRecyclerView!=null){ thrownewIllegalArgumentException("LayoutManager"+layout+ "isalreadyattachedtoaRecyclerView:"+layout.mRecyclerView); } layout.mRecyclerView=this; if(mIsAttached){ mLayout.onAttachedToWindow(this); } } requestLayout(); } @Override protectedParcelableonSaveInstanceState(){ SavedStatestate=newSavedState(super.onSaveInstanceState()); if(mPendingSavedState!=null){ state.copyFrom(mPendingSavedState); }elseif(mLayout!=null){ state.mLayoutState=mLayout.onSaveInstanceState(); }else{ state.mLayoutState=null; } returnstate; } @Override protectedvoidonRestoreInstanceState(Parcelablestate){ mPendingSavedState=(SavedState)state; super.onRestoreInstanceState(mPendingSavedState.getSuperState()); if(mLayout!=null&&mPendingSavedState.mLayoutState!=null){ mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState); } } /** *AddsaviewtotheanimatingViewslist. *mAnimatingViewsholdsthechildviewsthatarecurrentlybeingkeptaround *purelyforthepurposeofbeinganimatedoutofview.Theyaredrawnasaregular *partofthechildlistoftheRecyclerView,buttheyareinvisibletotheLayoutManager *astheyaremanagedseparatelyfromtheregularchildviews. *@paramviewTheviewtoberemoved */ privatevoidaddAnimatingView(Viewview){ booleanalreadyAdded=false; if(mNumAnimatingViews>0){ for(inti=mAnimatingViewIndex;i<getChildCount();++i){ if(getChildAt(i)==view){ alreadyAdded=true; break; } } } if(!alreadyAdded){ if(mNumAnimatingViews==0){ mAnimatingViewIndex=getChildCount(); } ++mNumAnimatingViews; addView(view); } mRecycler.unscrapView(getChildViewHolder(view)); } /** *RemovesaviewfromtheanimatingViewslist. *@paramviewTheviewtoberemoved *@see#addAnimatingView(View) */ privatevoidremoveAnimatingView(Viewview){ if(mNumAnimatingViews>0){ for(inti=mAnimatingViewIndex;i<getChildCount();++i){ if(getChildAt(i)==view){ removeViewAt(i); --mNumAnimatingViews; if(mNumAnimatingViews==0){ mAnimatingViewIndex=-1; } mRecycler.recycleView(view); return; } } } } privateViewgetAnimatingView(intposition,inttype){ if(mNumAnimatingViews>0){ for(inti=mAnimatingViewIndex;i<getChildCount();++i){ finalViewview=getChildAt(i); ViewHolderholder=getChildViewHolder(view); if(holder.getPosition()==position&& (type==INVALID_TYPE||holder.getItemViewType()==type)){ returnview; } } } returnnull; } /** *Returnthe{@linkLayoutManager}currentlyresponsiblefor *layoutpolicyforthisRecyclerView. * *@returnThecurrentlyboundLayoutManager */ publicLayoutManagergetLayoutManager(){ returnmLayout; } /** *RetrievethisRecyclerView's{@linkRecycledViewPool}.Thismethodwillneverreturnnull; *ifnopoolissetforthisviewanewonewillbecreated.See *{@link#setRecycledViewPool(RecycledViewPool)setRecycledViewPool}formoreinformation. * *@returnThepoolusedtostorerecycleditemviewsforreuse. *@see#setRecycledViewPool(RecycledViewPool) */ publicRecycledViewPoolgetRecycledViewPool(){ returnmRecycler.getRecycledViewPool(); } /** *RecycledviewpoolsallowmultipleRecyclerViewstoshareacommonpoolofscrapviews. *ThiscanbeusefulifyouhavemultipleRecyclerViewswithadaptersthatusethesame *viewtypes,forexampleifyouhaveseveraldatasetswiththesamekindsofitemviews *displayedbya{@linkandroid.support.v4.view.ViewPagerViewPager}. * *@parampoolPooltoset.Ifthisparameterisnullanewpoolwillbecreatedandused. */ publicvoidsetRecycledViewPool(RecycledViewPoolpool){ mRecycler.setRecycledViewPool(pool); } /** *Setthenumberofoffscreenviewstoretainbeforeaddingthemtothepotentiallyshared *{@link#getRecycledViewPool()recycledviewpool}. * *<p>Theoffscreenviewcachestaysawareofchangesintheattachedadapter,allowing *aLayoutManagertoreusethoseviewsunmodifiedwithoutneedingtoreturntotheadapter *torebindthem.</p> * *@paramsizeNumberofviewstocacheoffscreenbeforereturningthemtothegeneral *recycledviewpool */ publicvoidsetItemViewCacheSize(intsize){ mRecycler.setViewCacheSize(size); } /** *ReturnthecurrentscrollingstateoftheRecyclerView. * *@return{@link#SCROLL_STATE_IDLE},{@link#SCROLL_STATE_DRAGGING}or *{@link#SCROLL_STATE_SETTLING} */ publicintgetScrollState(){ returnmScrollState; } privatevoidsetScrollState(intstate){ if(state==mScrollState){ return; } mScrollState=state; if(state!=SCROLL_STATE_SETTLING){ stopScroll(); } if(mScrollListener!=null){ mScrollListener.onScrollStateChanged(state); } } /** *Addan{@linkItemDecoration}tothisRecyclerView.Itemdecorationscan *affectbothmeasurementanddrawingofindividualitemviews. * *<p>Itemdecorationsareordered.Decorationsplacedearlierinthelistwill *berun/queried/drawnfirstfortheireffectsonitemviews.Paddingaddedtoviews *willbenested;apaddingaddedbyanearlierdecorationwillmeanfurther *itemdecorationsinthelistwillbeaskedtodraw/padwithinthepreviousdecoration's *givenarea.</p> * *@paramdecorDecorationtoadd *@paramindexPositioninthedecorationchaintoinsertthisdecorationat.Ifthisvalue *isnegativethedecorationwillbeaddedattheend. */ publicvoidaddItemDecoration(ItemDecorationdecor,intindex){ if(mItemDecorations.isEmpty()){ setWillNotDraw(false); } if(index<0){ mItemDecorations.add(decor); }else{ mItemDecorations.add(index,decor); } markItemDecorInsetsDirty(); requestLayout(); } /** *Addan{@linkItemDecoration}tothisRecyclerView.Itemdecorationscan *affectbothmeasurementanddrawingofindividualitemviews. * *<p>Itemdecorationsareordered.Decorationsplacedearlierinthelistwill *berun/queried/drawnfirstfortheireffectsonitemviews.Paddingaddedtoviews *willbenested;apaddingaddedbyanearlierdecorationwillmeanfurther *itemdecorationsinthelistwillbeaskedtodraw/padwithinthepreviousdecoration's *givenarea.</p> * *@paramdecorDecorationtoadd */ publicvoidaddItemDecoration(ItemDecorationdecor){ addItemDecoration(decor,-1); } /** *Removean{@linkItemDecoration}fromthisRecyclerView. * *<p>Thegivendecorationwillnolongerimpactthemeasurementanddrawingof *itemviews.</p> * *@paramdecorDecorationtoremove *@see#addItemDecoration(ItemDecoration) */ publicvoidremoveItemDecoration(ItemDecorationdecor){ mItemDecorations.remove(decor); if(mItemDecorations.isEmpty()){ setWillNotDraw(ViewCompat.getOverScrollMode(this)==ViewCompat.OVER_SCROLL_NEVER); } markItemDecorInsetsDirty(); requestLayout(); } /** *Setalistenerthatwillbenotifiedofanychangesinscrollstateorposition. * *@paramlistenerListenertosetornulltoclear */ publicvoidsetOnScrollListener(OnScrollListenerlistener){ mScrollListener=listener; } /** *Conveniencemethodtoscrolltoacertainposition. * *RecyclerViewdoesnotimplementscrollinglogic,ratherforwardsthecallto *{@linkandroid.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)} *@parampositionScrolltothisadapterposition *@seeandroid.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int) */ publicvoidscrollToPosition(intposition){ stopScroll(); mLayout.scrollToPosition(position); awakenScrollBars(); } /** *Startsasmoothscrolltoanadapterposition. *<p> *Tosupportsmoothscrolling,youmustoverride *{@linkLayoutManager#smoothScrollToPosition(RecyclerView,State,int)}andcreatea *{@linkSmoothScroller}. *<p> *{@linkLayoutManager}isresponsibleforcreatingtheactualscrollaction.Ifyouwantto *provideacustomsmoothscrolllogic,override *{@linkLayoutManager#smoothScrollToPosition(RecyclerView,State,int)}inyour *LayoutManager. * *@parampositionTheadapterpositiontoscrollto *@seeLayoutManager#smoothScrollToPosition(RecyclerView,State,int) */ publicvoidsmoothScrollToPosition(intposition){ mLayout.smoothScrollToPosition(this,mState,position); } @Override publicvoidscrollTo(intx,inty){ thrownewUnsupportedOperationException( "RecyclerViewdoesnotsupportscrollingtoanabsoluteposition."); } @Override publicvoidscrollBy(intx,inty){ if(mLayout==null){ thrownewIllegalStateException("CannotscrollwithoutaLayoutManagerset."+ "CallsetLayoutManagerwithanon-nullargument."); } finalbooleancanScrollHorizontal=mLayout.canScrollHorizontally(); finalbooleancanScrollVertical=mLayout.canScrollVertically(); if(canScrollHorizontal||canScrollVertical){ scrollByInternal(canScrollHorizontal?x:0,canScrollVertical?y:0); } } /** *Helpermethodreflectdatachangestothestate. *<p> *Adapterchangesduringascrollmaytriggeracrashbecausescrollassumesnodatachange *butdataactuallychanged. *<p> *Thismethodconsumesalldeferredchangestoavoidthatcase. *<p> *Thisalsoendsallpendinganimations.Itwillbechangedoncewecansupport *animationsduringscroll. */ privatevoidconsumePendingUpdateOperations(){ if(mItemAnimator!=null){ mItemAnimator.endAnimations(); } if(mPendingUpdates.size()>0){ mUpdateChildViewsRunnable.run(); } } /** *Doesnotperformboundschecking.Usedbyinternalmethodsthathavealreadyvalidatedinput. */ voidscrollByInternal(intx,inty){ intoverscrollX=0,overscrollY=0; consumePendingUpdateOperations(); if(mAdapter!=null){ eatRequestLayout(); if(x!=0){ finalinthresult=mLayout.scrollHorizontallyBy(x,mRecycler,mState); overscrollX=x-hresult; } if(y!=0){ finalintvresult=mLayout.scrollVerticallyBy(y,mRecycler,mState); overscrollY=y-vresult; } resumeRequestLayout(false); } if(!mItemDecorations.isEmpty()){ invalidate(); } if(ViewCompat.getOverScrollMode(this)!=ViewCompat.OVER_SCROLL_NEVER){ pullGlows(overscrollX,overscrollY); } if(mScrollListener!=null&&(x!=0||y!=0)){ mScrollListener.onScrolled(x,y); } if(!awakenScrollBars()){ invalidate(); } } /** *<p>Computethehorizontaloffsetofthehorizontalscrollbar'sthumbwithinthehorizontal *range.Thisvalueisusedtocomputethelengthofthethumbwithinthescrollbar'strack. *</p> * *<p>Therangeisexpressedinarbitraryunitsthatmustbethesameastheunitsusedby *{@link#computeHorizontalScrollRange()}and{@link#computeHorizontalScrollExtent()}.</p> * *<p>Defaultimplementationreturns0.</p> * *<p>Ifyouwanttosupportscrollbars,override *{@linkRecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)}inyour *LayoutManager.</p> * *@returnThehorizontaloffsetofthescrollbar'sthumb *@seeandroid.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset *(RecyclerView.Adapter) */ @Override protectedintcomputeHorizontalScrollOffset(){ returnmLayout.canScrollHorizontally()?mLayout.computeHorizontalScrollOffset(mState) :0; } /** *<p>Computethehorizontalextentofthehorizontalscrollbar'sthumbwithinthe *horizontalrange.Thisvalueisusedtocomputethelengthofthethumbwithinthe *scrollbar'strack.</p> * *<p>Therangeisexpressedinarbitraryunitsthatmustbethesameastheunitsusedby *{@link#computeHorizontalScrollRange()}and{@link#computeHorizontalScrollOffset()}.</p> * *<p>Defaultimplementationreturns0.</p> * *<p>Ifyouwanttosupportscrollbars,override *{@linkRecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)}inyour *LayoutManager.</p> * *@returnThehorizontalextentofthescrollbar'sthumb *@seeRecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State) */ @Override protectedintcomputeHorizontalScrollExtent(){ returnmLayout.canScrollHorizontally()?mLayout.computeHorizontalScrollExtent(mState):0; } /** *<p>Computethehorizontalrangethatthehorizontalscrollbarrepresents.</p> * *<p>Therangeisexpressedinarbitraryunitsthatmustbethesameastheunitsusedby *{@link#computeHorizontalScrollExtent()}and{@link#computeHorizontalScrollOffset()}.</p> * *<p>Defaultimplementationreturns0.</p> * *<p>Ifyouwanttosupportscrollbars,override *{@linkRecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)}inyour *LayoutManager.</p> * *@returnThetotalhorizontalrangerepresentedbytheverticalscrollbar *@seeRecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State) */ @Override protectedintcomputeHorizontalScrollRange(){ returnmLayout.canScrollHorizontally()?mLayout.computeHorizontalScrollRange(mState):0; } /** *<p>Computetheverticaloffsetoftheverticalscrollbar'sthumbwithintheverticalrange. *Thisvalueisusedtocomputethelengthofthethumbwithinthescrollbar'strack.</p> * *<p>Therangeisexpressedinarbitraryunitsthatmustbethesameastheunitsusedby *{@link#computeVerticalScrollRange()}and{@link#computeVerticalScrollExtent()}.</p> * *<p>Defaultimplementationreturns0.</p> * *<p>Ifyouwanttosupportscrollbars,override *{@linkRecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)}inyour *LayoutManager.</p> * *@returnTheverticaloffsetofthescrollbar'sthumb *@seeandroid.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset *(RecyclerView.Adapter) */ @Override protectedintcomputeVerticalScrollOffset(){ returnmLayout.canScrollVertically()?mLayout.computeVerticalScrollOffset(mState):0; } /** *<p>Computetheverticalextentoftheverticalscrollbar'sthumbwithintheverticalrange. *Thisvalueisusedtocomputethelengthofthethumbwithinthescrollbar'strack.</p> * *<p>Therangeisexpressedinarbitraryunitsthatmustbethesameastheunitsusedby *{@link#computeVerticalScrollRange()}and{@link#computeVerticalScrollOffset()}.</p> * *<p>Defaultimplementationreturns0.</p> * *<p>Ifyouwanttosupportscrollbars,override *{@linkRecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)}inyour *LayoutManager.</p> * *@returnTheverticalextentofthescrollbar'sthumb *@seeRecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State) */ @Override protectedintcomputeVerticalScrollExtent(){ returnmLayout.canScrollVertically()?mLayout.computeVerticalScrollExtent(mState):0; } /** *<p>Computetheverticalrangethattheverticalscrollbarrepresents.</p> * *<p>Therangeisexpressedinarbitraryunitsthatmustbethesameastheunitsusedby *{@link#computeVerticalScrollExtent()}and{@link#computeVerticalScrollOffset()}.</p> * *<p>Defaultimplementationreturns0.</p> * *<p>Ifyouwanttosupportscrollbars,override *{@linkRecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)}inyour *LayoutManager.</p> * *@returnThetotalverticalrangerepresentedbytheverticalscrollbar *@seeRecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State) */ @Override protectedintcomputeVerticalScrollRange(){ returnmLayout.canScrollVertically()?mLayout.computeVerticalScrollRange(mState):0; } voideatRequestLayout(){ if(!mEatRequestLayout){ mEatRequestLayout=true; mLayoutRequestEaten=false; } } voidresumeRequestLayout(booleanperformLayoutChildren){ if(mEatRequestLayout){ if(performLayoutChildren&&mLayoutRequestEaten&& mLayout!=null&&mAdapter!=null){ dispatchLayout(); } mEatRequestLayout=false; mLayoutRequestEaten=false; } } /** *Animateascrollbythegivenamountofpixelsalongeitheraxis. * *@paramdxPixelstoscrollhorizontally *@paramdyPixelstoscrollvertically */ publicvoidsmoothScrollBy(intdx,intdy){ if(dx!=0||dy!=0){ mViewFlinger.smoothScrollBy(dx,dy); } } /** *Beginastandardflingwithaninitialvelocityalongeachaxisinpixelspersecond. *Ifthevelocitygivenisbelowthesystem-definedminimumthismethodwillreturnfalse *andnoflingwilloccur. * *@paramvelocityXInitialhorizontalvelocityinpixelspersecond *@paramvelocityYInitialverticalvelocityinpixelspersecond *@returntrueiftheflingwasstarted,falseifthevelocitywastoolowtofling */ publicbooleanfling(intvelocityX,intvelocityY){ if(Math.abs(velocityX)<mMinFlingVelocity){ velocityX=0; } if(Math.abs(velocityY)<mMinFlingVelocity){ velocityY=0; } velocityX=Math.max(-mMaxFlingVelocity,Math.min(velocityX,mMaxFlingVelocity)); velocityY=Math.max(-mMaxFlingVelocity,Math.min(velocityY,mMaxFlingVelocity)); if(velocityX!=0||velocityY!=0){ mViewFlinger.fling(velocityX,velocityY); returntrue; } returnfalse; } /** *Stopanycurrentscrollinprogress,suchasonestartedby *{@link#smoothScrollBy(int,int)},{@link#fling(int,int)}oratouch-initiatedfling. */ publicvoidstopScroll(){ mViewFlinger.stop(); mLayout.stopSmoothScroller(); } /** *Applyapulltorelevantoverscrollgloweffects */ privatevoidpullGlows(intoverscrollX,intoverscrollY){ if(overscrollX<0){ if(mLeftGlow==null){ mLeftGlow=newEdgeEffectCompat(getContext()); mLeftGlow.setSize(getMeasuredHeight()-getPaddingTop()-getPaddingBottom(), getMeasuredWidth()-getPaddingLeft()-getPaddingRight()); } mLeftGlow.onPull(-overscrollX/(float)getWidth()); }elseif(overscrollX>0){ if(mRightGlow==null){ mRightGlow=newEdgeEffectCompat(getContext()); mRightGlow.setSize(getMeasuredHeight()-getPaddingTop()-getPaddingBottom(), getMeasuredWidth()-getPaddingLeft()-getPaddingRight()); } mRightGlow.onPull(overscrollX/(float)getWidth()); } if(overscrollY<0){ if(mTopGlow==null){ mTopGlow=newEdgeEffectCompat(getContext()); mTopGlow.setSize(getMeasuredWidth()-getPaddingLeft()-getPaddingRight(), getMeasuredHeight()-getPaddingTop()-getPaddingBottom()); } mTopGlow.onPull(-overscrollY/(float)getHeight()); }elseif(overscrollY>0){ if(mBottomGlow==null){ mBottomGlow=newEdgeEffectCompat(getContext()); mBottomGlow.setSize(getMeasuredWidth()-getPaddingLeft()-getPaddingRight(), getMeasuredHeight()-getPaddingTop()-getPaddingBottom()); } mBottomGlow.onPull(overscrollY/(float)getHeight()); } if(overscrollX!=0||overscrollY!=0){ ViewCompat.postInvalidateOnAnimation(this); } } privatevoidreleaseGlows(){ booleanneedsInvalidate=false; if(mLeftGlow!=null)needsInvalidate=mLeftGlow.onRelease(); if(mTopGlow!=null)needsInvalidate|=mTopGlow.onRelease(); if(mRightGlow!=null)needsInvalidate|=mRightGlow.onRelease(); if(mBottomGlow!=null)needsInvalidate|=mBottomGlow.onRelease(); if(needsInvalidate){ ViewCompat.postInvalidateOnAnimation(this); } } voidabsorbGlows(intvelocityX,intvelocityY){ if(velocityX<0){ if(mLeftGlow==null){ mLeftGlow=newEdgeEffectCompat(getContext()); mLeftGlow.setSize(getMeasuredHeight()-getPaddingTop()-getPaddingBottom(), getMeasuredWidth()-getPaddingLeft()-getPaddingRight()); } mLeftGlow.onAbsorb(-velocityX); }elseif(velocityX>0){ if(mRightGlow==null){ mRightGlow=newEdgeEffectCompat(getContext()); mRightGlow.setSize(getMeasuredHeight()-getPaddingTop()-getPaddingBottom(), getMeasuredWidth()-getPaddingLeft()-getPaddingRight()); } mRightGlow.onAbsorb(velocityX); } if(velocityY<0){ if(mTopGlow==null){ mTopGlow=newEdgeEffectCompat(getContext()); mTopGlow.setSize(getMeasuredWidth()-getPaddingLeft()-getPaddingRight(), getMeasuredHeight()-getPaddingTop()-getPaddingBottom()); } mTopGlow.onAbsorb(-velocityY); }elseif(velocityY>0){ if(mBottomGlow==null){ mBottomGlow=newEdgeEffectCompat(getContext()); mBottomGlow.setSize(getMeasuredWidth()-getPaddingLeft()-getPaddingRight(), getMeasuredHeight()-getPaddingTop()-getPaddingBottom()); } mBottomGlow.onAbsorb(velocityY); } if(velocityX!=0||velocityY!=0){ ViewCompat.postInvalidateOnAnimation(this); } } //Focushandling @Override publicViewfocusSearch(Viewfocused,intdirection){ Viewresult=mLayout.onInterceptFocusSearch(focused,direction); if(result!=null){ returnresult; } finalFocusFinderff=FocusFinder.getInstance(); result=ff.findNextFocus(this,focused,direction); if(result==null&&mAdapter!=null){ eatRequestLayout(); result=mLayout.onFocusSearchFailed(focused,direction,mRecycler,mState); resumeRequestLayout(false); } returnresult!=null?result:super.focusSearch(focused,direction); } @Override publicvoidrequestChildFocus(Viewchild,Viewfocused){ if(!mLayout.onRequestChildFocus(this,child,focused)){ mTempRect.set(0,0,focused.getWidth(),focused.getHeight()); offsetDescendantRectToMyCoords(focused,mTempRect); offsetRectIntoDescendantCoords(child,mTempRect); requestChildRectangleOnScreen(child,mTempRect,!mFirstLayoutComplete); } super.requestChildFocus(child,focused); } @Override publicbooleanrequestChildRectangleOnScreen(Viewchild,Rectrect,booleanimmediate){ returnmLayout.requestChildRectangleOnScreen(this,child,rect,immediate); } @Override publicvoidaddFocusables(ArrayList<View>views,intdirection,intfocusableMode){ if(!mLayout.onAddFocusables(this,views,direction,focusableMode)){ super.addFocusables(views,direction,focusableMode); } } @Override protectedvoidonAttachedToWindow(){ super.onAttachedToWindow(); mIsAttached=true; mFirstLayoutComplete=false; if(mLayout!=null){ mLayout.onAttachedToWindow(this); } mPostedAnimatorRunner=false; } @Override protectedvoidonDetachedFromWindow(){ super.onDetachedFromWindow(); mFirstLayoutComplete=false; stopScroll(); //TODOMarkwhatourtargetpositionwasifrelevant,thenwecanjumpthere //onreattach. mIsAttached=false; if(mLayout!=null){ mLayout.onDetachedFromWindow(this); } removeCallbacks(mItemAnimatorRunner); } /** *Addan{@linkOnItemTouchListener}tointercepttoucheventsbeforetheyaredispatched *tochildviewsorthisview'sstandardscrollingbehavior. * *<p>Clientcodemayuselistenerstoimplementitemmanipulationbehavior.Oncealistener *returnstruefrom *{@linkOnItemTouchListener#onInterceptTouchEvent(RecyclerView,MotionEvent)}its *{@linkOnItemTouchListener#onTouchEvent(RecyclerView,MotionEvent)}methodwillbecalled *foreachincomingMotionEventuntiltheendofthegesture.</p> * *@paramlistenerListenertoadd */ publicvoidaddOnItemTouchListener(OnItemTouchListenerlistener){ mOnItemTouchListeners.add(listener); } /** *Removean{@linkOnItemTouchListener}.Itwillnolongerbeabletointercepttouchevents. * *@paramlistenerListenertoremove */ publicvoidremoveOnItemTouchListener(OnItemTouchListenerlistener){ mOnItemTouchListeners.remove(listener); if(mActiveOnItemTouchListener==listener){ mActiveOnItemTouchListener=null; } } privatebooleandispatchOnItemTouchIntercept(MotionEvente){ finalintaction=e.getAction(); if(action==MotionEvent.ACTION_CANCEL||action==MotionEvent.ACTION_DOWN){ mActiveOnItemTouchListener=null; } finalintlistenerCount=mOnItemTouchListeners.size(); for(inti=0;i<listenerCount;i++){ finalOnItemTouchListenerlistener=mOnItemTouchListeners.get(i); if(listener.onInterceptTouchEvent(this,e)&&action!=MotionEvent.ACTION_CANCEL){ mActiveOnItemTouchListener=listener; returntrue; } } returnfalse; } privatebooleandispatchOnItemTouch(MotionEvente){ finalintaction=e.getAction(); if(mActiveOnItemTouchListener!=null){ if(action==MotionEvent.ACTION_DOWN){ //Stalestatefromapreviousgesture,we'restartinganewone.Clearit. mActiveOnItemTouchListener=null; }else{ mActiveOnItemTouchListener.onTouchEvent(this,e); if(action==MotionEvent.ACTION_CANCEL||action==MotionEvent.ACTION_UP){ //Cleanupforthenextgesture. mActiveOnItemTouchListener=null; } returntrue; } } //ListenerswillhavealreadyreceivedtheACTION_DOWNviadispatchOnItemTouchIntercept //ascalledfromonInterceptTouchEvent;skipit. if(action!=MotionEvent.ACTION_DOWN){ finalintlistenerCount=mOnItemTouchListeners.size(); for(inti=0;i<listenerCount;i++){ finalOnItemTouchListenerlistener=mOnItemTouchListeners.get(i); if(listener.onInterceptTouchEvent(this,e)){ mActiveOnItemTouchListener=listener; returntrue; } } } returnfalse; } @Override publicbooleanonInterceptTouchEvent(MotionEvente){ if(dispatchOnItemTouchIntercept(e)){ cancelTouch(); returntrue; } finalbooleancanScrollHorizontally=mLayout.canScrollHorizontally(); finalbooleancanScrollVertically=mLayout.canScrollVertically(); if(mVelocityTracker==null){ mVelocityTracker=VelocityTracker.obtain(); } mVelocityTracker.addMovement(e); finalintaction=MotionEventCompat.getActionMasked(e); finalintactionIndex=MotionEventCompat.getActionIndex(e); switch(action){ caseMotionEvent.ACTION_DOWN: mScrollPointerId=MotionEventCompat.getPointerId(e,0); mInitialTouchX=mLastTouchX=(int)(e.getX()+0.5f); mInitialTouchY=mLastTouchY=(int)(e.getY()+0.5f); if(mScrollState==SCROLL_STATE_SETTLING){ getParent().requestDisallowInterceptTouchEvent(true); setScrollState(SCROLL_STATE_DRAGGING); } break; caseMotionEventCompat.ACTION_POINTER_DOWN: mScrollPointerId=MotionEventCompat.getPointerId(e,actionIndex); mInitialTouchX=mLastTouchX=(int)(MotionEventCompat.getX(e,actionIndex)+0.5f); mInitialTouchY=mLastTouchY=(int)(MotionEventCompat.getY(e,actionIndex)+0.5f); break; caseMotionEvent.ACTION_MOVE:{ finalintindex=MotionEventCompat.findPointerIndex(e,mScrollPointerId); if(index<0){ Log.e(TAG,"Errorprocessingscroll;pointerindexforid"+ mScrollPointerId+"notfound.DidanyMotionEventsgetskipped?"); returnfalse; } finalintx=(int)(MotionEventCompat.getX(e,index)+0.5f); finalinty=(int)(MotionEventCompat.getY(e,index)+0.5f); if(mScrollState!=SCROLL_STATE_DRAGGING){ finalintdx=x-mInitialTouchX; finalintdy=y-mInitialTouchY; booleanstartScroll=false; if(canScrollHorizontally&&Math.abs(dx)>mTouchSlop){ mLastTouchX=mInitialTouchX+mTouchSlop*(dx<0?-1:1); startScroll=true; } if(canScrollVertically&&Math.abs(dy)>mTouchSlop){ mLastTouchY=mInitialTouchY+mTouchSlop*(dy<0?-1:1); startScroll=true; } if(startScroll){ getParent().requestDisallowInterceptTouchEvent(true); setScrollState(SCROLL_STATE_DRAGGING); } } }break; caseMotionEventCompat.ACTION_POINTER_UP:{ onPointerUp(e); }break; caseMotionEvent.ACTION_UP:{ mVelocityTracker.clear(); }break; caseMotionEvent.ACTION_CANCEL:{ cancelTouch(); } } returnmScrollState==SCROLL_STATE_DRAGGING; } @Override publicbooleanonTouchEvent(MotionEvente){ if(dispatchOnItemTouch(e)){ cancelTouch(); returntrue; } finalbooleancanScrollHorizontally=mLayout.canScrollHorizontally(); finalbooleancanScrollVertically=mLayout.canScrollVertically(); if(mVelocityTracker==null){ mVelocityTracker=VelocityTracker.obtain(); } mVelocityTracker.addMovement(e); finalintaction=MotionEventCompat.getActionMasked(e); finalintactionIndex=MotionEventCompat.getActionIndex(e); switch(action){ caseMotionEvent.ACTION_DOWN:{ mScrollPointerId=MotionEventCompat.getPointerId(e,0); mInitialTouchX=mLastTouchX=(int)(e.getX()+0.5f); mInitialTouchY=mLastTouchY=(int)(e.getY()+0.5f); }break; caseMotionEventCompat.ACTION_POINTER_DOWN:{ mScrollPointerId=MotionEventCompat.getPointerId(e,actionIndex); mInitialTouchX=mLastTouchX=(int)(MotionEventCompat.getX(e,actionIndex)+0.5f); mInitialTouchY=mLastTouchY=(int)(MotionEventCompat.getY(e,actionIndex)+0.5f); }break; caseMotionEvent.ACTION_MOVE:{ finalintindex=MotionEventCompat.findPointerIndex(e,mScrollPointerId); if(index<0){ Log.e(TAG,"Errorprocessingscroll;pointerindexforid"+ mScrollPointerId+"notfound.DidanyMotionEventsgetskipped?"); returnfalse; } finalintx=(int)(MotionEventCompat.getX(e,index)+0.5f); finalinty=(int)(MotionEventCompat.getY(e,index)+0.5f); if(mScrollState!=SCROLL_STATE_DRAGGING){ finalintdx=x-mInitialTouchX; finalintdy=y-mInitialTouchY; booleanstartScroll=false; if(canScrollHorizontally&&Math.abs(dx)>mTouchSlop){ mLastTouchX=mInitialTouchX+mTouchSlop*(dx<0?-1:1); startScroll=true; } if(canScrollVertically&&Math.abs(dy)>mTouchSlop){ mLastTouchY=mInitialTouchY+mTouchSlop*(dy<0?-1:1); startScroll=true; } if(startScroll){ getParent().requestDisallowInterceptTouchEvent(true); setScrollState(SCROLL_STATE_DRAGGING); } } if(mScrollState==SCROLL_STATE_DRAGGING){ finalintdx=x-mLastTouchX; finalintdy=y-mLastTouchY; scrollByInternal(canScrollHorizontally?-dx:0, canScrollVertically?-dy:0); } mLastTouchX=x; mLastTouchY=y; }break; caseMotionEventCompat.ACTION_POINTER_UP:{ onPointerUp(e); }break; caseMotionEvent.ACTION_UP:{ mVelocityTracker.computeCurrentVelocity(1000,mMaxFlingVelocity); finalfloatxvel=canScrollHorizontally? -VelocityTrackerCompat.getXVelocity(mVelocityTracker,mScrollPointerId):0; finalfloatyvel=canScrollVertically? -VelocityTrackerCompat.getYVelocity(mVelocityTracker,mScrollPointerId):0; if(!((xvel!=0||yvel!=0)&&fling((int)xvel,(int)yvel))){ setScrollState(SCROLL_STATE_IDLE); } mVelocityTracker.clear(); releaseGlows(); }break; caseMotionEvent.ACTION_CANCEL:{ cancelTouch(); }break; } returntrue; } privatevoidcancelTouch(){ mVelocityTracker.clear(); releaseGlows(); setScrollState(SCROLL_STATE_IDLE); } privatevoidonPointerUp(MotionEvente){ finalintactionIndex=MotionEventCompat.getActionIndex(e); if(MotionEventCompat.getPointerId(e,actionIndex)==mScrollPointerId){ //Pickanewpointertopickuptheslack. finalintnewIndex=actionIndex==0?1:0; mScrollPointerId=MotionEventCompat.getPointerId(e,newIndex); mInitialTouchX=mLastTouchX=(int)(MotionEventCompat.getX(e,newIndex)+0.5f); mInitialTouchY=mLastTouchY=(int)(MotionEventCompat.getY(e,newIndex)+0.5f); } } @Override protectedvoidonMeasure(intwidthSpec,intheightSpec){ if(mAdapterUpdateDuringMeasure){ eatRequestLayout(); updateChildViews(); mAdapterUpdateDuringMeasure=false; resumeRequestLayout(false); } if(mAdapter!=null){ mState.mItemCount=mAdapter.getItemCount(); } mLayout.onMeasure(mRecycler,mState,widthSpec,heightSpec); finalintwidthSize=getMeasuredWidth(); finalintheightSize=getMeasuredHeight(); if(mLeftGlow!=null)mLeftGlow.setSize(heightSize,widthSize); if(mTopGlow!=null)mTopGlow.setSize(widthSize,heightSize); if(mRightGlow!=null)mRightGlow.setSize(heightSize,widthSize); if(mBottomGlow!=null)mBottomGlow.setSize(widthSize,heightSize); } /** *Setsthe{@linkItemAnimator}thatwillhandleanimationsinvolvingchanges *totheitemsinthisRecyclerView.Bydefault,RecyclerViewinstantiatesand *usesaninstanceof{@linkDefaultItemAnimator}.Whetheritemanimationsare *enabledfortheRecyclerViewdependsontheItemAnimatorandwhether *theLayoutManager{@linkLayoutManager#supportsPredictiveItemAnimations() *supportsitemanimations}. * *@paramanimatorTheItemAnimatorbeingset.Ifnull,noanimationswilloccur *whenchangesoccurtotheitemsinthisRecyclerView. */ publicvoidsetItemAnimator(ItemAnimatoranimator){ if(mItemAnimator!=null){ mItemAnimator.setListener(null); } mItemAnimator=animator; if(mItemAnimator!=null){ mItemAnimator.setListener(mItemAnimatorListener); } } /** *GetsthecurrentItemAnimatorforthisRecyclerView.Anullreturnvalue *indicatesthatthereisnoanimatorandthatitemchangeswillhappenwithout *anyanimations.Bydefault,RecyclerViewinstantiatesand *usesaninstanceof{@linkDefaultItemAnimator}. * *@returnItemAnimatorThecurrentItemAnimator.Ifnull,noanimationswilloccur *whenchangesoccurtotheitemsinthisRecyclerView. */ publicItemAnimatorgetItemAnimator(){ returnmItemAnimator; } /** *Postarunnabletothenextframetorunpendingitemanimations.Onlythefirstsuch *requestwillbeposted,governedbythemPostedAnimatorRunnerflag. */ privatevoidpostAnimationRunner(){ if(!mPostedAnimatorRunner&&mIsAttached){ ViewCompat.postOnAnimation(this,mItemAnimatorRunner); mPostedAnimatorRunner=true; } } privatebooleanpredictiveItemAnimationsEnabled(){ return(mItemAnimator!=null&&mLayout.supportsPredictiveItemAnimations()); } /** *WrapperaroundlayoutChildren()thathandlesanimatingchangescausedbylayout. *Animationsworkontheassumptionthattherearefivedifferentkindsofitems *inplay: *PERSISTENT:itemsarevisiblebeforeandafterlayout *REMOVED:itemswerevisiblebeforelayoutandwereremovedbytheapp *ADDED:itemsdidnotexistbeforelayoutandwereaddedbytheapp *DISAPPEARING:itemsexistinthedatasetbefore/after,butchangedfrom *visibletonon-visibleintheprocessoflayout(theyweremovedoff *screenasaside-effectofotherchanges) *APPEARING:itemsexistinthedatasetbefore/after,butchangedfrom *non-visibletovisibleintheprocessoflayout(theyweremovedon *screenasaside-effectofotherchanges) *Theoverallapproachfiguresoutwhatitemsexistbefore/afterlayoutand *infersoneofthefiveabovestatesforeachoftheitems.Thentheanimations *aresetupaccordingly: *PERSISTENTviewsaremoved({@linkItemAnimator#animateMove(ViewHolder,int,int,int,int)}) *REMOVEDviewsareremoved({@linkItemAnimator#animateRemove(ViewHolder)}) *ADDEDviewsareadded({@linkItemAnimator#animateAdd(ViewHolder)}) *DISAPPEARINGviewsaremovedoffscreen *APPEARINGviewsaremovedonscreen */ voiddispatchLayout(){ if(mAdapter==null){ Log.e(TAG,"Noadapterattached;skippinglayout"); return; } eatRequestLayout(); //simpleanimationsareasubsetofadvancedanimations(whichwillcausea //prelayoutstep) booleananimateChangesSimple=mItemAnimator!=null&&mItemsAddedOrRemoved &&!mItemsChanged; finalbooleananimateChangesAdvanced=ENABLE_PREDICTIVE_ANIMATIONS&& animateChangesSimple&&predictiveItemAnimationsEnabled(); mItemsAddedOrRemoved=mItemsChanged=false; ArrayMap<View,Rect>appearingViewInitialBounds=null; mState.mInPreLayout=animateChangesAdvanced; mState.mItemCount=mAdapter.getItemCount(); if(animateChangesSimple){ //Step0:Findoutwhereallnon-removeditemsare,pre-layout mState.mPreLayoutHolderMap.clear(); mState.mPostLayoutHolderMap.clear(); intcount=getChildCount(); for(inti=0;i<count;++i){ finalViewHolderholder=getChildViewHolderInt(getChildAt(i)); finalViewview=holder.itemView; mState.mPreLayoutHolderMap.put(holder,newItemHolderInfo(holder, view.getLeft(),view.getTop(),view.getRight(),view.getBottom(), holder.mPosition)); } } if(animateChangesAdvanced){ //Step1:runprelayout:Thiswillusetheoldpositionsofitems.Thelayoutmanager //isexpectedtolayouteverything,evenremoveditems(thoughnottoaddremoved //itemsbacktothecontainer).Thisgivesthepre-layoutpositionofAPPEARINGviews //whichcomeintoexistenceaspartofthereallayout. mInPreLayout=true; finalbooleandidStructureChange=mState.mStructureChanged; mState.mStructureChanged=false; //temporarilydisableflagbecauseweareaskingforpreviouslayout mLayout.onLayoutChildren(mRecycler,mState); mState.mStructureChanged=didStructureChange; mInPreLayout=false; appearingViewInitialBounds=newArrayMap<View,Rect>(); for(inti=0;i<getChildCount();++i){ booleanfound=false; Viewchild=getChildAt(i); for(intj=0;j<mState.mPreLayoutHolderMap.size();++j){ ViewHolderholder=mState.mPreLayoutHolderMap.keyAt(j); if(holder.itemView==child){ found=true; continue; } } if(!found){ appearingViewInitialBounds.put(child,newRect(child.getLeft(),child.getTop(), child.getRight(),child.getBottom())); } } } clearOldPositions(); dispatchLayoutUpdates(); mState.mItemCount=mAdapter.getItemCount(); //Step2:Runlayout mState.mInPreLayout=false; mLayout.onLayoutChildren(mRecycler,mState); mState.mStructureChanged=false; mPendingSavedState=null; //onLayoutChildrenmayhavecausedclientcodetodisableitemanimations;re-check animateChangesSimple=animateChangesSimple&&mItemAnimator!=null; if(animateChangesSimple){ //Step3:Findoutwherethingsarenow,post-layout intcount=getChildCount(); for(inti=0;i<count;++i){ ViewHolderholder=getChildViewHolderInt(getChildAt(i)); finalViewview=holder.itemView; mState.mPostLayoutHolderMap.put(holder,newItemHolderInfo(holder, view.getLeft(),view.getTop(),view.getRight(),view.getBottom(), holder.mPosition)); } //Step4:AnimateDISAPPEARINGandREMOVEDitems intpreLayoutCount=mState.mPreLayoutHolderMap.size(); for(inti=preLayoutCount-1;i>=0;i--){ ViewHolderitemHolder=mState.mPreLayoutHolderMap.keyAt(i); if(!mState.mPostLayoutHolderMap.containsKey(itemHolder)){ ItemHolderInfodisappearingItem=mState.mPreLayoutHolderMap.valueAt(i); mState.mPreLayoutHolderMap.removeAt(i); ViewdisappearingItemView=disappearingItem.holder.itemView; removeDetachedView(disappearingItemView,false); mRecycler.unscrapView(disappearingItem.holder); animateDisappearance(disappearingItem); } } //Step5:AnimateAPPEARINGandADDEDitems intpostLayoutCount=mState.mPostLayoutHolderMap.size(); if(postLayoutCount>0){ for(inti=postLayoutCount-1;i>=0;i--){ ViewHolderitemHolder=mState.mPostLayoutHolderMap.keyAt(i); ItemHolderInfoinfo=mState.mPostLayoutHolderMap.valueAt(i); if((mState.mPreLayoutHolderMap.isEmpty()|| !mState.mPreLayoutHolderMap.containsKey(itemHolder))){ mState.mPostLayoutHolderMap.removeAt(i); RectinitialBounds=(appearingViewInitialBounds!=null)? appearingViewInitialBounds.get(itemHolder.itemView):null; animateAppearance(itemHolder,initialBounds, info.left,info.top); } } } //Step6:AnimatePERSISTENTitems count=mState.mPostLayoutHolderMap.size(); for(inti=0;i<count;++i){ ViewHolderpostHolder=mState.mPostLayoutHolderMap.keyAt(i); ItemHolderInfopostInfo=mState.mPostLayoutHolderMap.valueAt(i); ItemHolderInfopreInfo=mState.mPreLayoutHolderMap.get(postHolder); if(preInfo!=null&&postInfo!=null){ if(preInfo.left!=postInfo.left||preInfo.top!=postInfo.top){ postHolder.setIsRecyclable(false); if(DEBUG){ Log.d(TAG,"PERSISTENT:"+postHolder+ "withview"+postHolder.itemView); } if(mItemAnimator.animateMove(postHolder, preInfo.left,preInfo.top,postInfo.left,postInfo.top)){ postAnimationRunner(); } } } } } resumeRequestLayout(false); mLayout.removeAndRecycleScrapInt(mRecycler,!animateChangesAdvanced); mState.mPreviousLayoutItemCount=mState.mItemCount; mState.mDeletedInvisibleItemCountSincePreviousLayout=0; } privatevoidanimateAppearance(ViewHolderitemHolder,RectbeforeBounds,intafterLeft, intafterTop){ ViewnewItemView=itemHolder.itemView; if(beforeBounds!=null&& (beforeBounds.left!=afterLeft||beforeBounds.top!=afterTop)){ //slideitemsinifbefore/afterlocationsdiffer itemHolder.setIsRecyclable(false); if(DEBUG){ Log.d(TAG,"APPEARING:"+itemHolder+"withview"+newItemView); } if(mItemAnimator.animateMove(itemHolder, beforeBounds.left,beforeBounds.top, afterLeft,afterTop)){ postAnimationRunner(); } }else{ if(DEBUG){ Log.d(TAG,"ADDED:"+itemHolder+"withview"+newItemView); } itemHolder.setIsRecyclable(false); if(mItemAnimator.animateAdd(itemHolder)){ postAnimationRunner(); } } } privatevoidanimateDisappearance(ItemHolderInfodisappearingItem){ ViewdisappearingItemView=disappearingItem.holder.itemView; addAnimatingView(disappearingItemView); intoldLeft=disappearingItem.left; intoldTop=disappearingItem.top; intnewLeft=disappearingItemView.getLeft(); intnewTop=disappearingItemView.getTop(); if(oldLeft!=newLeft||oldTop!=newTop){ disappearingItem.holder.setIsRecyclable(false); disappearingItemView.layout(newLeft,newTop, newLeft+disappearingItemView.getWidth(), newTop+disappearingItemView.getHeight()); if(DEBUG){ Log.d(TAG,"DISAPPEARING:"+disappearingItem.holder+ "withview"+disappearingItemView); } if(mItemAnimator.animateMove(disappearingItem.holder,oldLeft,oldTop, newLeft,newTop)){ postAnimationRunner(); } }else{ if(DEBUG){ Log.d(TAG,"REMOVED:"+disappearingItem.holder+ "withview"+disappearingItemView); } disappearingItem.holder.setIsRecyclable(false); if(mItemAnimator.animateRemove(disappearingItem.holder)){ postAnimationRunner(); } } } @Override protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){ eatRequestLayout(); dispatchLayout(); resumeRequestLayout(false); mFirstLayoutComplete=true; } @Override publicvoidrequestLayout(){ if(!mEatRequestLayout){ super.requestLayout(); }else{ mLayoutRequestEaten=true; } } voidmarkItemDecorInsetsDirty(){ finalintchildCount=getChildCount(); for(inti=0;i<childCount;i++){ finalViewchild=getChildAt(i); ((LayoutParams)child.getLayoutParams()).mInsetsDirty=true; } } @Override publicvoiddraw(Canvasc){ super.draw(c); finalintcount=mItemDecorations.size(); for(inti=0;i<count;i++){ mItemDecorations.get(i).onDrawOver(c,this); } booleanneedsInvalidate=false; if(mLeftGlow!=null&&!mLeftGlow.isFinished()){ finalintrestore=c.save(); c.rotate(270); c.translate(-getHeight()+getPaddingTop(),0); needsInvalidate=mLeftGlow!=null&&mLeftGlow.draw(c); c.restoreToCount(restore); } if(mTopGlow!=null&&!mTopGlow.isFinished()){ c.translate(getPaddingLeft(),getPaddingTop()); needsInvalidate|=mTopGlow!=null&&mTopGlow.draw(c); } if(mRightGlow!=null&&!mRightGlow.isFinished()){ finalintrestore=c.save(); finalintwidth=getWidth(); c.rotate(90); c.translate(-getPaddingTop(),-width); needsInvalidate|=mRightGlow!=null&&mRightGlow.draw(c); c.restoreToCount(restore); } if(mBottomGlow!=null&&!mBottomGlow.isFinished()){ finalintrestore=c.save(); c.rotate(180); c.translate(-getWidth()+getPaddingLeft(),-getHeight()+getPaddingTop()); needsInvalidate|=mBottomGlow!=null&&mBottomGlow.draw(c); c.restoreToCount(restore); } if(needsInvalidate){ ViewCompat.postInvalidateOnAnimation(this); } } @Override publicvoidonDraw(Canvasc){ super.onDraw(c); finalintcount=mItemDecorations.size(); for(inti=0;i<count;i++){ mItemDecorations.get(i).onDraw(c,this); } } @Override protectedbooleancheckLayoutParams(ViewGroup.LayoutParamsp){ returnpinstanceofLayoutParams&&mLayout.checkLayoutParams((LayoutParams)p); } @Override protectedViewGroup.LayoutParamsgenerateDefaultLayoutParams(){ if(mLayout==null){ thrownewIllegalStateException("RecyclerViewhasnoLayoutManager"); } returnmLayout.generateDefaultLayoutParams(); } @Override publicViewGroup.LayoutParamsgenerateLayoutParams(AttributeSetattrs){ if(mLayout==null){ thrownewIllegalStateException("RecyclerViewhasnoLayoutManager"); } returnmLayout.generateLayoutParams(getContext(),attrs); } @Override protectedViewGroup.LayoutParamsgenerateLayoutParams(ViewGroup.LayoutParamsp){ if(mLayout==null){ thrownewIllegalStateException("RecyclerViewhasnoLayoutManager"); } returnmLayout.generateLayoutParams(p); } privateintfi