解决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