白羽
2018-06-05
来源 :网络
阅读 1622
评论 0
摘要:Android AsyncListUtil是Android官方提供的专为列表这样的数据更新加载提供的异步加载组件。基于AsyncListUtil组件,可以轻易实现常见的RecyclerView分页加载技术。AsyncListUtil技术涉及的细节比较繁复,因此我将分别写若干篇文章,分点、分解AsyncListUtil技术。
Android AsyncListUtil是Android官方提供的专为列表这样的数据更新加载提供的异步加载组件。基于AsyncListUtil组件,可以轻易实现常见的RecyclerView分页加载技术。AsyncListUtil技术涉及的细节比较繁复,因此我将分别写若干篇文章,分点、分解AsyncListUtil技术。
先给出一个可运行的例子,MainActivity.java:
packagezhangphil.app;
importandroid.graphics.Color;
importandroid.os.Bundle;
importandroid.os.SystemClock;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.util.AsyncListUtil;
importandroid.support.v7.widget.LinearLayoutManager;
importandroid.support.v7.widget.RecyclerView;
importandroid.text.TextUtils;
importandroid.util.Log;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.LinearLayout;
importandroid.widget.TextView;
publicclassMainActivityextendsAppCompatActivity{
privateStringTAG=“调试”;
privatefinalintNULL=-1;
privateRecyclerViewmRecyclerView;
privateAsyncListUtilmAsyncListUtil;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView=findViewById(R.id.recycler_view);
LinearLayoutManagermLayoutManager=newLinearLayoutManager(this);
mLayoutManager.setOrientation(LinearLayout.VERTICAL);
mRecyclerView.setLayoutManager(mLayoutManager);
RecyclerView.AdaptermAdapter=newMyAdapter();
mRecyclerView.setAdapter(mAdapter);
MyDataCallbackmDataCallback=newMyDataCallback();
MyViewCallbackmViewCallback=newMyViewCallback();
mAsyncListUtil=newAsyncListUtil(String.class,20,mDataCallback,mViewCallback);
mRecyclerView.addOnScrollListener(newRecyclerView.OnScrollListener(){
@Override
publicvoidonScrollStateChanged(RecyclerViewrecyclerView,intnewState){
super.onScrollStateChanged(recyclerView,newState);
Log.d(TAG,”onRangeChanged”);
mAsyncListUtil.onRangeChanged();
}
});
findViewById(R.id.button).setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewv){
Log.d(TAG,”refresh”);
mAsyncListUtil.refresh();
}
});
}
privateclassMyDataCallbackextendsAsyncListUtil.DataCallback{
@Override
publicintrefreshData(){
//更新数据的元素个数。
//假设预先设定更新若干条。
intcount=Integer.MAX_VALUE;
Log.d(TAG,”refreshData:”+count);
returncount;
}
/**
*在这里完成数据加载的耗时任务。
*
*@paramdata
*@paramstartPosition
*@paramitemCount
*/
@Override
publicvoidfillData(String[]data,intstartPosition,intitemCount){
Log.d(TAG,”fillData:”+startPosition+“,”+itemCount);
for(inti=0;i<itemcount;i++){
· data[i]=String.valueOf(System.currentTimeMillis());
//模拟耗时任务,故意休眠一定时延。
· SystemClock.sleep(100);}
· }
}
·
privateclassMyViewCallbackextendsAsyncListUtil.ViewCallback{
·
/**
· *@paramoutRange
*/
· @Override
publicvoidgetItemRangeInto(int[]outRange){
· getOutRange(outRange);
· /**
*如果当前的RecyclerView为空,主动为用户加载数据.
· *假设预先加载若干条数据
*
· */
if(outRange[0]==NULL&&outRange[1]==NULL){
· Log.d(TAG,”当前RecyclerView为空!”);
outRange[0]=0;
· outRange[1]=9;
}
·
Log.d(TAG,”getItemRangeInto,当前可见position:”+outRange[0]+“~”+outRange[1]);
· }
· @Override
publicvoidonDataRefresh(){
· int[]outRange=newint[2];
getOutRange(outRange);
· mRecyclerView.getAdapter().notifyItemRangeChanged(outRange[0],outRange[1]-outRange[0]+1);
· Log.d(TAG,”onDataRefresh:”+outRange[0]+“,”+outRange[1]);
}
·
@Override
· publicvoidonItemLoaded(intposition){
mRecyclerView.getAdapter().notifyItemChanged(position);
· Log.d(TAG,”onItemLoaded:”+position);
}
· }
· privatevoidgetOutRange(int[]outRange){
outRange[0]=((LinearLayoutManager)mRecyclerView.getLayoutManager()).findFirstVisibleItemPosition();
· outRange[1]=((LinearLayoutManager)mRecyclerView.getLayoutManager()).findLastVisibleItemPosition();
}
·
privateclassMyAdapterextendsRecyclerView.Adapter{
· publicMyAdapter(){
super();
· }
· @Override
publicViewHolderonCreateViewHolder(ViewGroupviewGroup,inti){
· Viewview=LayoutInflater.from(getApplicationContext()).inflate(android.R.layout.simple_list_item_2,null);
ViewHolderholder=newViewHolder(view);
· returnholder;
}
·
@Override
· publicvoidonBindViewHolder(ViewHolderviewHolder,inti){
viewHolder.text1.setText(String.valueOf(i));
·
Strings=String.valueOf(mAsyncListUtil.getItem(i));
· if(TextUtils.equals(s,“null”)){
s=”加载中…”;
· }
· viewHolder.text2.setText(s);
}
·
@Override
· publicintgetItemCount(){
returnmAsyncListUtil.getItemCount();
· }
· publicclassViewHolderextendsRecyclerView.ViewHolder{
publicTextViewtext1;
· publicTextViewtext2;
· publicViewHolder(ViewitemView){
super(itemView);
·
text1=itemView.findViewById(android.R.id.text1);
· text1.setTextColor(Color.RED);
· text2=itemView.findViewById(android.R.id.text2);
text2.setTextColor(Color.BLUE);
· }
}
· }
}
· android:layout_width=“match_parent”android:layout_height=“match_parent”
android:orientation=“vertical”>
·
<button
· · android:id=“@+id/button”
· android:layout_width=“wrap_content”
· android:layout_height=“wrap_content”
android:text=“更新”/>
·
<android.support.v7.widget.recyclerview
· · android:id=“@+id/recycler_view”
· android:layout_width=“match_parent”
· android:layout_height=“match_parent”/>
?
<!--?xml version="1.0" encoding="utf-8"?-->
<linearlayout android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" xmlns:android="//schemas.android.com/apk/res/android"><button android:id="@+id/button" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="更新">
</android.support.v7.widget.recyclerview></button></linearlayout>
· (一)new AsyncListUtil之后Android自动就会启动初次刷新加载。原因在AsyncListUtil构造函数里面,已经调用refresh方法启动刷新,见AsyncListUtil构造函数源代码:
· /**
· *CreatesanAsyncListUtil.
*
· *@paramklassClassofthedataitem.
*@paramtileSizeNumberofitemperchunkloadedatonce.
· *@paramdataCallbackDataaccesscallback.
*@paramviewCallbackCallbackforqueryingvisibleitemrangeandupdatenotifications.
· */
publicAsyncListUtil(Classklass,inttileSize,DataCallbackdataCallback,
· ViewCallbackviewCallback){
mTClass=klass;
· mTileSize=tileSize;
mDataCallback=dataCallback;
· mViewCallback=viewCallback;
· mTileList=newTileList(mTileSize);
· ThreadUtilthreadUtil=newMessageThreadUtil();
mMainThreadProxy=threadUtil.getMainThreadProxy(mMainThreadCallback);
· mBackgroundProxy=threadUtil.getBackgroundProxy(mBackgroundCallback);
· refresh();
}当代码启动后logcat输出:
· 11-2214:41:18.31332764-447/zhangphil.appD/调试:refreshData:2147483647
· 11-2214:41:18.33632764-32764/zhangphil.appD/调试:onDataRefresh:-1,-1
11-2214:41:18.33632764-32764/zhangphil.appD/调试:当前RecyclerView为空!
· 11-2214:41:18.33632764-32764/zhangphil.appD/调试:getItemRangeInto,当前可见position:0~9
11-2214:41:18.33732764-449/zhangphil.appD/调试:fillData:0,20
· 11-2214:41:20.35032764-32764/zhangphil.appD/调试:onItemLoaded:0
11-2214:41:20.35132764-32764/zhangphil.appD/调试:onItemLoaded:1
· 11-2214:41:20.35132764-32764/zhangphil.appD/调试:onItemLoaded:2
11-2214:41:20.35232764-32764/zhangphil.appD/调试:onItemLoaded:3
· 11-2214:41:20.35332764-32764/zhangphil.appD/调试:onItemLoaded:4
11-2214:41:20.35332764-32764/zhangphil.appD/调试:onItemLoaded:5
· 11-2214:41:20.35332764-32764/zhangphil.appD/调试:onItemLoaded:6
11-22 14:41:18.313 32764-447/zhangphil.app D/调试: refreshData:2147483647
11-22 14:41:18.336 32764-32764/zhangphil.app D/调试: onDataRefresh:-1,-1
11-22 14:41:18.336 32764-32764/zhangphil.app D/调试: 当前RecyclerView为空!
11-22 14:41:18.336 32764-32764/zhangphil.app D/调试: getItemRangeInto,当前可见position: 0 ~ 9
11-22 14:41:18.337 32764-449/zhangphil.app D/调试: fillData:0,20
11-22 14:41:20.350 32764-32764/zhangphil.app D/调试: onItemLoaded:0
11-22 14:41:20.351 32764-32764/zhangphil.app D/调试: onItemLoaded:1
11-22 14:41:20.351 32764-32764/zhangphil.app D/调试: onItemLoaded:2
11-22 14:41:20.352 32764-32764/zhangphil.app D/调试: onItemLoaded:3
11-22 14:41:20.353 32764-32764/zhangphil.app D/调试: onItemLoaded:4
11-22 14:41:20.353 32764-32764/zhangphil.app D/调试: onItemLoaded:5
11-22 14:41:20.353 32764-32764/zhangphil.app D/调试: onItemLoaded:6
·
(二)在RecyclerView里面的onScrollStateChanged增加onRangeChanged方法,触发AsyncListUtil的关键函数getItemRangeInto。
触发getItemRangeInto的方法有很多种,通常在RecyclerView里面,分页加载常常会由用户的上下翻动RecyclerView触发。因此自然的就想到在RecyclerView的onScrollStateChanged触发AsyncListUtil分页更新加载逻辑。
getItemRangeInto参数outRange维护两个整型元素,前者outRange[0]表示列表顶部可见元素的位置position,后者outRange[1]表示最底部可见元素的position,开发者对这两个值进行计算,通常就是获取当前RecyclerView顶部outRange[0]的FirstVisibleItemPosition,
outRange[1]是LastVisibleItemPosition。当这两个参数赋值后,将直接触发fillData,fillData是AsyncListUtil进行长期耗时后台任务的地方,开发者可以在这里处理自己的后台线程任务。
· 比如现在手指在屏幕上从下往上翻滚RecyclerView,故意翻到没有数据的地方(position=21 ~position=28)然后加载出来,logcat输出:
·
11-2214:42:35.54332764-32764/zhangphil.appD/调试:getItemRangeInto,当前可见position:0~6 · 11-2214:42:36.01232764-32764/zhangphil.appD/调试:onRangeChanged 11-2214:42:36.01232764-32764/zhangphil.appD/调试:getItemRangeInto,当前可见position:5~12 · 11-2214:42:36.01332764-1011/zhangphil.appD/调试:fillData:20,20 11-2214:42:36.84432764-32764/zhangphil.appD/调试:onRangeChanged · 11-2214:42:36.84432764-32764/zhangphil.appD/调试:getItemRangeInto,当前可见position:10~16 11-2214:42:37.06732764-32764/zhangphil.appD/调试:onRangeChanged · 11-2214:42:37.06732764-32764/zhangphil.appD/调试:getItemRangeInto,当前可见position:13~20 11-2214:42:38.02032764-32764/zhangphil.appD/调试:onItemLoaded:20 · 11-2214:42:38.02032764-32764/zhangphil.appD/调试:onItemLoaded:21 11-2214:42:38.02032764-32764/zhangphil.appD/调试:onItemLoaded:22 · 11-2214:42:38.02032764-32764/zhangphil.appD/调试:onItemLoaded:23 11-2214:42:38.02132764-32764/zhangphil.appD/调试:onItemLoaded:24 · 11-2214:42:38.02132764-32764/zhangphil.appD/调试:onItemLoaded:25 11-2214:42:38.02132764-32764/zhangphil.appD/调试:onItemLoaded:26 · 11-2214:42:38.02132764-32764/zhangphil.appD/调试:onItemLoaded:27 11-2214:42:38.02132764-32764/zhangphil.appD/调试:onItemLoaded:28 · 11-2214:42:38.78432764-32764/zhangphil.appD/调试:onRangeChanged 11-2214:42:38.78432764-32764/zhangphil.appD/调试:getItemRangeInto,当前可见position:21~28
(三)fillData分页加载。
fillData将实现最终的分页加载,通常开发者在这里把数据从网络/数据库/文件系统把数据读出来。本例fillData每次读取20条数据,原因是在AsyncListUtil构造时候,指定了tileSize=20。tileSize决定每次分页加载的数据量。由此,每一次AsyncListUtil分页加载的startPosition位置依次是:0,20,40,60……
(四)onItemLoaded数据装载成功后回调。
当fillData把数据加载完成后,会主动的加载到getItemRangeInto所限定的第一个到最后一个可见范围内的item,此时在RecyclerView里面用notifyItemChanged更新UI即可。
(五)fillData加载的数据覆盖getItemRangeInto返回的第一个到最后一个可见范围内的RecyclerView列表项目。
比如,如果getItemRangeInto返回的两个position:outRange[0]=0,outRange[1]=9,那么fillData将一如既往的加载第0个位置开始的20条数据。即fillData的设计目的将为把用户可见区域内容的所有项目数据均加载完成,保证用户可见区域内的数据是优先加载的。
(六)AsyncListUtil的refresh强制刷新。
· /*
· *
Copyright(C)2015TheAndroidOpenSourceProject
*
· *LicensedundertheApacheLicense,Version2.0(the“License”);
*youmaynotusethisfileexceptincompliancewiththeLicense.
· *YoumayobtainacopyoftheLicenseat
*
· *//www.apache.org/licenses/LICENSE-2.0
*
· *Unlessrequiredbyapplicablelaworagreedtoinwriting,software
*distributedundertheLicenseisdistributedonan“ASIS”BASIS,
· *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
*SeetheLicenseforthespecificlanguagegoverningpermissionsand
· *limitationsundertheLicense.
*/
·
packageandroid.support.v7.util;
·
importandroid.support.annotation.UiThread;
· importandroid.support.annotation.WorkerThread;
importandroid.util.Log;
· importandroid.util.SparseBooleanArray;
importandroid.util.SparseIntArray;
·
/**
· *Autilityclassthatsupportsasynchronouscontentloading.
*
· *ItcanbeusedtoloadCursordatainchunkswithoutqueryingtheCursorontheUIThreadwhile
*keepingUIandcachesynchronousforbetteruserexperience.
· *
· *Itloadsthedataonabackgroundthreadandkeepsonlyalimitednumberoffixedsized
· *chunksinmemoryatalltimes.
*
· *{@linkAsyncListUtil}queriesthecurrentlyvisiblerangethrough{@linkViewCallback},
*loadstherequireddataitemsinthebackgroundthrough{@linkDataCallback},andnotifiesa
· *{@linkViewCallback}whenthedataisloaded.Itmayloadsomeextraitemsforsmoother
*scrolling.
· *
· *Notethatthisclassusesasinglethreadtoloadthedata,soitsuitabletoloaddatafrom
· *secondarystoragesuchasdisk,butnotfromnetwork.
*
· *Thisclassisdesignedtoworkwith{@linkandroid.support.v7.widget.RecyclerView},butitdoes
*notdependonitandcanbeusedwithotherlistviews.
· *
*/
· publicclassAsyncListUtil{
staticfinalStringTAG=“AsyncListUtil”;
·
staticfinalbooleanDEBUG=false;
·
finalClassmTClass;
· finalintmTileSize;
finalDataCallbackmDataCallback;
· finalViewCallbackmViewCallback;
· finalTileListmTileList;
· finalThreadUtil.MainThreadCallbackmMainThreadProxy;
finalThreadUtil.BackgroundCallbackmBackgroundProxy;
·
finalint[]mTmpRange=newint[2];
· finalint[]mPrevRange=newint[2];
finalint[]mTmpRangeExtended=newint[2];
·
booleanmAllowScrollHints;
· privateintmScrollHint=ViewCallback.HINT_SCROLL_NONE;
· intmItemCount=0;
· intmDisplayedGeneration=0;
intmRequestedGeneration=mDisplayedGeneration;
·
finalSparseIntArraymMissingPositions=newSparseIntArray();
·
voidlog(Strings,Object…args){
· Log.d(TAG,”[MAIN]”+String.format(s,args));
}
·
/**
· *CreatesanAsyncListUtil.
*
· *@paramklassClassofthedataitem.
*@paramtileSizeNumberofitemperchunkloadedatonce.
· *@paramdataCallbackDataaccesscallback.
*@paramviewCallbackCallbackforqueryingvisibleitemrangeandupdatenotifications.
· */
publicAsyncListUtil(Classklass,inttileSize,DataCallbackdataCallback,
· ViewCallbackviewCallback){
mTClass=klass;
· mTileSize=tileSize;
mDataCallback=dataCallback;
· mViewCallback=viewCallback;
· mTileList=newTileList(mTileSize);
· ThreadUtilthreadUtil=newMessageThreadUtil();
mMainThreadProxy=threadUtil.getMainThreadProxy(mMainThreadCallback);
· mBackgroundProxy=threadUtil.getBackgroundProxy(mBackgroundCallback);
· refresh();
}
·
privatebooleanisRefreshPending(){
· returnmRequestedGeneration!=mDisplayedGeneration;
}
·
/**
· *Updatesthecurrentlyvisibleitemrange.
*
· *
· *Identifiesthedataitemsthathavenotbeenloadedyetandinitiatesloadingtheminthe
· *background.Shouldbecalledfromtheview’sscrolllistener(suchas
*{@linkandroid.support.v7.widget.RecyclerView.OnScrollListener#onScrolled}).
· */
publicvoidonRangeChanged(){
· if(isRefreshPending()){
return;//Willupdaterangewilltherefreshresultarrives.
· }
updateRange();
· mAllowScrollHints=true;
}
·
/**
· *Forcesreloadingthedata.
*
· *Discardsallthecacheddataandreloadsallrequireddataitemsforthecurrentlyvisible
*range.Tobecalledwhenthedataitemcountand/orcontentshaschanged.
· */
publicvoidrefresh(){
· mMissingPositions.clear();
mBackgroundProxy.refresh(++mRequestedGeneration);
· }
· /**
*Returnsthedataitematthegivenpositionornullifithasnotbeenloaded
· *yet.
*
· *
· *Ifthismethodhasbeencalledforaspecificpositionandreturnednull,then
· *{@linkViewCallback#onItemLoaded(int)}willbecalledwhenitfinallyloads.Notethatif
*thispositionstaysoutsideofthecacheditemrange(asdefinedby
· *{@linkViewCallback#extendRangeInto}method),thenthecallbackwillneverbecalledfor
*thisposition.
· *
*@parampositionItemposition.
· *
*@returnThedataitematthegivenpositionornullifithasnotbeenloaded
· *yet.
*/
· publicTgetItem(intposition){
if(position<0||position>=mItemCount){
· thrownewIndexOutOfBoundsException(position+“isnotwithin0and”+mItemCount);
}
· Titem=mTileList.getItemAt(position);
if(item==null&&!isRefreshPending()){
· mMissingPositions.put(position,0);
}
· returnitem;
}
·
/**
· *Returnsthenumberofitemsinthedataset.
*
· *
· *Thisisthenumberreturnedbyarecentcallto
· *{@linkDataCallback#refreshData()}.
*
· *@returnNumberofitems.
*/
· publicintgetItemCount(){
returnmItemCount;
· }
· voidupdateRange(){
mViewCallback.getItemRangeInto(mTmpRange);
· if(mTmpRange[0]>mTmpRange[1]||mTmpRange[0]<0){
return;
· }
if(mTmpRange[1]>=mItemCount){
· //Invalidrangemayarrivesoonaftertherefresh.
return;
· }
· if(!mAllowScrollHints){
mScrollHint=ViewCallback.HINT_SCROLL_NONE;
· }elseif(mTmpRange[0]>mPrevRange[1]||mPrevRange[0]>mTmpRange[1]){
//Rangesdonotintersect,longleapnotascroll.
· mScrollHint=ViewCallback.HINT_SCROLL_NONE;
}elseif(mTmpRange[0]<mprevrange[0]){
· · mScrollHint=ViewCallback.HINT_SCROLL_DESC;
· }elseif(mTmpRange[0]>mPrevRange[0]){
· mScrollHint=ViewCallback.HINT_SCROLL_ASC;
}
·
mPrevRange[0]=mTmpRange[0];
· mPrevRange[1]=mTmpRange[1];
· mViewCallback.extendRangeInto(mTmpRange,mTmpRangeExtended,mScrollHint);
mTmpRangeExtended[0]=Math.min(mTmpRange[0],Math.max(mTmpRangeExtended[0],0));
· mTmpRangeExtended[1]=
Math.max(mTmpRange[1],Math.min(mTmpRangeExtended[1],mItemCount-1));
·
mBackgroundProxy.updateRange(mTmpRange[0],mTmpRange[1],
· mTmpRangeExtended[0],mTmpRangeExtended[1],mScrollHint);
}
·
privatefinalThreadUtil.MainThreadCallback
· mMainThreadCallback=newThreadUtil.MainThreadCallback(){
@Override
· publicvoidupdateItemCount(intgeneration,intitemCount){
if(DEBUG){
· log(”updateItemCount:size=%d,gen#%d”,itemCount,generation);
}
· if(!isRequestedGeneration(generation)){
return;
· }
mItemCount=itemCount;
· mViewCallback.onDataRefresh();
mDisplayedGeneration=mRequestedGeneration;
· recycleAllTiles();
· mAllowScrollHints=false;//Willbesettotrueafterafirstrealscroll.
//Therewillbenoscrolleventifthesizechangedoesnotaffectthecurrentrange.
· updateRange();
}
·
@Override
· publicvoidaddTile(intgeneration,TileList.Tiletile){
if(!isRequestedGeneration(generation)){
· if(DEBUG){
log(”recyclinganoldergenerationtile@%d”,tile.mStartPosition);
· }
mBackgroundProxy.recycleTile(tile);
· return;
}
· TileList.Tileduplicate=mTileList.addOrReplace(tile);
if(duplicate!=null){
· Log.e(TAG,”duplicatetile@”+duplicate.mStartPosition);
mBackgroundProxy.recycleTile(duplicate);
· }
if(DEBUG){
· log(”gen#%d,addedtile@%d,totaltiles:%d”,
generation,tile.mStartPosition,mTileList.size());
· }
intendPosition=tile.mStartPosition+tile.mItemCount;
· intindex=0;
while(index<mmissingpositions.size()){
· · finalintposition=mMissingPositions.keyAt(index);
· if(tile.mStartPosition<=position&&position<endposition){
· · mMissingPositions.removeAt(index);
· mViewCallback.onItemLoaded(position);
· }else{
index++;
· }
}
· }
· @Override
publicvoidremoveTile(intgeneration,intposition){
· if(!isRequestedGeneration(generation)){
return;
· }
TileList.Tiletile=mTileList.removeAtPos(position);
· if(tile==null){
Log.e(TAG,”tilenotfound@”+position);
· return;
}
· if(DEBUG){
log(”recyclingtile@%d,totaltiles:%d”,tile.mStartPosition,mTileList.size());
· }
mBackgroundProxy.recycleTile(tile);
· }
· privatevoidrecycleAllTiles(){
if(DEBUG){
· log(”recyclingall%dtiles”,mTileList.size());
}
· for(inti=0;i<mtilelist.size();i++){
· mBackgroundProxy.recycleTile(mTileList.getAtIndex(i));}
mTileList.clear();
· }
· privatebooleanisRequestedGeneration(intgeneration){
returngeneration==mRequestedGeneration;
· }
};
·
privatefinalThreadUtil.BackgroundCallback
· mBackgroundCallback=newThreadUtil.BackgroundCallback(){
· privateTileList.TilemRecycledRoot;
· finalSparseBooleanArraymLoadedTiles=newSparseBooleanArray();
· privateintmGeneration;
privateintmItemCount;
·
privateintmFirstRequiredTileStart;
· privateintmLastRequiredTileStart;
· @Override
publicvoidrefresh(intgeneration){
· mGeneration=generation;
mLoadedTiles.clear();
· mItemCount=mDataCallback.refreshData();
mMainThreadProxy.updateItemCount(mGeneration,mItemCount);
· }
· @Override
publicvoidupdateRange(intrangeStart,intrangeEnd,intextRangeStart,intextRangeEnd,
· intscrollHint){
if(DEBUG){
· log(”updateRange:%d..%dextendedto%d..%d,scrollhint:%d”,
rangeStart,rangeEnd,extRangeStart,extRangeEnd,scrollHint);
· }
· if(rangeStart>rangeEnd){
return;
· }
· finalintfirstVisibleTileStart=getTileStart(rangeStart);
finalintlastVisibleTileStart=getTileStart(rangeEnd);
·
mFirstRequiredTileStart=getTileStart(extRangeStart);
· mLastRequiredTileStart=getTileStart(extRangeEnd);
if(DEBUG){
· log(”requestingtilerange:%d..%d”,
mFirstRequiredTileStart,mLastRequiredTileStart);
· }
· //AllpendingtilerequestsareremovedbyThreadUtilatthispoint.
//Re-requestallrequiredtilesinthemostoptimalorder.
· if(scrollHint==ViewCallback.HINT_SCROLL_DESC){
requestTiles(mFirstRequiredTileStart,lastVisibleTileStart,scrollHint,true);
· requestTiles(lastVisibleTileStart+mTileSize,mLastRequiredTileStart,scrollHint,
false);
· }else{
requestTiles(firstVisibleTileStart,mLastRequiredTileStart,scrollHint,false);
· requestTiles(mFirstRequiredTileStart,firstVisibleTileStart-mTileSize,scrollHint,
true);
· }
}
·
privateintgetTileStart(intposition){
· returnposition-position%mTileSize;
}
·
privatevoidrequestTiles(intfirstTileStart,intlastTileStart,intscrollHint,
· booleanbackwards){
for(inti=firstTileStart;i<=lastTileStart;i+=mTileSize){
· inttileStart=backwards?(lastTileStart+firstTileStart-i):i;
if(DEBUG){
· log(”requestingtile@%d”,tileStart);
}
· mBackgroundProxy.loadTile(tileStart,scrollHint);
}
· }
· @Override
publicvoidloadTile(intposition,intscrollHint){
· if(isTileLoaded(position)){
if(DEBUG){
· log(”alreadyloadedtile@%d”,position);
}
· return;
}
· TileList.Tiletile=acquireTile();
tile.mStartPosition=position;
· tile.mItemCount=Math.min(mTileSize,mItemCount-tile.mStartPosition);
mDataCallback.fillData(tile.mItems,tile.mStartPosition,tile.mItemCount);
· flushTileCache(scrollHint);
addTile(tile);
· }
· @Override
publicvoidrecycleTile(TileList.Tiletile){
· if(DEBUG){
log(”recyclingtile@%d”,tile.mStartPosition);
· }
mDataCallback.recycleData(tile.mItems,tile.mItemCount);
·
tile.mNext=mRecycledRoot;
· mRecycledRoot=tile;
}
·
privateTileList.TileacquireTile(){
· if(mRecycledRoot!=null){
TileList.Tileresult=mRecycledRoot;
· mRecycledRoot=mRecycledRoot.mNext;
returnresult;
· }
returnnewTileList.Tile(mTClass,mTileSize);
· }
· privatebooleanisTileLoaded(intposition){
returnmLoadedTiles.get(position);
· }
· privatevoidaddTile(TileList.Tiletile){
mLoadedTiles.put(tile.mStartPosition,true);
· mMainThreadProxy.addTile(mGeneration,tile);
if(DEBUG){
· log(”loadedtile@%d,totaltiles:%d”,tile.mStartPosition,mLoadedTiles.size());
}
· }
· privatevoidremoveTile(intposition){
mLoadedTiles.delete(position);
· mMainThreadProxy.removeTile(mGeneration,position);
if(DEBUG){
· log(”flushedtile@%d,totaltiles:%s”,position,mLoadedTiles.size());
}
· }
· privatevoidflushTileCache(intscrollHint){
finalintcacheSizeLimit=mDataCallback.getMaxCachedTiles();
· while(mLoadedTiles.size()>=cacheSizeLimit){
intfirstLoadedTileStart=mLoadedTiles.keyAt(0);
· intlastLoadedTileStart=mLoadedTiles.keyAt(mLoadedTiles.size()-1);
intstartMargin=mFirstRequiredTileStart-firstLoadedTileStart;
· intendMargin=lastLoadedTileStart-mLastRequiredTileStart;
if(startMargin>0&&(startMargin>=endMargin||
· (scrollHint==ViewCallback.HINT_SCROLL_ASC))){
removeTile(firstLoadedTileStart);
· }elseif(endMargin>0&&(startMargin<endmargin||
· (scrollHint==ViewCallback.HINT_SCROLL_DESC))){removeTile(lastLoadedTileStart);
}else{
· //Couldnotflushoneitherside,bailout.
return;
· }
}
· }
· privatevoidlog(Strings,Object…args){
Log.d(TAG,”[BKGR]”+String.format(s,args));
· }
};
·
/**
· *Thecallbackthatprovidesdataaccessfor{@linkAsyncListUtil}.
*
· *
· *Allmethodsarecalledonthebackgroundthread.
· */
publicstaticabstractclassDataCallback{
·
/**
· *Refreshthedatasetandreturnthenewdataitemcount.
*
· *
· *Ifthedataisbeingaccessedthrough{@linkandroid.database.Cursor}thisiswhere
· *thenewcursorshouldbecreated.
*
· *@returnDataitemcount.
*/
· @WorkerThread
publicabstractintrefreshData();
·
/**
· *Fillthegiventile.
*
· *
· *Theprovidedtilemightbearecycledtile,inwhichcaseitwillalreadyhaveobjects.
· *Itissuggestedtore-usetheseobjectsifpossibleinyourusecase.
*
· *@paramstartPositionThestartpositioninthelist.
*@paramitemCountThedataitemcount.
· *@paramdataThedataitemarraytofillinto.Shouldnotbeaccessedbeyond
*itemCount.
· */
@WorkerThread
· publicabstractvoidfillData(T[]data,intstartPosition,intitemCount);
· /**
*Recycletheobjectscreatedin{@link#fillData}ifnecessary.
· *
*
· *@paramdataArrayofdataitems.ShouldnotbeaccessedbeyonditemCount.
*@paramitemCountThedataitemcount.
· */
@WorkerThread
· publicvoidrecycleData(T[]data,intitemCount){
}
·
/**
· *Returnstilecachesizelimit(intiles).
*
· *
· *Theactualnumberofcachedtileswillbethemaximumofthisvalueandthenumberof
· *tilesthatisrequiredtocovertherangereturnedby
*{@linkViewCallback#extendRangeInto(int[],int[],int)}.
· *
· *Forexample,ifthismethodreturns10,andthemost
· *recentcallto{@linkViewCallback#extendRangeInto(int[],int[],int)}returned
*{100,179},andthetilesizeis5,thenthemaximumnumberofcachedtileswillbe16.
· *
· *However,ifthetilesizeis20,thenthemaximumnumberofcachedtileswillbe10.
· *
· *Thedefaultimplementationreturns10.
· *
*@returnMaximumcachesize.
· */
@WorkerThread
· publicintgetMaxCachedTiles(){
return10;
· }
}
·
/**
· *Thecallbackthatlinks{@linkAsyncListUtil}withthelistview.
*
· *
· *Allmethodsarecalledonthemainthread.
· */
publicstaticabstractclassViewCallback{
·
/**
· *Noscrolldirectionhintavailable.
*/
· publicstaticfinalintHINT_SCROLL_NONE=0;
· /**
*Scrollingindescendingorder(fromhighertolowerpositionsintheorderofthebacking
· *storage).
*/
· publicstaticfinalintHINT_SCROLL_DESC=1;
· /**
*Scrollinginascendingorder(fromlowertohigherpositionsintheorderofthebacking
· *storage).
*/
· publicstaticfinalintHINT_SCROLL_ASC=2;
· /**
*Computetherangeofvisibleitempositions.
· *
· *outRange[0]isthepositionofthefirstvisibleitem(intheorderofthebacking
· *storage).
*
· *outRange[1]isthepositionofthelastvisibleitem(intheorderofthebacking
*storage).
· *
· *Negativepositionsandpositionsgreaterorequalto{@link#getItemCount}areinvalid.
· *Ifthereturnedrangecontainsinvalidpositionsitisignored(noitemwillbeloaded).
*
· *@paramoutRangeThevisibleitemrange.
*/
· @UiThread
publicabstractvoidgetItemRangeInto(int[]outRange);
·
/**
· *Computeawiderrangeofitemsthatwillbeloadedforsmootherscrolling.
*
· *
· *Ifthereisnoscrollhint,thedefaultimplementationextendsthevisiblerangebyhalf
· *itslengthinbothdirections.Ifthereisascrollhint,therangeisextendedby
*itsfulllengthinthescrolldirection,andbyhalfintheotherdirection.
· *
· *Forexample,ifrangeis{100,200}andscrollHint
· *is{@link#HINT_SCROLL_ASC},thenoutRangewillbe{50,300}.
*
· *However,ifscrollHintis{@link#HINT_SCROLL_NONE},then
*outRangewillbe{50,250}
· *
*@paramrangeVisibleitemrange.
· *@paramoutRangeExtendedrange.
*@paramscrollHintThescrolldirectionhint.
· */
@UiThread
· publicvoidextendRangeInto(int[]range,int[]outRange,intscrollHint){
finalintfullRange=range[1]-range[0]+1;
· finalinthalfRange=fullRange/2;
outRange[0]=range[0]-(scrollHint==HINT_SCROLL_DESC?fullRange:halfRange);
· outRange[1]=range[1]+(scrollHint==HINT_SCROLL_ASC?fullRange:halfRange);
}
·
/**
· *Calledwhentheentiredatasethaschanged.
*/
· @UiThread
publicabstractvoidonDataRefresh();
·
/**
· *Calledwhenanitematthegivenpositionisloaded.
*@parampositionItemposition.
· */
@UiThread
· publicabstractvoidonItemLoaded(intposition);
}
<code><code><code>/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.util;
import android.support.annotation.UiThread;
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
/**
* A utility class that supports asynchronous content loading.
* </code></code></code>
· * It can be used to load Cursor data in chunks without querying the Cursor on the UI Thread while * keeping UI and cache synchronous for better user experience. *
· * It loads the data on a background thread and keeps only a limited number of fixed sized * chunks in memory at all times. *
· * {@link AsyncListUtil} queries the currently visible range through {@link ViewCallback}, * loads the required data items in the background through {@link DataCallback}, and notifies a * {@link ViewCallback} when the data is loaded. It may load some extra items for smoother * scrolling. *
· * Note that this class uses a single thread to load the data, so it suitable to load data from * secondary storage such as disk, but not from network. *
· * This class is designed to work with {@link android.support.v7.widget.RecyclerView}, but it does * not depend on it and can be used with other list views. * */ public class AsyncListUtil { static final String TAG = "AsyncListUtil"; static final boolean DEBUG = false; final Class mTClass; final int mTileSize; final DataCallback mDataCallback; final ViewCallback mViewCallback; final TileList mTileList; final ThreadUtil.MainThreadCallback mMainThreadProxy; final ThreadUtil.BackgroundCallback mBackgroundProxy; final int[] mTmpRange = new int[2]; final int[] mPrevRange = new int[2]; final int[] mTmpRangeExtended = new int[2]; boolean mAllowScrollHints; private int mScrollHint = ViewCallback.HINT_SCROLL_NONE; int mItemCount = 0; int mDisplayedGeneration = 0; int mRequestedGeneration = mDisplayedGeneration; final SparseIntArray mMissingPositions = new SparseIntArray(); void log(String s, Object... args) { Log.d(TAG, "[MAIN] " + String.format(s, args)); } /** * Creates an AsyncListUtil. * * @param klass Class of the data item. * @param tileSize Number of item per chunk loaded at once. * @param dataCallback Data access callback. * @param viewCallback Callback for querying visible item range and update notifications. */ public AsyncListUtil(Class klass, int tileSize, DataCallback dataCallback, ViewCallback viewCallback) { mTClass = klass; mTileSize = tileSize; mDataCallback = dataCallback; mViewCallback = viewCallback; mTileList = new TileList(mTileSize); ThreadUtil threadUtil = new MessageThreadUtil(); mMainThreadProxy = threadUtil.getMainThreadProxy(mMainThreadCallback); mBackgroundProxy = threadUtil.getBackgroundProxy(mBackgroundCallback); refresh(); } private boolean isRefreshPending() { return mRequestedGeneration != mDisplayedGeneration; } /** * Updates the currently visible item range. * *
· * Identifies the data items that have not been loaded yet and initiates loading them in the * background. Should be called from the view's scroll listener (such as * {@link android.support.v7.widget.RecyclerView.OnScrollListener#onScrolled}). */ public void onRangeChanged() { if (isRefreshPending()) { return; // Will update range will the refresh result arrives. } updateRange(); mAllowScrollHints = true; } /** * Forces reloading the data. *
· * Discards all the cached data and reloads all required data items for the currently visible * range. To be called when the data item count and/or contents has changed. */ public void refresh() { mMissingPositions.clear(); mBackgroundProxy.refresh(++mRequestedGeneration); } /** * Returns the data item at the given position or null if it has not been loaded * yet. * *
· * If this method has been called for a specific position and returned null, then * {@link ViewCallback#onItemLoaded(int)} will be called when it finally loads. Note that if * this position stays outside of the cached item range (as defined by * {@link ViewCallback#extendRangeInto} method), then the callback will never be called for * this position. * * @param position Item position. * * @return The data item at the given position or null if it has not been loaded * yet. */ public T getItem(int position) { if (position < 0 || position >= mItemCount) { throw new IndexOutOfBoundsException(position + " is not within 0 and " + mItemCount); } T item = mTileList.getItemAt(position); if (item == null && !isRefreshPending()) { mMissingPositions.put(position, 0); } return item; } /** * Returns the number of items in the data set. * *
· * This is the number returned by a recent call to * {@link DataCallback#refreshData()}. * * @return Number of items. */ public int getItemCount() { return mItemCount; } void updateRange() { mViewCallback.getItemRangeInto(mTmpRange); if (mTmpRange[0] > mTmpRange[1] || mTmpRange[0] < 0) { return; } if (mTmpRange[1] >= mItemCount) { // Invalid range may arrive soon after the refresh. return; } if (!mAllowScrollHints) { mScrollHint = ViewCallback.HINT_SCROLL_NONE; } else if (mTmpRange[0] > mPrevRange[1] || mPrevRange[0] > mTmpRange[1]) { // Ranges do not intersect, long leap not a scroll. mScrollHint = ViewCallback.HINT_SCROLL_NONE; } else if (mTmpRange[0] < mPrevRange[0]) { mScrollHint = ViewCallback.HINT_SCROLL_DESC; } else if (mTmpRange[0] > mPrevRange[0]) { mScrollHint = ViewCallback.HINT_SCROLL_ASC; } mPrevRange[0] = mTmpRange[0]; mPrevRange[1] = mTmpRange[1]; mViewCallback.extendRangeInto(mTmpRange, mTmpRangeExtended, mScrollHint); mTmpRangeExtended[0] = Math.min(mTmpRange[0], Math.max(mTmpRangeExtended[0], 0)); mTmpRangeExtended[1] = Math.max(mTmpRange[1], Math.min(mTmpRangeExtended[1], mItemCount - 1)); mBackgroundProxy.updateRange(mTmpRange[0], mTmpRange[1], mTmpRangeExtended[0], mTmpRangeExtended[1], mScrollHint); } private final ThreadUtil.MainThreadCallback mMainThreadCallback = new ThreadUtil.MainThreadCallback() { @Override public void updateItemCount(int generation, int itemCount) { if (DEBUG) { log("updateItemCount: size=%d, gen #%d", itemCount, generation); } if (!isRequestedGeneration(generation)) { return; } mItemCount = itemCount; mViewCallback.onDataRefresh(); mDisplayedGeneration = mRequestedGeneration; recycleAllTiles(); mAllowScrollHints = false; // Will be set to true after a first real scroll. // There will be no scroll event if the size change does not affect the current range. updateRange(); } @Override public void addTile(int generation, TileList.Tile tile) { if (!isRequestedGeneration(generation)) { if (DEBUG) { log("recycling an older generation tile @%d", tile.mStartPosition); } mBackgroundProxy.recycleTile(tile); return; } TileList.Tile duplicate = mTileList.addOrReplace(tile); if (duplicate != null) { Log.e(TAG, "duplicate tile @" + duplicate.mStartPosition); mBackgroundProxy.recycleTile(duplicate); } if (DEBUG) { log("gen #%d, added tile @%d, total tiles: %d", generation, tile.mStartPosition, mTileList.size()); } int endPosition = tile.mStartPosition + tile.mItemCount; int index = 0; while (index < mMissingPositions.size()) { final int position = mMissingPositions.keyAt(index); if (tile.mStartPosition <= position && position < endPosition) { mMissingPositions.removeAt(index); mViewCallback.onItemLoaded(position); } else { index++; } } } @Override public void removeTile(int generation, int position) { if (!isRequestedGeneration(generation)) { return; } TileList.Tile tile = mTileList.removeAtPos(position); if (tile == null) { Log.e(TAG, "tile not found @" + position); return; } if (DEBUG) { log("recycling tile @%d, total tiles: %d", tile.mStartPosition, mTileList.size()); } mBackgroundProxy.recycleTile(tile); } private void recycleAllTiles() { if (DEBUG) { log("recycling all %d tiles", mTileList.size()); } for (int i = 0; i < mTileList.size(); i++) { mBackgroundProxy.recycleTile(mTileList.getAtIndex(i)); } mTileList.clear(); } private boolean isRequestedGeneration(int generation) { return generation == mRequestedGeneration; } }; private final ThreadUtil.BackgroundCallback mBackgroundCallback = new ThreadUtil.BackgroundCallback() { private TileList.Tile mRecycledRoot; final SparseBooleanArray mLoadedTiles = new SparseBooleanArray(); private int mGeneration; private int mItemCount; private int mFirstRequiredTileStart; private int mLastRequiredTileStart; @Override public void refresh(int generation) { mGeneration = generation; mLoadedTiles.clear(); mItemCount = mDataCallback.refreshData(); mMainThreadProxy.updateItemCount(mGeneration, mItemCount); } @Override public void updateRange(int rangeStart, int rangeEnd, int extRangeStart, int extRangeEnd, int scrollHint) { if (DEBUG) { log("updateRange: %d..%d extended to %d..%d, scroll hint: %d", rangeStart, rangeEnd, extRangeStart, extRangeEnd, scrollHint); } if (rangeStart > rangeEnd) { return; } final int firstVisibleTileStart = getTileStart(rangeStart); final int lastVisibleTileStart = getTileStart(rangeEnd); mFirstRequiredTileStart = getTileStart(extRangeStart); mLastRequiredTileStart = getTileStart(extRangeEnd); if (DEBUG) { log("requesting tile range: %d..%d", mFirstRequiredTileStart, mLastRequiredTileStart); } // All pending tile requests are removed by ThreadUtil at this point. // Re-request all required tiles in the most optimal order. if (scrollHint == ViewCallback.HINT_SCROLL_DESC) { requestTiles(mFirstRequiredTileStart, lastVisibleTileStart, scrollHint, true); requestTiles(lastVisibleTileStart + mTileSize, mLastRequiredTileStart, scrollHint, false); } else { requestTiles(firstVisibleTileStart, mLastRequiredTileStart, scrollHint, false); requestTiles(mFirstRequiredTileStart, firstVisibleTileStart - mTileSize, scrollHint, true); } } private int getTileStart(int position) { return position - position % mTileSize; } private void requestTiles(int firstTileStart, int lastTileStart, int scrollHint, boolean backwards) { for (int i = firstTileStart; i <= lastTileStart; i += mTileSize) { int tileStart = backwards ? (lastTileStart + firstTileStart - i) : i; if (DEBUG) { log("requesting tile @%d", tileStart); } mBackgroundProxy.loadTile(tileStart, scrollHint); } } @Override public void loadTile(int position, int scrollHint) { if (isTileLoaded(position)) { if (DEBUG) { log("already loaded tile @%d", position); } return; } TileList.Tile tile = acquireTile(); tile.mStartPosition = position; tile.mItemCount = Math.min(mTileSize, mItemCount - tile.mStartPosition); mDataCallback.fillData(tile.mItems, tile.mStartPosition, tile.mItemCount); flushTileCache(scrollHint); addTile(tile); } @Override public void recycleTile(TileList.Tile tile) { if (DEBUG) { log("recycling tile @%d", tile.mStartPosition); } mDataCallback.recycleData(tile.mItems, tile.mItemCount); tile.mNext = mRecycledRoot; mRecycledRoot = tile; } private TileList.Tile acquireTile() { if (mRecycledRoot != null) { TileList.Tile result = mRecycledRoot; mRecycledRoot = mRecycledRoot.mNext; return result; } return new TileList.Tile(mTClass, mTileSize); } private boolean isTileLoaded(int position) { return mLoadedTiles.get(position); } private void addTile(TileList.Tile tile) { mLoadedTiles.put(tile.mStartPosition, true); mMainThreadProxy.addTile(mGeneration, tile); if (DEBUG) { log("loaded tile @%d, total tiles: %d", tile.mStartPosition, mLoadedTiles.size()); } } private void removeTile(int position) { mLoadedTiles.delete(position); mMainThreadProxy.removeTile(mGeneration, position); if (DEBUG) { log("flushed tile @%d, total tiles: %s", position, mLoadedTiles.size()); } } private void flushTileCache(int scrollHint) { final int cacheSizeLimit = mDataCallback.getMaxCachedTiles(); while (mLoadedTiles.size() >= cacheSizeLimit) { int firstLoadedTileStart = mLoadedTiles.keyAt(0); int lastLoadedTileStart = mLoadedTiles.keyAt(mLoadedTiles.size() - 1); int startMargin = mFirstRequiredTileStart - firstLoadedTileStart; int endMargin = lastLoadedTileStart - mLastRequiredTileStart; if (startMargin > 0 && (startMargin >= endMargin || (scrollHint == ViewCallback.HINT_SCROLL_ASC))) { removeTile(firstLoadedTileStart); } else if (endMargin > 0 && (startMargin < endMargin || (scrollHint == ViewCallback.HINT_SCROLL_DESC))){ removeTile(lastLoadedTileStart); } else { // Could not flush on either side, bail out. return; } } } private void log(String s, Object... args) { Log.d(TAG, "[BKGR] " + String.format(s, args)); } }; /** * The callback that provides data access for {@link AsyncListUtil}. * *
· * All methods are called on the background thread. */ public static abstract class DataCallback { /** * Refresh the data set and return the new data item count. * *
· * If the data is being accessed through {@link android.database.Cursor} this is where * the new cursor should be created. * * @return Data item count. */ @WorkerThread public abstract int refreshData(); /** * Fill the given tile. * *
· * The provided tile might be a recycled tile, in which case it will already have objects. * It is suggested to re-use these objects if possible in your use case. * * @param startPosition The start position in the list. * @param itemCount The data item count. * @param data The data item array to fill into. Should not be accessed beyond * itemCount. */ @WorkerThread public abstract void fillData(T[] data, int startPosition, int itemCount); /** * Recycle the objects created in {@link #fillData} if necessary. * * * @param data Array of data items. Should not be accessed beyond itemCount. * @param itemCount The data item count. */ @WorkerThread public void recycleData(T[] data, int itemCount) { } /** * Returns tile cache size limit (in tiles). * *
· * The actual number of cached tiles will be the maximum of this value and the number of * tiles that is required to cover the range returned by * {@link ViewCallback#extendRangeInto(int[], int[], int)}. *
· * For example, if this method returns 10, and the most * recent call to {@link ViewCallback#extendRangeInto(int[], int[], int)} returned * {100, 179}, and the tile size is 5, then the maximum number of cached tiles will be 16. *
· * However, if the tile size is 20, then the maximum number of cached tiles will be 10. *
· * The default implementation returns 10. * * @return Maximum cache size. */ @WorkerThread public int getMaxCachedTiles() { return 10; } } /** * The callback that links {@link AsyncListUtil} with the list view. * *
· * All methods are called on the main thread. */ public static abstract class ViewCallback { /** * No scroll direction hint available. */ public static final int HINT_SCROLL_NONE = 0; /** * Scrolling in descending order (from higher to lower positions in the order of the backing * storage). */ public static final int HINT_SCROLL_DESC = 1; /** * Scrolling in ascending order (from lower to higher positions in the order of the backing * storage). */ public static final int HINT_SCROLL_ASC = 2; /** * Compute the range of visible item positions. *
· * outRange[0] is the position of the first visible item (in the order of the backing * storage). *
· * outRange[1] is the position of the last visible item (in the order of the backing * storage). *
· * Negative positions and positions greater or equal to {@link #getItemCount} are invalid. * If the returned range contains invalid positions it is ignored (no item will be loaded). * * @param outRange The visible item range. */ @UiThread public abstract void getItemRangeInto(int[] outRange); /** * Compute a wider range of items that will be loaded for smoother scrolling. * *
· * If there is no scroll hint, the default implementation extends the visible range by half * its length in both directions. If there is a scroll hint, the range is extended by * its full length in the scroll direction, and by half in the other direction. *
· * For example, if range is {100, 200} and scrollHint * is {@link #HINT_SCROLL_ASC}, then outRange will be {50, 300}. *
· * However, if scrollHint is {@link #HINT_SCROLL_NONE}, then * outRange will be {50, 250} * * @param range Visible item range. * @param outRange Extended range. * @param scrollHint The scroll direction hint. */ @UiThread public void extendRangeInto(int[] range, int[] outRange, int scrollHint) { final int fullRange = range[1] - range[0] + 1; final int halfRange = fullRange / 2; outRange[0] = range[0] - (scrollHint == HINT_SCROLL_DESC ? fullRange : halfRange); outRange[1] = range[1] + (scrollHint == HINT_SCROLL_ASC ? fullRange : halfRange); } /** * Called when the entire data set has changed. */ @UiThread public abstract void onDataRefresh(); /** * Called when an item at the given position is loaded. * @param position Item position. */ @UiThread public abstract void onItemLoaded(int position); } }·
本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之Android频道!
喜欢 | 0
不喜欢 | 0
您输入的评论内容中包含违禁敏感词
我知道了

请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式AI+学习就业服务平台 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号