Android学习笔记--ListView、自定义适配器、模糊查找

安卓学习笔记--ListView、自定义适配器、模糊查找、RecyclerView

ListView的基本使用

最简单的ListView

最简单的List View
这是一个最简单的List View,创建很简便,但是缺点也很明显,仅仅只能适配一个字符串。现在就来看看如何创建这个最简单的适配器
1.在布局文件中添加List View

<ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

2.在主活动中,绑定这个控件。在主活动的onCreate()方法里面:

ListView listView = (ListView)this.findViewById(R.id.list_view);

3.在主活动中,创建一个ArrayAdapter适配器,对其实例化,然后让其和List View进行绑定: 写在onCreate()方法里面

// 以下是很简单的通过ArrayAdapter适配器来适配一个列表,缺点是一行只能适配一个文本
        String[] data= {
   
   "apple","banana","origin","pear"};
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                MainActivity.this,    //上下文
                android.R.layout.simple_list_item_1,  //列表布局采用系统样式1
                data                  //列表数据来源字符串数组data
        );
        listView.setAdapter(adapter);

①data 字符串数组,用于存放每一个子项的文本。
②实例化一个ArrayAdapter适配器,这里使用它带有3个参数的构造函数,第一个参数是context,是适配器的上下文环境,代表在哪个活动适配。 第二个参数是一个样式,这里使用系统自带的一个样式android.R.layout.simple_list_item1 第三个参数列表的数据来源,为字符串数组类型。
③适配器创建好以后,就可以将其和List View绑定到一起,利用ListView的setAdapter()方法,把这个适配器和ListView绑定到一起。这样就可以显示出上图的效果。

3-2.ArrayAdapter的另一种构造方法:
如果你不满足于系统提供给你的样式,也可以使用自己的样式。

ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                MainActivity.this,    //上下文
                R.layout.stu_list,    //列表布局文件
                R.id.custom_textview, //列表布局文件里面的一个TextView控件
                data                  //适配的数据
        );

这是带四个参数的构造函数,可以发现,这里面有两个地方和上面的不一样,第二个参数是你自己写的layout布局文件,而第三个参数,是这个布局文件里面的一个控件。这样,数据源的文本就会显示到这个指定的控件上,而ListView的每一个子项,都是你这个布局文件。
这种带四个参数的构造函数,比起上面的那一个,多了一个自己的布局文件,你可以显示文本信息要写在哪一个地方,但是缺点还是只能适配一个字符串。对比上面的那个,就是单纯的好看一点吧。

4.当适配的内容发生变化的时候,通知适配器进行更新:
当你的列表数据源发生了改变,这时候就需要通知适配器所适配的内容需要改变了,具体的显示就是你可以看到新的ListView内容。
比如下面这样:

data[0] = "mmm";
adapter.notifyDataSetChanged();   //修改数据源以后更新适配器,动态刷新列表

在上例基础上,把字符串数组的第一个元素值改变为"mmm"。这时候,就可以使用适配器的notifyDataSetChanged()方法,通知适配器更新,实现动态刷新的效果。

为ListView设置点击事件

有了ListView显示出来以后,还需要为其设置点击事件,当点击不同的子项的时候,有不同的反应。如果仅仅可以看,那有什么用呢?
在主活动的onCreate()方法里面,加入如下代码:

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
   
   
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
   
   
                String item = adapter.getItem(i);
                Toast.makeText(MainActivity.this, item, Toast.LENGTH_SHORT).show();
            }
        });

使用ListView的setonItemClickListerner可以实现对列表视图当个子项点击的事件监听。这上面的代码中,重写onItemClick方法,实现点击子项弹出显示对应文本。
!!注意:如果你想要获得子项的内容,比如上面我就获得了一个列表视图的数据源字符串,我建议你使用适配器的getItem(i)来获得子项。或许你也可以用List<> 的get()方法,但是并不推荐这样使用,因为如果是List的get方法,这个List你写在主活动里面,如果你进行了模糊查找等,适配器里面的List发生了改变,但是你任然使用List的getItem方法来获得,那么就会出现不对应的问题。
好像有点而绕,总之,只要记得推荐使用适配器提供的getItem方法来获得子项。这样是最好的方法。
在这里插入图片描述


自定义适配器

上面的ListView,是使用最简单的适配器绑定的。它有一个不足,那就是所能适配的仅仅只有一个文本信息。但是,通常我们的子项,可能有多个TextView,有多个ImageView等,需要对不同的TextView适配不同的内容,这个时候,就需要用到自定义的适配器了。
自定义的适配器,简单来说,需要有3个文件:
1.子项的布局文件、2.所需要适配的数据源类、3.自定义的适配器类
举个示例,现在我们来实现一下下面这个图片的ListView
在这里插入图片描述
可以看到,每一个子项,都有一个卡号、类型、金额、日期需要进行适配。前面所用的适配,已经完全不能够满足我们的需要。那么,现在开始自定义的适配器:
1.新建一个布局文件,用于子项的布局:





<?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="120dp"
    android:paddingTop="5dp"
    android:paddingBottom="5dp"
    android:orientation="vertical"
    >
    <TextView
        android:id="@+id/tv_idCard"
        android:text="卡号:1234567890123456"
        android:textSize="18sp"
        android:layout_width="wrap_content"
        android:layout_height="20dp" />
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/tv_leixing"
            android:text="类型:转账"
            android:textSize="18sp"
            android:layout_alignParentRight="true"
            android:gravity="left"
            android:layout_toRightOf="@+id/tv_idCard"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            />
        <TextView
            android:id="@+id/tv_jiner"
            android:text="金额:1000000.00"
            android:textSize="18sp"
            android:layout_alignParentRight="true"
            android:gravity="left"
            android:layout_toRightOf="@+id/tv_idCard"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            />
    </LinearLayout>
    <TextView
        android:id="@+id/tv_day"
        android:text="日期:2020-12-11-12-56-04"
        android:textSize="18sp"
        android:gravity="right"
        android:layout_below="@+id/tv_idCard"
        android:layout_toRightOf="@+id/tv_idCard"
        android:layout_alignParentRight="true"
        android:layout_width="wrap_content"
        android:layout_height="20dp" />
    <View
        android:id="@+id/vw_Line_password"
        android:layout_width="match_parent"
        android:layout_height="1dip"
        android:background="#83738F"
        android:layout_below="@id/tv_day"
        android:layout_marginTop="2dp"
        android:layout_marginBottom="2dp"
        />

</LinearLayout>

上面的布局文件代码,你不必要详细的看,我们只需要知道自定义的适配器如何实现。这里仅仅贴出来。
2.自定义一个数据源类:

public class JiaoYi {
   
   
    private String bankId;  //交易卡号
    private String type;    //类型
    private float money;    //金额
    private String day;     //日期
    private String targetBankId=""; //目标账户卡号,仅在转账的时候会被赋值

    JiaoYi(String id,String ty,float mo,String d){
   
   
        this.bankId = id;
        this.type = ty;
        this.money = mo;
        this.day = d;
    }

    public void setTargetBankId(String t){
   
   this.targetBankId = t;}
    public String getTargetBankId(){
   
   return this.targetBankId;}
    public String getBankId(){
   
   return this.bankId;}
    public String getType(){
   
   return this.type;}
    public float getMoney(){
   
   return this.money;}
    public String getDay(){
   
   return this.day;}
}

这一个类的作用,是包括了你所需要的适配的内容。比如我们这里所需要适配的内容,有卡号、类型、金额、日期。那么我们这里就写了一个交易类,这个类保存了上述所需要的信息。

3.自定义适配器,实现绑定视图和获得适配内容:
新建一个类,继承于BaseAdapter。然后实现其6个重要的方法
右击包名–新建一个class 。手动添加继承 extends BaseAdapter ,这个时候你会发现有红色波浪线,点击波浪线处,等待一会有个小灯泡,选择小灯泡就会看到一个继承基类的方法。那些方法就是必须要实现的方法。
在这里插入图片描述
点击小灯泡,implement method
在这里插入图片描述
见名知意,
getCount 是得到适配的数量,即有多个子项
getItem 是得到子项,即返回所适配的子项
getItemId 是得到子项的id号
getView 是得到子项的视图,用于每一个子项在显示的时候如何进行显示
分析完毕,现在让我们看一下具体的代码吧:










class JiaoyiAdapter extends BaseAdapter {
   
   
    private List<JiaoYi> list_jiaoyi;           //用于显示数据
    private List<JiaoYi> list_forRemember;      //用于备份全部数据
    private LayoutInflater inflater;
    private JiaoyiFilter jiaoyiFilter;


    private class ViewHolder{
   
   
        TextView kahao,type,money,day;  //卡号,类型,金额,日期

    }
    JiaoyiAdapter(Context context,List<JiaoYi> m){
   
   
        this.list_jiaoyi = m;
        this.list_forRemember = list_jiaoyi;
        this.inflater = LayoutInflater.from(context);
    }
    @Override
    public int getCount() {
   
   
        return list_jiaoyi.size();
    }

    @Override
    public Object getItem(int i) {
   
   
        return list_jiaoyi.get(i);
    }

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

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
   
   
        JiaoYi temp = list_jiaoyi.get(i);
        ViewHolder viewHolder = null;
        if (view == null){
   
   
            view = inflater.inflate(R.layout.listview_jiaoyi,null);
            viewHolder = new ViewHolder();
            viewHolder.kahao = (TextView)view.findViewById(R.id.tv_idCard);
            viewHolder.type = (TextView)view.findViewById(R.id.tv_leixing);
            viewHolder.money = (TextView)view.findViewById(R.id.tv_jiner);
            viewHolder.day = (TextView)view.findViewById(R.id.tv_day);
            view.setTag(viewHolder);
        }
        else {
   
   
            viewHolder = (ViewHolder) view.getTag();
        }
        viewHolder.kahao.setText("卡号:"+temp.getBankId());
        viewHolder.type.setText("类型:"+temp.getType());
        viewHolder.money.setText("金额:"+String.valueOf(temp.getMoney()));
        viewHolder.day.setText("日期:"+temp.getDay());
        return view;
    }
}

分析:
①首先来看一下构造函数,这个是必须要写的。

JiaoyiAdapter(Context context,List<JiaoYi> m){
   
   
        this.list_jiaoyi = m;
        this.list_forRemember = list_jiaoyi;
        this.inflater = LayoutInflater.from(context);
    }

一般是写一个带两个参数的构造函数即可,第一个参数是context 上下文,第二个参数是一个List类型,是你要适配的数据源。在构造函数中,把外部的context和List赋值给自己的成员变量。这里面使用到了一个成员变量LayoutInflater类型的inflater,这个是用于在后面动态加入布局。

②重写getCount,getItem,getItemId

@Override
    public int getCount() {
   
   
        return list_jiaoyi.size();
    }

    @Override
    public Object getItem(int i) {
   
   
        return list_jiaoyi.get(i);
    }

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

从上面的分析,可以知道,
我们要适配的数据源是List列表里面的每一个元素,那么getCount这个方法返回的适配的数量,就是这个List的size
getItem需要获得每一个适配子项,这每一个子项,就是List对应的每一个元素,可以通过List的get方法,直接获得
getItemId方法,是得到子项的Id号,因为子项的id就是List的id,所以这里直接返回i即可。


③重写getView方法,这个方法是最最至关重要的,从它的返回值是一个view类型,我们可以知道,这个方法,解决的是每一个子项的view。即这个子项是如何显示的(在哪个context里面显示?显示的布局是什么样子的?所需要适配的又是什么?)
在写这个方法之前,我们先写一个私有类ViewHolder

private class ViewHolder{
   
   
        TextView kahao,type,money,day;  //卡号,类型,金额,日期

    }

这个私有类,里面包含该适配器所需要适配的所有控件。因为我们这里需要适配的卡号、类型、金额、日期,是分别在4个不同的TextView里面,那么我们就需要4个TextView控件。
现在,开始重写这个getView方法

@Override
    public View getView(int i, View view, ViewGroup viewGroup) {
   
   
        JiaoYi temp = list_jiaoyi.get(i);
        ViewHolder viewHolder = null;
        if (view == null){
   
   
            view = inflater.inflate(R.layout.listview_jiaoyi,null);
            viewHolder = new ViewHolder();
            viewHolder.kahao = (TextView)view.findViewById(R.id.tv_idCard);
            viewHolder.type = (TextView)view.findViewById(R.id.tv_leixing);
            viewHolder.money = (TextView)view.findViewById(R.id.tv_jiner);
            viewHolder.day = (TextView)view.findViewById(R.id.tv_day);
            view.setTag(viewHolder);
        }
        else {
   
   
            viewHolder = (ViewHolder) view.getTag();
        }
        viewHolder.kahao.setText("卡号:"+temp.getBankId());
        viewHolder.type.setText("类型:"+temp.getType());
        viewHolder.money.setText("金额:"+String.valueOf(temp.getMoney()));
        viewHolder.day.setText("日期:"+temp.getDay());
        return view;
    }

先来看一下这个getView方法,所带有的四个参数。
1.i 这个i其实就是position位置,即现在是哪一个子项进行getView操作
2.view 视图,每一个子项,都有其自己的一个view,调用这个方法,最后返回的view,就是告诉适配器,这个子项最终显示的样子是什么样的
3.ViewGroup 视图组,这个我们这里暂时不用到。
回顾一下前面说的,在哪个context里面显示?显示的布局是什么样子的?所需要适配的又是什么?现在就来一一解决这些问题
1.view = inflater.inflate(R.layout.listview_jiaoyi,null);
通过这一行代码,为这个子项的view加入需要显示的布局,上下文context之前在构造函数中已经指明了。
2.






viewHolder = new ViewHolder();
            viewHolder.kahao = (TextView)view.findViewById(R.id.tv_idCard);
            viewHolder.type = (TextView)view.findViewById(R.id.tv_leixing);
            viewHolder.money = (TextView)view.findViewById(R.id.tv_jiner);
            viewHolder.day = (TextView)view.findViewById(R.id.tv_day);`

这一部分代码,实例化一个ViewHolder类,然后把里面的TextView和布局文件里面的控件一一绑定。
3.

JiaoYi temp = list_jiaoyi.get(i);
viewHolder.kahao.setText("卡号:"+temp.getBankId());
        viewHolder.type.setText("类型:"+temp.getType());
        viewHolder.money.setText("金额:"+String.valueOf(temp.getMoney()));
        viewHolder.day.setText("日期:"+temp.getDay());

这一部分的代码,解决了前面4个控件所需要适配显示的具体文本信息。首先定义一个数据源类对象temp ,从List的get方法为其实例化。然后就可以进行下面的具体赋值操作了。
4.最后,看最完整的getView,为什么要使用一个私有类ViewHolder?为什么要对view进行if(view == null)这样的判断呢?
因为在ListView显示的时候,如果有很多的数据,而我们上下滑动翻动的速度过快,那么适配器就会不停的进行getView方法,为了让效率提高,这里使用一个私有类ViewHolder,里面先绑定了需要适配的具体控件,这样就可以提高一点速度。其次,因为每一个getView,本身都会传入一个view参数,如果每一次进行getView,都要重写返回一个新的view,那样势必会造成内存的大量浪费。所以,我们可以通过view.setTag方法,可以把ViewHolder放入,当下一次调用这个子项的getView的时候,先判单一下view是否为空,如果不为空,就说明之前已经对其初始化一次了,那么就可以通过getTag方法,从里面取得ViewHolder,然后对里面的控件进行赋值操作。

模糊查找

如果是自定义的适配器,就需要自定义一个Filter类来实现模糊查找。
在刚刚的交易适配器类里面,再添加一个自定义类

class JiaoyiFilter extends Filter{
   
   

        //在下面这个方法中定义过滤的规则
        @Override
        protected FilterResults performFiltering(CharSequence charSequence) {
   
   
            FilterResults results = new FilterResults();    //过滤结果
            List<JiaoYi> list;                              //用于存放交易的结果
            if (TextUtils.isEmpty(charSequence)){
   
   
                list = list_forRemember;                   //如果过滤内容为空则显示全部,把备份的数据替换进去
            }else {
   
   
                list = new ArrayList<JiaoYi>();
                for (JiaoYi jiaoYi:list_forRemember){
   
         //遍历的对象是备份list 这里面才是全部元素
                    //下面是过滤的规则 从JiaoYi对象里面获得匹配内容然后判断是否contains过滤文本
                    if (jiaoYi.getBankId().contains(charSequence)){
   
   
                        list.add(jiaoYi);
                    }
                }
            }
            results.values = list;                         //设置value 为list
            results.count = list.size();                    //设置大小
            return results;
        }

        //在下面的方法中 通知适配器更新界面
        @Override
        protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
   
   
            list_jiaoyi = (List<JiaoYi>) filterResults.values;
            if (filterResults.count>0){
   
   
                notifyDataSetChanged();     //通知适配器数据已经改变
            }else {
   
   
                notifyDataSetInvalidated(); //通知数据失效
            }
        }

    }

别忘记:
class JiaoyiAdapter extends BaseAdapter implements Filterable
接着,重写getFilter方法

@Override
    public Filter getFilter() {
   
   
        if (jiaoyiFilter == null){
   
   
            jiaoyiFilter = new JiaoyiFilter();
        }
        return jiaoyiFilter;
    }

这里面的jiaoyiFilter是前面适配器的成员变量private JiaoyiFilter jiaoyiFilter;
写好了以后,在主活动里面listView_jiaoyi.setTextFilterEnabled(true);
为ListView启动过滤。然后就可以在活动里面进行过滤了
adapter_jiaoyi.getFilter().filter(edt_yhk.getText().toString());
如果筛选的条件为空,那么记得使用
adapter_jiaoyi.getFilter().filter("");把过滤清除
比如下面,我在搜索框内为其设置当输入内容发生改变的时候进行ListView的过滤





edt_yhk.addTextChangedListener(new TextWatcher() {
   
   
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
   
   

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
   
   
                if (TextUtils.isEmpty(edt_yhk.getText().toString())){
   
   
                    //清除过滤
                    adapter_jiaoyi.getFilter().filter("");
                }else {
   
   
                    adapter_jiaoyi.getFilter().filter(edt_yhk.getText().toString());
                    //list_jiaoyi = adapter_jiaoyi.getList_jiaoyi();
                }
            }

            @Override
            public void afterTextChanged(Editable editable) {
   
   
            }
        });

到此,基本上都已经讲完了。关于自定义的适配器,在主活动里面如何调用,这个和前面最简单的适配器其实是一模一样的,都是

list_jiaoyi = new ArrayList<JiaoYi>();
listView_jiaoyi = (ListView)findViewById(R.id.lv_jiaoyi);
adapter_jiaoyi = new JiaoyiAdapter(JiaoYiActivity.this,list_jiaoyi);
listView_jiaoyi.setAdapter(adapter_jiaoyi);

初始化所需要数据源List列表
绑定ListView控件
初始化自定义的适配器,用到context上下文以及List数据源列表两个参数
然后为这个ListView绑定适配器ListView.setAdapter。


如果要为List添加元素,可以通过List的add方法。或者remove方法来移除某个元素。再通过适配器的notifyDataSetChanged()方法动态刷新。

到此,这一篇关于ListView以及自定义适配器、模糊查找的学习笔记就结束了。本来还想着写RecyclerView的,但是内容有点长,有点啰嗦,就放在另一篇笔记里面记录吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章