5. 列表控件

一、ListView

1. 简单用法

在布局文件中创建一个ListView,修改代码让其不满整个屏幕:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android: layout_height= "match_parent">
    <ListView
    android:id="@+id/list_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
</LinearLayout>

MainActivity中为ListView绑定数据:

​ 这里为ListView绑定了一个string数组。

​ 列表控件中的子项是一个TextView,当然除了TextView之外,我们还可以自定义列表的子项。

public class MainActivity extends AppCompatActivity {
    private String[] data = { "Apple", "Banana", "Orange", "Watermelon",
        "Pear", "Grape", "Pineapple", "Strawberry", "Cherry""Mango" ,
        "Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape" ,
        "Pineapple", "Strawberry", "Cherry", "Mango" };
    @0verride
    protected void onCreate (Bundle savedInstanceState) {
    super.onCreate (savedIns tanceState);
        setContentView(R.layout.activity_main);
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
        MainActivity.this, android.R.layout.simple_list_item_1, data);
        ListView listView = (ListView)findViewById(R.id.list_view) ;
        listView.setAdapter(adapter);
    }
}

2. 定制ListView的界面

关键是需要设置好适配器Adapter,我们可以自定义一个列表项的视图,将交给适配器,再将适配器与ListViewListView根据需要从ListAdapter请求视图,例如在用户向上或向下滚动时显示新视图。

在这里插入图片描述

常用的适配器类有: ArrayAdapterSimpleAdapterBaseAdapter。他们之间的关系如下:

在这里插入图片描述

(1)ArrayAdapter适配器

使用步骤:

  1. xml 文件布局上实现 ListView
  2. Activity 中定义数据源(列表或者数组)
  3. 构造 ArrayAdapter 对象,设置适配器
  4. ListView 绑定到 ArrayAdapter

构造函数:

ArrayAdapter<String> adapter = new ArrayAdapter<String>(
        MainActivity.this, android.R.layout.simple_list_item_1, data);

实例化ArrayAdapter对象传入了三个参数,第一个参数是上下文对象,第二个参数是子项布局,这里调用了系统内容的布局,第三个参数是数据集,这里传入的是字符串数组。

(2)SimpleAdapter适配器

ArrayAdapter适用于显示信息比较单一的场景,若显示项中包含多种形式的数据,就不太适用了,而SimpleAdapter可以适配多种数据类型。

使用步骤:

  1. xml中添加ListView
  2. 实现item布局(根据实际UI需求)
  3. 创建数据源(数据源形式有要求 List<?extends Map<String,? >>
  4. 创建SimpleAdapter适配器
  5. SimpleAdapter 适配器绑定到 ListView

构造函数:

 SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
  • context:即上下文对象;
  • data:是一个包裹Map集合的List数据集;
  • resource:子项布局文件;
  • from:是一个字符串数组,字符串指的是Map中的键值;
  • to:是一个int型数组,表示子项布局中对应控件的id

示例:

子项布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="horizontal"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/img"
        android:src="@mipmap/ic_launcher"
        android:layout_width="50dp"
        android:layout_height="50dp" />
    <TextView
        android:id="@+id/tv"
        android:text="hello"
        android:gravity="center"
        android:layout_marginLeft="50dp"
        android:textSize="28sp"
        android:layout_width="wrap_content"
        android:layout_height="50dp" />
</LinearLayout>

主视图布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

代码:

public class MainActivity extends Activity {
    private ListView listView;
    private SimpleAdapter simpleAdapter;
    private List<Map<String, Object>> datas=new ArrayList<Map<String, Object>>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView=(ListView)findViewById(R.id.listview);
        initDatas();//初始化数据集
//实例化SimpleAdapter
        simpleAdapter=new SimpleAdapter(this,datas,R.layout.animal_layout,new String[]{"img","name"},new int[]{R.id.img,R.id.tv});        listView.setAdapter(simpleAdapter);//设置配置器
    }
    private void initDatas() {
        Map map1=new HashMap();
        map1.put("img",R.drawable.fish);
        map1.put("name","小金鱼");
        Map map2=new HashMap();
        map2.put("img",R.drawable.horse);
        map2.put("name","千里马");
        Map map3=new HashMap();
        map3.put("img",R.drawable.mouse);
        map3.put("name","米老鼠");
        datas.add(map1);
        datas.add(map2);
        datas.add(map3);
    }
}
(3) BaseAdapter适配器

ArrayAdapter一般适用于数据源数据种类比较单一的情形,若数据类型比较复杂,需要个性化定制布局,则可以采用BaseAdapter适配器进行数据适配。

若要问BaseAdapterSimpleAdapter有什么不同,通过API文档可以获悉,SimpleAdapterBaseAdapter的子类,BaseAdapterSimpleAdapter来讲更为灵活,在开发中也更为常用。

实现步骤:

  1. 在布局中添加 ListView
  2. 实现 item 布局(根据 UI 设计的)
  3. 创建数据源
  4. 创建自己的 Adapter 类继承 BaseAdapter
  5. 创建自定义的 Adapter 类对象
  6. 将创建的适配器绑定到 ListView

构造函数:

构造自己的Adapter,继承自BaseAdapter类,必须需要覆写四个方法。

// 继承 BaseAdapter 必须要实现它的 4 个方法
class MyAdapter extends BaseAdapter{
    private Context context;
    private List<Animal> datas;
    
    // 返回适配器中所代表的数据集合的条数
    // 会首先执行这个方法(连续执行好几次),如果是 0 则后面的方法就不会执行了
    @Override
    public int getCount() {
        return datas.size();
    }

    // 返回数据集合中指定索引 position 对应的数据项
    // 手动调用才会执行
    @Override
    public Object getItem(int position) {
        return datas.get(position);
    }
    // 返回列表中与指定索引对应的行 id
    // 手动调用才会执行
    @Override
    public long getItemId(int position) {
        return position;
    }
    // 返回指定索引对应的数据的视图,会多次调用
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Animal animal= (Animal) getItem(position);
        View view;
        ViewHolder viewHolder;
        if(convertView==null){
            view = LayoutInflater.from(context).inflate(R.layout.animal_layout,null);
            viewHolder=new ViewHolder();
            viewHolder.animalImage=(ImageView)view.findViewById(R.id.img);
            viewHolder.animalName=(TextView)view.findViewById(R.id.tv);
            view.setTag(viewHolder);
        }else{
            view=convertView;
            viewHolder= (ViewHolder) view.getTag();
        }
        viewHolder.animalName.setText(animal.getAnimal());
        viewHolder.animalImage.setImageResource(animal.getImgId());
        return view;
    }
    //创建ViewHolder类
    class ViewHolder{
        ImageView animalImage;
        TextView animalName;
    }
}


3. 优化

优化方式一:

界面上有多少个Item,那么getView方法就会被调用多少次!使用 convertView 作为 View 缓存 ,将 convertView 作为 getView 的输入参数、返回参数借助 ListView 的缓存机制,实现 view 的复用。(如BaseAdapter上面的代码)

优化方式二:

getView()会被调用多次,那么findViewById不一样得调用多次,而我们的ListViewItem 一般都是一样的布局,我们可以对这里在优化下,我们可以自己定义一个ViewHolder类来对这一部分 进行性能优化!在方式一的基础上,进行优化,引入 ViewHolder 减少 findViewById的次数。

 @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Animal animal= (Animal) getItem(position);
        View view;
        ViewHolder viewHolder;
        // 判断 convertView 是否已经存在
        if(convertView==null){
            view = LayoutInflater.from(context).inflate(R.layout.animal_layout,null);
            viewHolder=new ViewHolder();
            viewHolder.animalImage=(ImageView)view.findViewById(R.id.img);
            viewHolder.animalName=(TextView)view.findViewById(R.id.tv);
            view.setTag(viewHolder);
        }else{
            view=convertView;
            viewHolder= (ViewHolder) view.getTag();
        }
        // 减少 findViewById 的次数
        viewHolder.animalName.setText(animal.getAnimal());
        viewHolder.animalImage.setImageResource(animal.getImgId());
        return view;
    }

参考:掘金文章

4. 点击事件

这个比较简单:

public class MainActivity extends AppCompatActivity {
    private List<Fruit> fruitList = new ArrayList<>();
    @0verride
    protected void onCreate ( Bundle savedInstanceState) {
        super.onCreate ( savedInstanceState);
        setContentView(R. layout . activity_ main) ;
        initFruits();
        FruitAdapter adapter = new F rui tAdapter (MainActivity.this, R.layout.fruit_item,fruitList);
        ListView listView = (ListView) findViewById(R.id.list_ view) ;
        listView. setAdapter(adapter);
        listView. setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @0verride
        public void onItemClick (AdapterView<?> parent, View view,
            int position, Long id) {
            // 获取点击的是哪一个 Item
            Fruit fruit = fruitList.get(position) ;
            Toast.makeText (MainActivity.this, fruit.getName(),
            Toast.LENGTH_SHORT).show();
        }
    });
}

二、RecyclerView

ListView有强大的功能,但如果我们不使用一些技巧来提升它的运行效率,那么ListView 的性能就会非常差。还有,ListView 的扩展性也不够好,它只能实现数据纵向滚动的效果,如果我们想实现横向滚动的话,ListView 是做不到的。

​ 为此,Android 提供了一个更强大的滚动控件RecyclerView。 它可以说是一个增强版的ListView,不仅可以轻松实现和ListView同样的效果,还优化了ListView中存在的各种不足之处。

步骤:

  1. 添加Recyclerview依赖。
  2. xml文件中添加Recyclerview控件。
  3. 创建RecyclerviewAdapter(适配器)。
  4. 将适配器设置给Recyclerview
1. 添加Recyclerview依赖

想要使用RecyclerView这个控件,首先需要在项目的build.gradle中添加相应的依赖库才行。

implementation 'com.android.support:recyclerview-v7:28.0.0'

添加完之后记得要点击一下Sync Now来进行同步。

2. 在xml文件中添加Recyclerview控件

Activity布局文件activity_rv.xml

Item的布局文件item_1.xml
...

3. 创建Adapter

标准实现步骤如下:
创建Adapter:创建一个继承RecyclerView.AdapterAdapter类(VHViewHolder的类名)
创建ViewHolder:在Adapter中创建一个
继承RecyclerView.ViewHolder的静态内部类,记为VHViewHolder的实现和ListViewViewHolder实现几乎一样。
③ 在
Adapter中实现3个方法

  • onCreateViewHolder()
    这个方法主要生成每个Item inflater出一个View,但是该方法返回的是一个**ViewHolder**。该方法把View直接封装在ViewHolder中,然后我们面向的是ViewHolder这个实例,当然这个ViewHolder需要我们自己去编写。

需要注意的是在onCreateViewHolder()中,映射**Layout必须为**

View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);

而不能是:

View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, null);
  • onBindViewHolder()
    这个方法主要用于适配渲染数据到View中。方法提供给你了一**viewHolder**而不是原来的convertView
  • getItemCount()
    这个方法就类似BaseAdapter的**getCount**方法了,即总共有多少个条目。

可以看出,RecyclerViewListViewgetView()的功能拆分成了onCreateViewHolder()onBindViewHolder()

基本的Adapter实现如下:

// ① 创建Adapter
public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.VH>{
    //② 创建ViewHolder
    public static class VH extends RecyclerView.ViewHolder{
        public final TextView title;
        public VH(View v) {
            super(v);
            title = (TextView) v.findViewById(R.id.title);
        }
    }
    
    private List<String> mDatas;
    public NormalAdapter(List<String> data) {
        this.mDatas = data;
    }

    //③ 在Adapter中实现3个方法
    @Override
    public void onBindViewHolder(VH holder, int position) {
        holder.title.setText(mDatas.get(position));
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //item 点击事件
            }
        });
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    @Override
    public VH onCreateViewHolder(ViewGroup parent, int viewType) {
        //LayoutInflater.from指定写法
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);
        return new VH(v);
    }
}
4. 设置

创建完Adapter,接着对RecyclerView进行设置,一般来说,需要为RecyclerView进行以下设置:

  • Layout ManagerItem的布局。
  • Adapter:为Item提供数据。
  • Item DecorationItem之间的Divider
  • Item Animator:添加、删除Item动画。
Layout Manager

RecyclerView提供了三种布局管理器

  • LinerLayoutManager垂直或者水平列表方式展示Item
  • GridLayoutManager网格方式展示Item
  • StaggeredGridLayoutManager瀑布流方式展示Item

如果你想用 RecyclerView 来实现自己自定义效果,则应该去继承实现自己的 LayoutManager,并重写相应的方法,而不应该想着去改写 RecyclerView

5. 点击事件

RecyclerView并没有像ListView一样暴露出Item点击事件或者长按事件处理的api,也就是说使用RecyclerView时候,需要我们自己来实现Item的点击和长按等事件的处理。
实现方法有很多:

  • 可以监听RecyclerViewTouch事件然后判断手势做相应的处理,
  • 也可以通过在绑定ViewHolder的时候设置监听,然后通过Apater回调出去
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{
    // 展示数据
    private ArrayList<String> mData;
    // 事件回调监听
    private MyAdapter.OnItemClickListener onItemClickListener;
    public MyAdapter(ArrayList<String> data) {
        this.mData = data;
    }
    public void updateData(ArrayList<String> data) {
        this.mData = data;
        notifyDataSetChanged();
    }
    // 添加新的Item
    public void addNewItem() {
        if(mData == null) {
            mData = new ArrayList<>();
        }
        mData.add(0, "new Item");
        notifyItemInserted(0);
    }
    // 删除Item
    public void deleteItem() {
        if(mData == null || mData.isEmpty()) {
            return;
        }
        mData.remove(0);
        notifyItemRemoved(0);
    }

    // ① 定义点击回调接口
    public interface OnItemClickListener {
        void onItemClick(View view, int position);
        void onItemLongClick(View view, int position);
    }
    
    // ② 定义一个设置点击监听器的方法
    public void setOnItemClickListener(MyAdapter.OnItemClickListener listener) {
        this.onItemClickListener = listener;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // 实例化展示的view
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_item, parent, false);
        // 实例化viewholder
        ViewHolder viewHolder = new ViewHolder(v);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        // 绑定数据
        holder.mTv.setText(mData.get(position));
        //③ 对RecyclerView的每一个itemView设置点击事件
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if(onItemClickListener != null) {
                    int pos = holder.getLayoutPosition();
                    onItemClickListener.onItemClick(holder.itemView, pos);
                }
            }
        });

        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if(onItemClickListener != null) {
                    int pos = holder.getLayoutPosition();
                    onItemClickListener.onItemLongClick(holder.itemView, pos);
                }
                //表示此事件已经消费,不会触发单击事件
                return true;
            }
        });
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {
        TextView mTv;
        public ViewHolder(View itemView) {
            super(itemView);
            mTv = (TextView) itemView.findViewById(R.id.item_tv);
        }
    }
}

设置Adapter的事件监听。

mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(MDRvActivity.this,"click " + position + " item", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongClick(View view, int position) {
        Toast.makeText(MDRvActivity.this,"long click " + position + " item", Toast.LENGTH_SHORT).show();
    }
});

参考:

Android 控件 RecyclerView

RecyclerView使用指南

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值