Android 适配器教程(四)

之前我们学习了什么是适配器,并且三种常用的安卓原生适配器也讲完了,接下来我们就要自定义适配器了,自定义的适配器能适应更多的情况,功能更加强大,当然也需要我们更加深入的学习才能应用自如。

 

终于到自己写一个适配器的时候了!

 

我准备了两个例子,一个简单一些,一个复杂一些,这次先看个简单的:

我还是继续在前三次的Demo项目上继续添加例子,最后一篇的时候把源码分享给大家~

让我们继续一步步写下去。

 

这个例子是在ListView上面添加按钮,具体来说是显示一个按钮和一个图片,两行字。这个小问题涉及到的知识挺多的。也许你会想:添加按钮首先要写一个有按钮的xml文件,然后用教程(三)的方法定义一个适配器,然后将数据映射到布局文件上。但是事实并非这样,因为按钮是无法映射的,即使你成功的用布局文件显示出了按钮也无法添加按钮的响应,这时就要研究一下ListView是如何现实的了,而且必须要重写一个类继承BaseAdapter

 

 

首先我们先了解一下自己写适配器的原理:

 

第一步要首先重写一个类继承BaseAdapter

 

先让我们看一下各个方法:

 

(1)首先是getCount()方法,这个方法要返回你要添加进ListView里的东西的总数,

也就是要告诉ListView,我添加进列表里的东西有多少,需要多长的列表。

这里的mArray就是一个简单的List<String>,

可能有人会问,为什么不直接返回mListView的长度呢?

原因就是我们的ListView可能会添加”头“和”尾“,来进行一些更新之类的交互,

就像微博之类的下拉刷新或者到底后加载,所以干脆直接用我们添加的内容的长度

 

@Override   

public int getCount() {  

return mArray == null ? 0 : mArray.size();

}  

 

(2)接下来就是getItem(int position)方法

 

ListView要加载内容,要获得内容才可以加载!

 

这个方法就是要让ListView可以通过一个position来获得我们要添加在相应位置的内容的

 

内容是什么?当然是刚才mArray里相应位置的东西啦!

 

@Override  

public Object getItem(int position) {  

    return mArray.get(position);  

} 

 

(3)然后是getItemId(int position)方法,这个方法应该是为了方便ListView进行管理的,

 

简单说,我们就按原来的position来让他管理,原本是几就是几,省事,直接返回position!

 

@Override  

public long getItemId(int position) {  

    return position;  

}  

 

最后,重头戏!getView()方法!

 

这里要实现的东西就比较多了

 

这个也很好理解,个人的理解就是ListView要方便的得到自己里面的每个View

 

不然人家怎么知道你的mArray里的数据,要怎么填入ListView里的每个View

 

@Override 

        public View getView(int position, View convertView, ViewGroup parent) {  

            return null;  

        }  

 

这里面一般我们会怎么做呢?

一般,我们添加到ListView里的每个View都是xml定义好的。

一开始需要构造过来一个Context!

通过LayoutInflater.from(context).inflate(R.layout.你定义的xml,null);

获得你要添加进去的View来赋给convertView

如果我们定义的xml里有一个TextView

那我们就TextView tTextView = (TextView)convertView.findViewById(R.id.你的textview);

这样就可以通过position,在mArray里找到我们相应位置的内容,让TextView显示出来

当然,最后要return convertView

把这个我们包装好的View给回ListView,让它在列表里显示。

下面是具体实现的过程:

项目开始:

 

也还是先在activity_main.xml里添加一个button,一会跳转的时候使用。

然后新建一个类MyAdapterDemo继承自Activity作为我们第四个个例子的Activity,@Override 我们的onCreate方法。

新建一个xml文件myadapterdemo.xml作为我们的布局文件,其中也是包含一个文本域和一个ListView:

 

myadapterdemo.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" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="这是myadapter的一个例子" >
    </TextView>

    <ListView
        android:id="@+id/myadapterlistview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >
    </ListView>

</LinearLayout>

 

然后需要定义好一个用来显示每一个列内容的xml

 

Listitem2.xml 包含横向的图片与文字还有一个button,

Listitem2.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="horizontal" >

    <ImageView
        android:id="@+id/imgview1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5px" />

    <TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5px"
        android:textColor="#000000"
        android:textSize="22px" />

    <TextView
        android:id="@+id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5px"
        android:textColor="#000000"
        android:textSize="15px" />

    <Button
        android:id="@+id/view_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="more" />

</LinearLayout>

自定义适配器:

新建一个类MyAdapter继承自BaseAdapter,这时候Eclipse会提示你Override默认的方法,点击之后就会出现上面我所说的那些方法了。

 

之后创建一个ViewHolder,具体原因我会在下一次教程中仔细说明

 

ViewHolder:

 

public final class ViewHolder{
        public ImageView img;
        public TextView text3;
        public TextView text4;
        public Button viewBtn;
    }

把ViewHolder作为内部类放在最后等着,一会就有用了。

 

 

 

 

构造器

 

然后我们再研究下我们需要外部的什么东西,也就是构造器需要被传进什么参数。

 

我们类比一下之前原生的适配器的构造方法,自然而然的想到他们都有一个Context,同时都需要传参数!

 

所以我们的MyAdapter自然也不例外了~

 

在之前的说明中说过,一开始需要构造过来一个Context!

 

 

 

只有这样才能通过LayoutInflater.from(context).inflate(R.layout.你定义的xml,null);

 

获得你要添加进去的View来赋给convertView。

 

所以构造器以及必要的对象就要这样写:

 

    private LayoutInflater mInflater;
    private List<Map<String, Object>> data;
//    构造器,接收数据
    public MyAdapter(Context context, List<Map<String, Object>> data){
        this.mInflater = LayoutInflater.from(context);
        this.data = data;
    }

 

注意这有个LayoutInflater我给大家解释一下

 

在实际开发中LayoutInflater这个类还是非常有用的,它的作用类似于findViewById()。不同点是LayoutInflater是用来找res/layout/下的xml布局文件,并且实例化;而findViewById()是找xml布局文件下的具体widget控件(如  Button、TextView等)。 具体作用: 1、对于一个没有被载入或者想要动态载入的界面,都需要使用LayoutInflater.inflate()来载入;

 

2、对于一个已经载入的界面,就可以使用Activiyt.findViewById()方法来获得其中的界面元素。

 

这样我们从构造器中得到了数据data还有context(通过LayoutInflater得到布局文件

 

 

 

 

 

之后我们只要把@Override的方法填写完整就好了,希望这时候你还没有忘记开始时我讲过的基本知识。

 

不过没关系,我会一点点讲清楚的:

 

 

 

第一个方法getCount():

 

是得到长度,所以外部数据有所少就要有多长,所以返回的是data==null?0:data.size();

 

也就是如果外面传过来的数据为空,那么长度为0,不是空,长度就是数据的数量,

 

注意:这里非常有必要处理一下data==null这种情况!很多代码这里都没做处理,这是很不好的习惯。

 

所以这个方法填好了:

 

@Override
public int  getCount() {

return data == null? 0:data.size();
}

 

第二个方法getItem(int position)

 

之前也说过了,这个方法就是要让ListView可以通过一个position来获得我们要添加在相应位置的内容的

 

内容是什么?当然是data里相应位置的东西啦!

 

所以这个方法也填好了:

 

@Override
public Object getItem(int position) {

return data.get(position);
}

 

第三个方法getItemId(int position) :

这个方法应该是为了方便ListView进行管理的,

 

没有什么特殊需求的话,我们就按原来的position来让他管理,position原本是几就是几,直接返回position

 

@Override
public long getItemId(int position) {

return position;
}

 

这样的话就只剩下一个大头了:

 

千呼万唤始出来的

 

public View getView(int position, View convertView, ViewGroup parent)

 

写到这我发现还是不得不先解释一下ViewHolder了,我先粗略的尽量让大家理解,因为下一讲的主题就是它!

 

咱们一点点的分析,注意看返回值的类型,是一个View,不难想象这就是返回了List里面一个Item的View,Android中有个叫做Recycler(反复循环器)的构件,ListView的加载原理是这样的:

 

 

 

有一个item出了屏幕,在空出来的部分是由出屏幕的那个item通过适配器的getView方法改变成了新的item加以填充的。所以

 

1.如果你有10亿个项目(item),其中只有可见的项目存在内存中,其他的在Recycler中

 

2.初始化的时候,ListView先请求一个type1视图(getView),然后请求其他可见的项目。这时conVertView在getView中是null的

 

3.当item1滚出屏幕,并且一个新的项目从屏幕地段上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1.你只需要设定新的数据返回convertView,不必重新创建一个视图。这样直接使用convertView从而减少了很不不必要view的创建

 

 而更快的方式是定义一个ViewHolder,将convertView的tag设置为ViewHolder,不为空是重新使用
 
 (Tip:View中的setTag(Onbect)表示给View添加一个格外的数据以后可以用getTag()将这个数据取出来。)

 

这个问题先说到这,下一讲我们在仔细讨论,这样对getView有所理解了吧
 
我直接贴代码,然后大家看注释就好了:

 

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;            //首先定义一个ViewHolder对象
        if (convertView == null) {            //当初始化的时候 convertView是空的
            
            holder=new ViewHolder();        //创建一个ViewHolder
            
            //通过LayoutInflater用来找res/layout/下的xml布局文件,并且实例化,这样convertView的界面就有了。
            convertView = mInflater.inflate(R.layout.listitem2, null);
            //使用Activiyt.findViewById()方法来获得其中的界面元素。
            holder.img = (ImageView)convertView.findViewById(R.id.imgview2);
            holder.text3 = (TextView)convertView.findViewById(R.id.text3);
            holder.text4 = (TextView)convertView.findViewById(R.id.text4);
            holder.viewBtn = (Button)convertView.findViewById(R.id.view_btn);
            //将holder对象作为标签添加到View上
            convertView.setTag(holder);
            
        }else {
            //不为空的时候直接重新使用convertView从而减少了很多不必要的View的创建
            holder = (ViewHolder)convertView.getTag();
        }
        
            //然后加载数据
        holder.img.setBackgroundResource((Integer)data.get(position).get("imgview2"));
        holder.text3.setText((String)data.get(position).get("text3"));
        holder.text4.setText((String)data.get(position).get("text4"));
        
            //为按钮加监听
        holder.viewBtn.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                showInfo();                    
            }
        });
        
        
        return convertView;
    }

这样大家就比较理解了吧,至于ViewHolder和Tag,下次会好好解释的~

 
下面是MyAdapter的完整代码

 

package com.example.adapterdemo;

import java.util.List;
import java.util.Map;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

public class MyAdapter extends BaseAdapter{
    
    private LayoutInflater mInflater;
    private List<Map<String, Object>> data;
    private Context c;
//    构造器,接收数据
    public MyAdapter(Context context, List<Map<String, Object>> data){
        this.c = context;
        this.mInflater = LayoutInflater.from(context);
        this.data = data;
    }

    @Override
    public int getCount() {
        
        return data == null? 0:data.size();
    }

    @Override
    public Object getItem(int position) {
        
        return data.get(position);
    }

    @Override
    public long getItemId(int position) {
        
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;            //首先定义一个ViewHolder对象
        if (convertView == null) {            //当初始化的时候 convertView是空的
            
            holder=new ViewHolder();        //创建一个ViewHolder
            
            //通过LayoutInflater用来找res/layout/下的xml布局文件,并且实例化,这样convertView的界面就有了。
            convertView = mInflater.inflate(R.layout.listitem2, null);
            //使用Activiyt.findViewById()方法来获得其中的界面元素。
            holder.img = (ImageView)convertView.findViewById(R.id.imgview2);
            holder.text3 = (TextView)convertView.findViewById(R.id.text3);
            holder.text4 = (TextView)convertView.findViewById(R.id.text4);
            holder.viewBtn = (Button)convertView.findViewById(R.id.view_btn);
            //将holder对象作为标签添加到View上
            convertView.setTag(holder);
            
        }else {
            //不为空的时候直接重新使用convertView从而减少了很多不必要的View的创建
            holder = (ViewHolder)convertView.getTag();
        }
        
            //然后加载数据
        holder.img.setBackgroundResource((Integer)data.get(position).get("img"));
        holder.text3.setText((String)data.get(position).get("text3"));
        holder.text4.setText((String)data.get(position).get("text4"));
        
            //为按钮加监听
        holder.viewBtn.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                showInfo();                    
            }
        });
        
        
        return convertView;
    }
    
    
    public final class ViewHolder{
        public ImageView img;
        public TextView text3;
        public TextView text4;
        public Button viewBtn;
    }
    /**
     * listview中点击按键弹出对话框
     */
    public void showInfo(){
        new AlertDialog.Builder(c)
        .setTitle("我的listview")
        .setMessage("介绍...")
        .setPositiveButton("确定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
            }
        })
        .show();
        
    }
    
}

之后我们再回到MyAdapterDemo向之前那样增加适配器,添加数据就好了,在按钮的处理上也比较简单,我偷下懒,先贴代码后解释~

 
MyAdapterDemo的完整代码如下:

 

package com.example.adapterdemo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;


public class MyAdapterDemo extends Activity {
    private ListView lv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myadapterdemo);
        lv = (ListView) findViewById(R.id.myadapterlistview);
        MyAdapter myadapter = new MyAdapter(this,getData());
        lv.setAdapter(myadapter);
        
    }
    
    private List<Map<String, Object>> getData() {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();

        Map<String, Object> map = new HashMap<String, Object>();
        map.put("text3", "Image 1");
        map.put("text4", "info 1");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);

        map = new HashMap<String, Object>();
        map.put("text3", "Image 2");
        map.put("text4", "info 2");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);

        map = new HashMap<String, Object>();
        map.put("text3", "Image 3");
        map.put("text4", "info 3");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);
        
        map = new HashMap<String, Object>();
        map.put("text3", "Image 4");
        map.put("text4", "info 4");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);
        
        map = new HashMap<String, Object>();
        map.put("text3", "Image 5");
        map.put("text4", "info 5");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);
        
        map = new HashMap<String, Object>();
        map.put("text3", "Image 6");
        map.put("text4", "info 6");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);
        
        map = new HashMap<String, Object>();
        map.put("text3", "Image 7");
        map.put("text4", "info 7");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);
        
        map = new HashMap<String, Object>();
        map.put("text3", "Image 8");
        map.put("text4", "info 8");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);
        
        map = new HashMap<String, Object>();
        map.put("text3", "Image 9");
        map.put("text4", "info 9");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);
        
        map = new HashMap<String, Object>();
        map.put("text3", "Image10");
        map.put("text4", "info10");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);
        
        map = new HashMap<String, Object>();
        map.put("text3", "Image11");
        map.put("text4", "info11");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);
        
        map = new HashMap<String, Object>();
        map.put("text3", "Image12");
        map.put("text4", "info12");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);
        
        map = new HashMap<String, Object>();
        map.put("text3", "Image13");
        map.put("text4", "info13");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);
        
        map = new HashMap<String, Object>();
        map.put("text3", "Image14");
        map.put("text4", "info14");
        map.put("img", R.drawable.ic_launcher);
        list.add(map);
        
        return list;
    }
    
}

 

让我们再总结一下工作原理listView在开始绘制的时候,系统首先调用getCount()函数,根据他的返回值得到listView的长度,然后根据这个长度,调用getView()逐一绘制每一行。如果你的getCount()返回值是0的话,列表将不显示同样return1,就只显示一行。

 

  系统显示列表时,首先实例化一个适配器(这里将实例化自定义的适配器)。当手动完成适配时,必须手动映射数据,这需要重写getView()方法。系统在绘制列表的每一行的时候将调用此方法。getView()有三个参数,position表示将显示的是第几行,covertView是从布局文件中inflate来的布局。我们用LayoutInflater的方法将定义好的listitem.xml文件提取成View实例用来显示。然后将xml文件中的各个组件实例化(简单的findViewById()方法)。这样便可以将数据对应到各个组件上了。但是按钮为了响应点击事件,需要为它添加点击监听器,这样就能捕获点击事件。至此一个自定义的listView就完成了。

 

现在让我们回过头从新审视这个过程。系统要绘制ListView了,他首先获得要绘制的这个列表的长度,然后开始绘制第一行,怎么绘制呢?调用getView()函数。在这个函数里面首先获得一个View(实际上是一个ViewGroup),然后再实例并设置各个组件,显示之。好了,绘制完这一行了。那 再绘制下一行,直到绘完为止。

 

如果需要ListView也加入监听,在实际的运行过程中会发现listView的每一行没有焦点了,这是因为Button抢夺了listView的焦点,只要布局文件中将Button设置为没有焦点应该就OK了。

 
最后我们看一下实现效果图:

 

到此为止,我们的学习之旅又进了一大步,自定义适配器这一部分需要好好的进行理解,只有真正理解的比较透彻,写起来才会比较顺手,学会和精通是不一样的,仅仅是学会就只能实现一些简单的功能,而学精才能推陈出新,创造出更有影响力的项目。

 

下一讲我会对这一讲留下的几个问题进行详细的分析,比如Holder,tag的详解,争取让大家理解的更加透彻一些,请继续关注~
源代码我会在最后一讲的最后附上链接,因为我也是边写博客边码代码,我觉得这样思路比较清楚一些。
我也还是个学生,水平有限,还请大家多多指教~

 

posted @ 2015-05-16 09:35  卧龙传奇  阅读(578)  评论(0编辑  收藏  举报