Android学习随笔--ListView的分页功能

第一次写博客,可能格式,排版什么的会非常不美观,不过我主要是为了记录自己的Android学习之路,为了以后能有些东西回顾。既然是为了学习,那我肯定会吸收各位大大们的知道经验,有不足的地方请指出。

通过本次小Demo我学到了:

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

本次我是通过慕课网(视频链接:http://www.imooc.com/learn/136)学习,要实现下面的效果--当拖动ListView到底部的时候,显示一个ProgressBar和一个"正在加载..."的TextView。并且过两秒钟后,在下面加载出新的数据。项目的目录结构和程序要实现的效果如下:

                 

首先是布局部分:

我为了实现此效果,首先在布局文件中新建了一个footer_layout.xml的布局文件:

复制代码
 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical" >
 6     <LinearLayout 
 7         android:id="@+id/load_layout"
 8         android:layout_width="match_parent"
 9         android:layout_height="wrap_content"
10         android:orientation="horizontal"
11         android:paddingTop="10dip"
12         android:paddingBottom="10dip"
13         android:gravity="center"
14         >
15         <ProgressBar 
16             android:layout_width="wrap_content"
17             android:layout_height="wrap_content"
18             style="?android:attr/progressBarStyleSmall"
19             android:background="#ff0000"
20             />
21         <TextView 
22             android:layout_width="wrap_content"
23             android:layout_height="wrap_content"
24             android:text="正在加载..."
25             />
26         
27     </LinearLayout>
28 
29 </LinearLayout>
复制代码

然后新建了一个item.xml用于作为ListView的子项:

复制代码
 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical" >
 6 
 7     <TextView
 8         android:id="@+id/tv1"
 9         android:layout_width="wrap_content"
10         android:layout_height="wrap_content"
11         android:text="哈哈哈" />
12     <TextView 
13         android:id="@+id/tv2"
14         android:layout_width="wrap_content"
15         android:layout_height="wrap_content"
16         android:text="嘎嘎嘎嘎嘎"
17     />
18 </LinearLayout>
复制代码

最后在主布局文件中添加了一个自定义的ListView控件:

复制代码
 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     >
 6 
 7     <com.lx.loadListView.LoadListView
 8         android:id="@+id/list"
 9         android:layout_width="match_parent"
10         android:layout_height="wrap_content"
11         android:layout_alignParentTop="true"
12         android:layout_centerHorizontal="true"
13         android:cacheColorHint="#00000000" >
14     </com.lx.loadListView.LoadListView>
15 
16 </RelativeLayout>
复制代码

然后为了实现ListView的这种效果,我们需要一个自定义的ListView,并在上面的布局文件中引用我们自定义的ListView,代码如下:

复制代码
 1 package com.lx.loadListView;
 2 
 3 import com.example.listviewloaddemo.R;
 4 
 5 import android.content.Context;
 6 import android.util.AttributeSet;
 7 import android.view.LayoutInflater;
 8 import android.view.View;
 9 import android.widget.AbsListView;
10 import android.widget.ListView;
11 import android.widget.AbsListView.OnScrollListener;
12 
13 public class LoadListView extends ListView implements OnScrollListener {
14 
15     View footer;
16     int lastVisiableItem;// 最后一个可见的Item
17     int totalItemCount;// Item的总数量
18     boolean isLoading; // 正在加载
19     ILoadListener iLoadListener;
20 
21     public LoadListView(Context context, AttributeSet attrs, int defStyle) {
22         super(context, attrs, defStyle);
23         // TODO 自动生成的构造函数存根
24         initView(context);
25     }
26 
27     public LoadListView(Context context, AttributeSet attrs) {
28         super(context, attrs);
29         // TODO 自动生成的构造函数存根
30         initView(context);
31     }
32 
33     public LoadListView(Context context) {
34         super(context);
35         // TODO 自动生成的构造函数存根
36         initView(context);
37     }
38 
39     /***
40      * 添加底部提示加载布局到listView
41      * 
42      * @param context
43      */
44     public void initView(Context context) {
45         LayoutInflater inflater = LayoutInflater.from(context);
46         footer = inflater.inflate(R.layout.footer_layout, null);
47         // 初始时候让底部布局不可见
48         footer.findViewById(R.id.load_layout).setVisibility(View.GONE);
49         this.addFooterView(footer);
50         this.setOnScrollListener(this);
51     }
52 
53     @Override
54     public void onScrollStateChanged(AbsListView view, int scrollState) {
55         // TODO 自动生成的方法存根
56         // 当总共的Item数量等于最后一个Item的位置,并且滚动停止时
57         if (totalItemCount == lastVisiableItem
58                 && scrollState == SCROLL_STATE_IDLE) {
59             if (!isLoading) {
60                 isLoading = true;
61                 footer.findViewById(R.id.load_layout).setVisibility(
62                         View.VISIBLE);
63                 //加载更多
64                 iLoadListener.onLoad();
65             }
66         }
67     }
68     
69     /**
70     *firstVisibleItem  第一个可见Item的位置
71     *visibleItemCount  可见的Item的数量
72     *totalItemCount       Item的总数量
73     **/
74     @Override
75     public void onScroll(AbsListView view, int firstVisibleItem,
76             int visibleItemCount, int totalItemCount) {
77         // TODO 自动生成的方法存根
78         this.lastVisiableItem = firstVisibleItem + visibleItemCount;
79         this.totalItemCount = totalItemCount;
80     }
81 
82     //加载完毕将footer隐藏
83     public void loadComplete(){
84         isLoading=false;
85         footer.findViewById(R.id.load_layout).setVisibility(View.GONE);
86     }
87     
88     public void setInterface(ILoadListener iLoadListener) {
89         this.iLoadListener = iLoadListener;
90     }
91 
92     //加载更多数据回调接口
93     public interface ILoadListener {
94         public void onLoad();
95     }
96 
97 }
复制代码

我们自定义的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:

复制代码
 1 package com.lx.entity;
 2 
 3 public class ApkEntity {
 4 
 5     private String name;
 6     private String info;
 7     public String getName() {
 8         return name;
 9     }
10     public void setName(String name) {
11         this.name = name;
12     }
13     public String getInfo() {
14         return info;
15     }
16     public void setInfo(String info) {
17         this.info = info;
18     }
19     
20 }
复制代码

之后我们为ListView定义了一个数据适配器MyAdapter,继承自BaseAdapter,并实现其中的四个方法,在其中我们主要实现数据的填充:

复制代码
 1 package com.lx.adapter;
 2 
 3 import java.util.ArrayList;
 4 
 5 import com.example.listviewloaddemo.R;
 6 import com.lx.entity.ApkEntity;
 7 
 8 
 9 import android.content.Context;
10 import android.view.LayoutInflater;
11 import android.view.View;
12 import android.view.ViewGroup;
13 import android.widget.BaseAdapter;
14 import android.widget.TextView;
15 
16 public class MyAdapter extends BaseAdapter {
17 
18     ArrayList<ApkEntity> list;
19     LayoutInflater inflater;
20     
21     
22     //构造函数中传入了list列表项和初始化LayoutInflater
23     public MyAdapter(Context context,ArrayList<ApkEntity> list) {
24         this.list=list;
25         this.inflater=LayoutInflater.from(context);
26     }
27 
28     //得到list的长度(是程序在加载显示到UI上是就要先读取的,这里获得的值决定了ListView显示多少行)
29     @Override
30     public int getCount() {
31         // TODO 自动生成的方法存根
32         return list.size();
33     }
34 
35     //得到list中指定位置的data(根据ListView所在的位置返回View)
36     @Override
37     public Object getItem(int position) {
38         // TODO 自动生成的方法存根
39         return list.get(position);
40     }
41     
42     //根据ListView位置得到数据源集合中的ID
43     @Override
44     public long getItemId(int position) {
45         // TODO 自动生成的方法存根
46         return position;
47     }
48 
49     //***最主要,决定ListView的界面样式
50     @Override
51     public View getView(int position, View convertView, ViewGroup parent) {
52         // TODO 自动生成的方法存根
53         //从list中获取实体
54         ApkEntity entity=list.get(position);
55         //使用ViewHolder的目的是为了使每次在getView的时候不是每次都findViewById()来获取控件实例
56         ViewHolder holder;
57         /**
58          * convertView:The old View to reuses
59          * 用于将之前加载好的布局缓存,以便之后可以重用
60          */
61         //为了避免重复加载布局,仅仅在convertView为空的时候才使用LayoutInflate加载布局
62         if(convertView==null){
63             holder=new ViewHolder();
64             //找到并将layout转换为View
65             convertView=inflater.inflate(R.layout.item, null);
66             holder.tv_name=(TextView) convertView.findViewById(R.id.tv1);
67             holder.tv_info=(TextView) convertView.findViewById(R.id.tv2);
68             convertView.setTag(holder);
69         }else{
70             holder=(ViewHolder) convertView.getTag();
71         }
72         holder.tv_name.setText(entity.getName());
73         holder.tv_info.setText(entity.getInfo());
74         return convertView;
75     }
76     
77     class ViewHolder{
78         TextView tv_name,tv_info;
79     }
80     
81     //布局改变时用来刷新ListView
82     public void onDateChanged(ArrayList<ApkEntity> list){
83         this.list=list;
84         this.notifyDataSetChanged();
85     }
86 
87 }
复制代码

在这个自定义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适配完成填充数据:

复制代码
 1 package com.example.listviewloaddemo;
 2 
 3 import java.util.ArrayList;
 4 import java.util.HashMap;
 5 import java.util.List;
 6 import java.util.Map;
 7 
 8 import com.lx.adapter.MyAdapter;
 9 import com.lx.entity.ApkEntity;
10 import com.lx.loadListView.LoadListView;
11 import com.lx.loadListView.LoadListView.ILoadListener;
12 
13 import android.os.Bundle;
14 import android.os.Handler;
15 import android.app.Activity;
16 import android.util.Log;
17 import android.view.Menu;
18 import android.widget.BaseAdapter;
19 import android.widget.ListView;
20 import android.widget.SimpleAdapter;
21 
22 public class MainActivity extends Activity implements ILoadListener {
23 
24     private LoadListView lv;
25     private ArrayList<ApkEntity> list=new ArrayList<ApkEntity>();
26     private MyAdapter myAdapter;
27     @Override
28     protected void onCreate(Bundle savedInstanceState) {
29         super.onCreate(savedInstanceState);
30         setContentView(R.layout.activity_main);
31         getDate();
32         showListView(list);
33         
34     }
35 
36     private void getDate() {
37         // TODO 自动生成的方法存根
38         for (int i = 0; i < 10; i++) {
39             ApkEntity entity=new ApkEntity();
40             entity.setName("大毛");
41             entity.setInfo("我是一只pig");
42             list.add(entity);
43         }    
44     }
45     
46     private void getOnLoadDate() {
47         // TODO 自动生成的方法存根
48         for (int i = 0; i < 2; i++) {
49             ApkEntity entity=new ApkEntity();
50             entity.setName("小毛");
51             entity.setInfo("我是一只dog");
52             list.add(entity);
53         }    
54     }
55 
56     private void showListView(ArrayList<ApkEntity> list) {    
57         if(myAdapter==null){    
58             lv = (LoadListView) findViewById(R.id.list);
59             lv.setInterface(this);
60             Log.d("SetInterface--->>", this.toString());
61             myAdapter=new MyAdapter(this, list);
62             lv.setAdapter(myAdapter);
63         }else{
64             myAdapter.onDateChanged(list);
65         }
66     }
67 
68     @Override
69     public void onLoad() {
70         // TODO 自动生成的方法存根
71         //用现线程来控制隔多少秒之后获取数据,然后设置到ListView上(正常情况下不需要加,只是为了看出来这个延时的效果)
72         Handler handler=new Handler();
73         handler.postDelayed(new Runnable() {    
74             @Override
75             public void run() {
76                 // TODO 自动生成的方法存根
77                 getOnLoadDate();
78                 showListView(list);
79                 //通知ListView加载完毕
80                 lv.loadComplete();
81             }
82         }, 2000);    
83     }
84 
85 }
复制代码

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的学习基本完成,其中还有些知识不太懂,比如说接口的回调,自定义控件部分等等,还需要加深练习。

 

 

我的理解是:

1、一个有许多getter的类(就是getView(),getCount()....这些方法)

2、有多少个get方法?都是什么?

 

这些getter是特定的,你可以复写他们,全部的方法如下

其中一般我们只用复写getCount(),getView(),getItemId(),getItem()这四个方法

 

 

3、这些被谁调用?

 

这些getter是被android系统自行调用的(具体如何调用,作为像我这样的新手做稍微了解就好)

 

4、为什么要复写这些get方法?

 

复写这些getter是为了返回给android系统,自己想给的结果

 

5、android系统通过这些getter能得到什么?

 

得到这个view自身的一些信息

 

eg:以ListView中的Adapter为例,其中

 

public int getCount() 

 

返回ListView中要显示的子View数量,就是item的总数量

 

public Object getItem(int position)

 

返回一个子View,即ListView中的一个子条目

 

public long getItemId(int position)

 

返回一个item的id,由参数position决定是哪个id

 

public View getView(int position, View convertView, ViewGroupparent)

 

后做详解

….

 

6、android系统得到这些为了干什么?

 

得到这些信息以便绘制出这个View,以及以怎么样的方式绘制…

 

 

View getview(int position, View convertview, ViewGroup parent )的理解:

 

引用自:http://www.cnblogs.com/xiaowenji/archive/2010/12/08/1900579.html其中也有一案例帮助理解

 

一、原理的理解

工作原理:

  1. ListView 针对List中每个item,要求 adapter “给我一个视图” (getView)。
  2. 一个新的视图被返回并显示

如果我们有上亿个项目要显示怎么办?为每个项目创建一个新视图?NO!这不可能!

实际上Android为你缓存了视图。

Android中有个叫做Recycler的构件,下图是他的工作原理:

  1. 如果你有10亿个项目(item),其中只有可见的项目存在内存中,其他的在Recycler中。
  2. ListView先请求一个type1视图(getView)然后请求其他可见的项目。convertView在getView中是空(null)的。
  3. 当item1滚出屏幕,并且一个新的项目从屏幕低端上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1。你只需设定新的数据然后返回convertView,不必重新创建一个视图。

 

二、代码的理解

 

Google官方的demo代码:

 

[java] view plain copy
 
  1. <span style="font-family:Courier New;font-size:12px;">public View getView(int position, View convertView, ViewGroup parent)   
  2. {   
  3.     // A ViewHolder keeps references to children views to avoid unneccessary calls   
  4.     // to findViewById() on each row.   
  5.   
  6.     ViewHolder holder;   
  7.     // When convertView is not null, we can reuse it directly, there is no need   
  8.     // to reinflate it. We only inflate a new View when the convertView supplied   
  9.     // by ListView is null.   
  10.   
  11.     if (convertView == )   
  12.     {   
  13.         convertView = mInflater.inflate(R.layout.list_item_icon_text, );   
  14.         Log.v("tag", "positon "+position+" convertView is null, "+"new: "+convertView);   
  15.         // Creates a ViewHolder and store references to the two children views   
  16.         // we want to bind data to.   
  17.         holder = new ViewHolder();   
  18.         holder.text = (TextView) convertView.findViewById(R.id.text);   
  19.         holder.icon = (ImageView) convertView.findViewById(R.id.icon);   
  20.         convertView.setTag(holder);   
  21.     }   
  22.     else   
  23.     {   
  24.         // Get the ViewHolder back to get fast access to the TextView   
  25.         // and the ImageView.   
  26.         holder = (ViewHolder) convertView.getTag();   
  27.         Log.v("tag", "positon "+position+" convertView is not null, "+convertView);   
  28.     }   
  29.   
  30.     // Bind the data efficiently with the holder.   
  31.     holder.text.setText(DATA[position]);   
  32.     holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);   
  33.     return convertView;   
  34. }   
  35.    
  36. static class ViewHolder   
  37. {   
  38.     TextView text;   
  39.     ImageView icon;   
  40. } </span>  

 

下两段引用自:http://blog.csdn.net/pkxiuluo01/article/details/7380974

convertView参数的理解:

每次显示一个item都调用一次getview方法但是每次调用的时候covertview为空(因为还没有旧的view),当显示完了之后。如果屏幕移动了之后,并且导致有些Item(也可以说是view)跑到屏幕外面,此时如果还有新的item需要产生,则这些item显示时调用的getview方法中的convertview参数就不是null,而是那些移出屏幕的view(旧view),我们所要做的就是将需要显示的item填充到这些回收的view(旧view)中去,最后注意convertview为null的不仅仅是初始显示的那些item,还有一些是已经开始移入屏幕但是还没有view被回收的那些item。

 

setTag()和getTag()的理解:

 

view的setTag和getTag方法其实很简单,在实际编写代码的时候一个view不仅仅是为了显示一些字符串、图片,有时我们还需要他们携带一些其他的数据以便我们对该view的识别或者其他操作。于是android 的设计者们就创造了setTag(Object)方法来存放一些数据和view绑定,我们可以理解为这个是view 的标签也可以理解为view 作为一个容器存放了一些数据。而这些数据我们也可以通过getTag() 方法来取出来。

到这里setTag和getTag大家应该已经明白了。再回到上面的话题,我们通过convertview的setTag方法和getTag方法来将我们要显示的数据来绑定在convertview上。如果convertview 是第一次展示我们就创建新的Holder对象与之绑定,并在最后通过return convertview 返回,去显示;如果convertview 是回收来的那么我们就不必创建新的holder对象,只需要把原来的绑定的holder取出加上新的数据就行了。

 

 

我理解过程中所看的文章:

源头上理解Adapter,以及一个案例

http://blog.csdn.net/chunqiuwei/article/details/39934169

帮助理解

http://blog.csdn.net/primer_programer/article/details/23064767

 

 

ListView是安卓中很经常使用的一个控件。

 

安卓设计使用Adapter来对ListView进行管理。

可是系统提供的Adapter无法满足一些复杂的显示情况,这个时候我们就须要使用BaseAdapter来自行实现ListView的控制。

其它的方法都非常好说。这里主要解释一下getView方法。

首先ListView在设计时,为了保障系统的性能(毕竟是移动终端,系统资源有限),设计时并非无限载入ItemView进去的。

 

试想一下,假设有1000条记录。难道要同一时候创建。并缓存1000个 ItemView对象吗?假设还包括图片等大型资源呢?

显然是不现实的。所以安卓设计时,採用了例如以下设计思路。

如上图所看到的。系统仅仅会创建一屏ItemView。详细个数依据屏幕显示控件计算得到。

当第一个itemView彻底被遮盖时,系统会移动它作为最后一个ItemView显示。

这样就保证始终仅仅有7个ItemView被实例化。

 

可是显然Item8和Item1显示内容是不一样的,这时候。我们就须要用到GetView这种方法了。

 

getView中的convertView,事实上就是屏幕显示的ItemView的引用。

在第一屏数据显示是,convertView为空。由于他们都须要被又一次创建。

在第二屏第一项数据被展示时。convertView就不在是空,而是被移动下来的第一屏第一项的View对象。

这时候我们须要做的,就是又一次把新的数据填充上去,保证它显示的是第二屏第一项的数据就可以。

 

详细參考链接:http://www.cnblogs.com/xiaowenji/archive/2010/12/08/1900579.html

至于我们经常见到的PlaceHolder这个东西,好吧,事实上他仅仅是一个经常使用的方式,为了不须要每次都又一次getViewByID。来获取对象引用。

简单来说,就是用起来比較方便罢了。事实上你也没必要要用这东西。每次都从convertView中又一次获取也不是不行,就是浪费代码,浪费资源罢了。

 

然后说说getView反复调用问题。多数都是因为重绘导致的。一般来说。奖Item的宽度和高度设置成fill_parent,尽量不要用match。这样会好非常多。

 

假设须要又一次绘制viewlist。能够使用notifyDataSetChanged,主要是起到刷新界面的作用。

详细參考链接:http://www.cnblogs.com/kissazi2/p/3721941.html

 

嘛,最后吐槽一下,感觉安卓非常多控件设计的耦合性好高。并且即使想要自行分离变量,也非常困难。

作者想要写一个通用的Adapter,吧GetView里面的可变因素抽象,开放出来,结果发现,真这样实现了导致好多接口对象的产生,感觉还不如一个页面一个Adapter来实现的方便,所以就不在这里献丑了。这也側面说明了,为什么安卓没有比較通用成熟的一些好的高速开发框架的原因。

 

posted on 2020-07-20 11:32  大西瓜3721  阅读(303)  评论(0编辑  收藏  举报

导航