基于Android官方AsyncListUtil优化改进RecyclerView分页加载机制(一)
基于android官方asynclistutil优化改进recyclerview分页加载机制(一)。
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//模拟耗时任务,故意休眠一定时延。
systemclock.sleep(100);}
}}
privateclassmyviewcallbackextendsasynclistutil.viewcallback{
/**
*@paramoutrange*/
@overridepublicvoidgetitemrangeinto(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
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”>
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”android:text=“更新”/>
android:layout_width=“match_parent”
android:layout_height=“match_parent”/>
(一)new asynclistutil之后android自动就会启动初次刷新加载。
原因在asynclistutil构造函数里面,已经调用refresh方法启动刷新,见asynclistutil构造函数源代码:
/**
*createsanasynclistutil.*
*@paramklassclassofthedataitem.*@paramtilesizenumberofitemperchunkloadedatonce.
*@paramdatacallbackdataaccesscallback.*@paramviewcallbackcallbackforqueryingvisibleitemrangeandupdatenotifications.
*/publicasynclistutil(class
mtclass=klass;
mtilesize=tilesize;mdatacallback=datacallback;
mviewcallback=viewcallback;mtilelist=newtilelist
threadutil
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,-111-2214:41:18.33632764-32764/zhangphil.appd/调试:当前recyclerview为空!
11-2214:41:18.33632764-32764/zhangphil.appd/调试:getitemrangeinto,当前可见position:0~911-2214:41:18.33732764-449/zhangphil.appd/调试:filldata:0,20
11-2214:41:20.35032764-32764/zhangphil.appd/调试:onitemloaded:011-2214:41:20.35132764-32764/zhangphil.appd/调试:onitemloaded:1
11-2214:41:20.35132764-32764/zhangphil.appd/调试:onitemloaded:211-2214:41:20.35232764-32764/zhangphil.appd/调试:onitemloaded:3
11-2214:41:20.35332764-32764/zhangphil.appd/调试:onitemloaded:411-2214:41:20.35332764-32764/zhangphil.appd/调试:onitemloaded:5
11-2214:41:20.35332764-32764/zhangphil.appd/调试:onitemloaded:611-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/调试:onrangechanged11-2214:42:36.01232764-32764/zhangphil.appd/调试:getitemrangeinto,当前可见position:5~12
11-2214:42:36.01332764-1011/zhangphil.appd/调试:filldata:20,2011-2214:42:36.84432764-32764/zhangphil.appd/调试:onrangechanged
11-2214:42:36.84432764-32764/zhangphil.appd/调试:getitemrangeinto,当前可见position:10~1611-2214:42:37.06732764-32764/zhangphil.appd/调试:onrangechanged
11-2214:42:37.06732764-32764/zhangphil.appd/调试:getitemrangeinto,当前可见position:13~2011-2214:42:38.02032764-32764/zhangphil.appd/调试:onitemloaded:20
11-2214:42:38.02032764-32764/zhangphil.appd/调试:onitemloaded:2111-2214:42:38.02032764-32764/zhangphil.appd/调试:onitemloaded:22
11-2214:42:38.02032764-32764/zhangphil.appd/调试:onitemloaded:2311-2214:42:38.02132764-32764/zhangphil.appd/调试:onitemloaded:24
11-2214:42:38.02132764-32764/zhangphil.appd/调试:onitemloaded:2511-2214:42:38.02132764-32764/zhangphil.appd/调试:onitemloaded:26
11-2214:42:38.02132764-32764/zhangphil.appd/调试:onitemloaded:2711-2214:42:38.02132764-32764/zhangphil.appd/调试:onitemloaded:28
11-2214:42:38.78432764-32764/zhangphil.appd/调试:onrangechanged11-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*
*http://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.
**/
publicclassasynclistutilstaticfinalstringtag=“asynclistutil”;
staticfinalbooleandebug=false;
finalclass
finaldatacallback
finaltilelist
finalthreadutil.mainthreadcallback
finalthreadutil.backgroundcallback
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(class
mtclass=klass;
mtilesize=tilesize;mdatacallback=datacallback;
mviewcallback=viewcallback;mtilelist=newtilelist
threadutil
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.
*
*
*ifthismethodhasbeencalledforecificpositionandreturned
null,then
*{@linkviewcallback#onitemloaded(int)}willbecalledwhenitfinallyloads.notethatif
*thispositionstaysoutsideofthecacheditemrange(asdefinedby
*{@linkviewcallback#extendrangeinto}method),thenthecallbackwillneverbecalledfor
*thisposition.
*
*@parampositionitemposition.
*
*@returnthedataitematthegivenpositionor
nullifithasnotbeenloaded
*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]
}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
if(tile.mstartposition<=position&&position
mviewcallback.onitemloaded(position);
}else{
index++;
}
}
}
@override
publicvoidremovetile(intgeneration,intposition){
if(!isrequestedgeneration(generation)){
return;
}
tilelist.tile
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;imbackgroundproxy.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.tile
if(debug){
log(”recyclingtile@%d”,tile.mstartposition);
}
mdatacallback.recycledata(tile.mitems,tile.mitemcount);
tile.mnext=mrecycledroot;
mrecycledroot=tile;
}
privatetilelist.tile
if(mrecycledroot!=null){
tilelist.tile
mrecycledroot=mrecycledroot.mnext;
returnresult;
}
returnnewtilelist.tile
}
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(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-usethebjectsifpossibleinyourusecase.
*
*@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,if
rangeis
{100,200}and
scrollhint
*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);
}
}
/* * 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 * * http://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. *
* 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
* 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
上一篇: PHP单文件上传原理及上传函数的封装
下一篇: 深入浅出WPF 第二部分(1)