欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android ListView分页功能实现方法

程序员文章站 2024-02-29 16:43:28
通过本次小demo我学到了: 1、listview的小小的一个分页功能 2、加深了对自定义控件的理解 3、对listview的优化 4、对baseadapter的使...

通过本次小demo我学到了:

1、listview的小小的一个分页功能
2、加深了对自定义控件的理解
3、对listview的优化
4、对baseadapter的使用
5、自定义adapter
6、接口的回调

要实现下面的效果--当拖动listview到底部的时候,显示一个progressbar和一个"正在加载..."的textview。并且过两秒钟后,在下面加载出新的数据。项目的目录结构和程序要实现的效果如下:

   Android ListView分页功能实现方法              

首先是布局部分:

我为了实现此效果,首先在布局文件中新建了一个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的学习基本完成,其中还有些知识不太懂,比如说接口的回调,自定义控件部分等等,还需要加深练习。