Android ListView分页功能实现方法
通过本次小demo我学到了:
1、listview的小小的一个分页功能
2、加深了对自定义控件的理解
3、对listview的优化
4、对baseadapter的使用
5、自定义adapter
6、接口的回调
要实现下面的效果--当拖动listview到底部的时候,显示一个progressbar和一个"正在加载..."的textview。并且过两秒钟后,在下面加载出新的数据。项目的目录结构和程序要实现的效果如下:
首先是布局部分:
我为了实现此效果,首先在布局文件中新建了一个footer_layout.xml的布局文件:
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <linearlayout android:id="@+id/load_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingtop="10dip" android:paddingbottom="10dip" android:gravity="center" > <progressbar android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:attr/progressbarstylesmall" android:background="#ff0000" /> <textview android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="正在加载..." /> </linearlayout> </linearlayout>
然后新建了一个item.xml用于作为listview的子项:
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <textview android:id="@+id/tv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="哈哈哈" /> <textview android:id="@+id/tv2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="嘎嘎嘎嘎嘎" /> </linearlayout>
最后在主布局文件中添加了一个自定义的listview控件:
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <com.lx.loadlistview.loadlistview android:id="@+id/list" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignparenttop="true" android:layout_centerhorizontal="true" android:cachecolorhint="#00000000" > </com.lx.loadlistview.loadlistview> </relativelayout>
然后为了实现listview的这种效果,我们需要一个自定义的listview,并在上面的布局文件中引用我们自定义的listview,代码如下:
package com.lx.loadlistview; import com.example.listviewloaddemo.r; import android.content.context; import android.util.attributeset; import android.view.layoutinflater; import android.view.view; import android.widget.abslistview; import android.widget.listview; import android.widget.abslistview.onscrolllistener; public class loadlistview extends listview implements onscrolllistener { view footer; int lastvisiableitem;// 最后一个可见的item int totalitemcount;// item的总数量 boolean isloading; // 正在加载 iloadlistener iloadlistener; public loadlistview(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); // todo 自动生成的构造函数存根 initview(context); } public loadlistview(context context, attributeset attrs) { super(context, attrs); // todo 自动生成的构造函数存根 initview(context); } public loadlistview(context context) { super(context); // todo 自动生成的构造函数存根 initview(context); } /*** * 添加底部提示加载布局到listview * * @param context */ public void initview(context context) { layoutinflater inflater = layoutinflater.from(context); footer = inflater.inflate(r.layout.footer_layout, null); // 初始时候让底部布局不可见 footer.findviewbyid(r.id.load_layout).setvisibility(view.gone); this.addfooterview(footer); this.setonscrolllistener(this); } @override public void onscrollstatechanged(abslistview view, int scrollstate) { // todo 自动生成的方法存根 // 当总共的item数量等于最后一个item的位置,并且滚动停止时 if (totalitemcount == lastvisiableitem && scrollstate == scroll_state_idle) { if (!isloading) { isloading = true; footer.findviewbyid(r.id.load_layout).setvisibility( view.visible); //加载更多 iloadlistener.onload(); } } } /** *firstvisibleitem 第一个可见item的位置 *visibleitemcount 可见的item的数量 *totalitemcount item的总数量 **/ @override public void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount) { // todo 自动生成的方法存根 this.lastvisiableitem = firstvisibleitem + visibleitemcount; this.totalitemcount = totalitemcount; } //加载完毕将footer隐藏 public void loadcomplete(){ isloading=false; footer.findviewbyid(r.id.load_layout).setvisibility(view.gone); } public void setinterface(iloadlistener iloadlistener) { this.iloadlistener = iloadlistener; } //加载更多数据回调接口 public interface iloadlistener { public void onload(); } }
我们自定义的listview继承自listview,并实现其中父类的三个构造方法,为了将底部我们想要的布局加载到listview中来,我们自定义了一个initview方法,用于找到并实例化footer_layout.xml使其添加到listview底部。在父类的三个构造方法中添加初始化方法initview(),在initview方法的最后还要调用listview的addfooterview(view)方法,将底部布局add进来。由于在listview刚加载进来的时候我们不想显示这个footer,所以要设置它的visible为gone。想要实现listview拉到底部的时候才显示footer,要实现listview的onscrolllistener接口,并实现其父类中的两个方法。在onscrollstatechanged()方法中判断是否滚动到底部(我们定义了一个全局变量lastvisibleitem=firstvisibleitem+visibleitemcount,若此值和totalitemcount相等,则证明滚动到listview的底端了)和此时listview是否停止滚动(scrollstate=scroll_state_idle)。
为了向listview中添加数据我们定义了一个实体类apk_entity:
package com.lx.entity; public class apkentity { private string name; private string info; public string getname() { return name; } public void setname(string name) { this.name = name; } public string getinfo() { return info; } public void setinfo(string info) { this.info = info; } }
之后我们为listview定义了一个数据适配器myadapter,继承自baseadapter,并实现其中的四个方法,在其中我们主要实现数据的填充:
package com.lx.adapter; import java.util.arraylist; import com.example.listviewloaddemo.r; import com.lx.entity.apkentity; import android.content.context; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; import android.widget.baseadapter; import android.widget.textview; public class myadapter extends baseadapter { arraylist<apkentity> list; layoutinflater inflater; //构造函数中传入了list列表项和初始化layoutinflater public myadapter(context context,arraylist<apkentity> list) { this.list=list; this.inflater=layoutinflater.from(context); } //得到list的长度(是程序在加载显示到ui上是就要先读取的,这里获得的值决定了listview显示多少行) @override public int getcount() { // todo 自动生成的方法存根 return list.size(); } //得到list中指定位置的data(根据listview所在的位置返回view) @override public object getitem(int position) { // todo 自动生成的方法存根 return list.get(position); } //根据listview位置得到数据源集合中的id @override public long getitemid(int position) { // todo 自动生成的方法存根 return position; } //***最主要,决定listview的界面样式 @override public view getview(int position, view convertview, viewgroup parent) { // todo 自动生成的方法存根 //从list中获取实体 apkentity entity=list.get(position); //使用viewholder的目的是为了使每次在getview的时候不是每次都findviewbyid()来获取控件实例 viewholder holder; /** * convertview:the old view to reuses * 用于将之前加载好的布局缓存,以便之后可以重用 */ //为了避免重复加载布局,仅仅在convertview为空的时候才使用layoutinflate加载布局 if(convertview==null){ holder=new viewholder(); //找到并将layout转换为view convertview=inflater.inflate(r.layout.item, null); holder.tv_name=(textview) convertview.findviewbyid(r.id.tv1); holder.tv_info=(textview) convertview.findviewbyid(r.id.tv2); convertview.settag(holder); }else{ holder=(viewholder) convertview.gettag(); } holder.tv_name.settext(entity.getname()); holder.tv_info.settext(entity.getinfo()); return convertview; } class viewholder{ textview tv_name,tv_info; } //布局改变时用来刷新listview public void ondatechanged(arraylist<apkentity> list){ this.list=list; this.notifydatasetchanged(); } }
在这个自定义adapter中最主要的就是getview()方法,它决定了listview的每项的布局(长什么样),在getview()方法中,为了优化listview的运行效率,使得不是每次item创建的时候都要findviewbyid()来实例化控件,我们定义了一个viewholder的内部类,用来对控件的实例进行缓存,在类中声明了item布局中的布局控件。因为getview()方法每次都将布局重新加载了一遍,所以在listview快速滚动的时候就会成为性能的瓶颈。所以用到了getview()方法中的convertview参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以重新使用。通过上面的代码可以看到,如果convertview 为空的时候,我们就使用layoutinflate加载布局并实例化item中的控件,还要调用view的settag()方法,将viewholder对象存储在convertviewu 中。这样,当convertview不为空的时候,则直接调用view的gettag()方法,把viewholder直接取出,这样所有的控件的实例都缓存在了viewholder里,就没有必要每次都对控件进行findviewbyid()了。
1.使用convertview参数:避免重复加载布局,用他来对之前加载过的布局进行缓存。
2.使用viewholder:避免每次getview()的时候都对控件进行实例化,用这个类完成对控件实例化的缓存。
然后我们需要完成在mainactivity中对loadlistview的实例化,和mydapter的实例化,向实体类中添加数据并使adapter和listview适配完成填充数据:
package com.example.listviewloaddemo; import java.util.arraylist; import java.util.hashmap; import java.util.list; import java.util.map; import com.lx.adapter.myadapter; import com.lx.entity.apkentity; import com.lx.loadlistview.loadlistview; import com.lx.loadlistview.loadlistview.iloadlistener; import android.os.bundle; import android.os.handler; import android.app.activity; import android.util.log; import android.view.menu; import android.widget.baseadapter; import android.widget.listview; import android.widget.simpleadapter; public class mainactivity extends activity implements iloadlistener { private loadlistview lv; private arraylist<apkentity> list=new arraylist<apkentity>(); private myadapter myadapter; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); getdate(); showlistview(list); } private void getdate() { // todo 自动生成的方法存根 for (int i = 0; i < 10; i++) { apkentity entity=new apkentity(); entity.setname("大毛"); entity.setinfo("我是一只pig"); list.add(entity); } } private void getonloaddate() { // todo 自动生成的方法存根 for (int i = 0; i < 2; i++) { apkentity entity=new apkentity(); entity.setname("小毛"); entity.setinfo("我是一只dog"); list.add(entity); } } private void showlistview(arraylist<apkentity> list) { if(myadapter==null){ lv = (loadlistview) findviewbyid(r.id.list); lv.setinterface(this); log.d("setinterface--->>", this.tostring()); myadapter=new myadapter(this, list); lv.setadapter(myadapter); }else{ myadapter.ondatechanged(list); } } @override public void onload() { // todo 自动生成的方法存根 //用现线程来控制隔多少秒之后获取数据,然后设置到listview上(正常情况下不需要加,只是为了看出来这个延时的效果) handler handler=new handler(); handler.postdelayed(new runnable() { @override public void run() { // todo 自动生成的方法存根 getonloaddate(); showlistview(list); //通知listview加载完毕 lv.loadcomplete(); } }, 2000); } }
mainactivity中主要需要注意的就是showlistview()方法,在该方法中我们判断了一下adapter是否为空,若adapter为空,则实例化listview,实例化adapter等等一系列操作,否则调用myadapter的ondatechanged()方法(此方法中调用了adapter的notifydatasetchanged()方法此方法用于listview发生变化时更新ui)。由于要在监听到滑动到listview底部的时候加载新的数据,所以在loadlistview类中实现一个队mainactivoity的回调,在loadlistview中写一个回调接口iloadlistener(),在其中实现一个onload()方法,在mainactivity中实现这个接口,重写onload()方法,在其中 实现想要实现的其他方法,比如新数据的加载和ui的刷新展示,最后,刷新加载完新的数据后,要将footer隐藏,所以执行loadlistview中的loadcomplete()方法。
至此,整个小demo的学习基本完成,其中还有些知识不太懂,比如说接口的回调,自定义控件部分等等,还需要加深练习。