ListView

作者: _戏_梦 | 来源:发表于2022-04-15 11:28 被阅读0次

    ListView的使用

    主要分为以下几个部分:

    1. ListView本身的处理

      findViewById,setAdapter

    2. 每个Item对应的数据和布局的处理

      对数据的获取,对布局的创建和处理

    3. 完成Adapter

      在Adapter中完成数据向界面的转换

    // 1. findViewById,获取ListView控件对象
    ListView list_view = findViewById(R.id.list_view);
    // 2. 需要有数据。显示列表型的数据
    // 一般情况下数据是需要从后台获取的,这里我们自己定义数据来模拟。
    initData();
    // 3. ListView中的每一个item的布局,需要我们自己定义,自己实现。
    // 在layout目录下,新建一个布局文件
    
    // 4. 完成Adapter类
    // Adapter:根据适配器设计模式来设计的。功能:将数据和界面进行适配。
    // ListView需要的是一个个View。而我们一般情况下有的是一条条数据。
    // Adapter的作用就是将数据转化成View,然后把这些View给ListView
    
    // 5. 将数据设置进Adapter中,然后将Adapter对象设置进ListView
    FruitAdapter adapter = new FruitAdapter(initData());
    list_view.setAdapter(adapter);
    

    Adapter

    自定义类,继承android.widget.BaseAdapter类。然后复写BaseAdapter中的四个方法。

    1. Adapter的四个覆写方法的意义

    getCount():告诉ListView,一共需要展示多少条数据。写法固定,几乎不会变。

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

    getView(): 当某个item想要展示在屏幕上时,就会调用对应position的getView方法。

    /**
     * 当某个item想要展示在屏幕上时,就会调用对应position的getView方法
     *
     * @param position    在ListView中的索引
     * @param convertView 复用的View
     * @param parent      当前View的容器
     * @return 要展示在界面上的view
     */
    @Override
    @SuppressLint("ViewHolder")
    public View getView(int position, View convertView, ViewGroup parent) {
        // 1. 找到布局文件,加载
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_fruit_first, parent, false);
        // 2. findViewById,将数据塞进去
        // 不同的Item,对数据的处理不同
        return view;
    }
    

    getItem()方法: AdapterView.getItemAtPosition()方法获取的值就是这个方法的返回值。其实作用不大,可以不管它。

    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            // ListView的想法是这样,只是我们一般不这么做
            Object item = parent.getItemAtPosition(position);
        }
    });
    

    getItemId()方法: 在点击事件响应的处理方法中的id的值就是这个方法的返回值。作用不大,也可以不管它。

    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        // 这个方法的最后一个参数id就是Adapter中的getItemId()方法的返回值。
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    
        }
    });
    

    完整代码

    Activity中:

    initData方法只是生成一个List。

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // 如何使用ListView
            // 1. findViewById
            ListView list_view = findViewById(R.id.list_view);
            // 2. 需要有数据。显示列表型的数据
            // 一般情况下数据是需要从后台获取的,这里我们自己定义数据来模拟。
            initData();
            // 3. ListView中的每一个item的布局,需要我们自己定义,自己实现。
            // 在layout目录下,新建一个布局文件
    
            // 4. 完成Adapter类
            // Adapter:根据适配器设计模式来设计的。功能:将数据和界面进行适配。
            // ListView需要的是一个个View。而我们一般情况下有的是一条条数据。
            // Adapter的作用就是将数据转化成View,然后把这些View给ListView
    
            // 5. 将数据设置进Adapter中,然后将Adapter对象设置进ListView
            FruitAdapter adapter = new FruitAdapter(initData());
            list_view.setAdapter(adapter);
    
        }
    }
    

    数据类:

    /**
     * 要显示在ListView中的数据Bean类
     */
    public class Fruit {
    
        private String name;
        private int drawableRes;
        // 其它的getter/setter、构造方法、toString等方法
    }
    

    item_fruit布局文件:很简单,左边一个ImageView,紧跟着一个TextView

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="60dp">
    
        <ImageView
            android:id="@+id/iv_fruit"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_marginStart="16dp"
            android:src="@drawable/apple_pic"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <TextView
            android:id="@+id/tv_fruit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginStart="16dp"
            android:text="Apple"
            android:textSize="16sp"
            app:layout_constraintStart_toEndOf="@id/iv_fruit"
            app:layout_constraintTop_toTopOf="parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    Adapter类:

    /**
     * Adapter的使用过程:
     * 1. 继承BaseAdapter类
     * 2. 添加4个空方法。
     * 3. 添加成员变量data,并且提供从外部设置data的方式
     * 4. 复写getCount()方法和getView()方法。
     */
    public class FruitAdapter extends BaseAdapter {
    
        /**
         * 这个data里面存的数据就是将要显示出来的数据
         */
        private ArrayList<Fruit> data;
    
        /**
         * 通过构造方法将数据传递进来
         * @param data
         */
        public FruitAdapter(ArrayList<Fruit> data) {
            this.data = data;
        }
    
        /**
         * 告诉ListView,一共需要展示多少条数据。写法固定,几乎不会变。
         *
         * @return
         */
        @Override
        public int getCount() {
            return data == null ? 0 : data.size();
        }
    
        /**
         * 当某个item想要展示在屏幕上时,就会调用对应position的getView方法。
         *
         * @param position    在ListView中的索引
         * @param convertView 复用的View
         * @param parent      当前View的容器
         * @return 要展示在界面上的view
         */
        @Override
        @SuppressLint("ViewHolder")
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_fruit, parent, false);
            // 将xml布局渲染成view了以后,还需要对view内部的控件设置对应的数据。
            ImageView iv = view.findViewById(R.id.iv_fruit);
            TextView tv = view.findViewById(R.id.tv_fruit);
            Fruit fruit = data.get(position);
            iv.setImageResource(fruit.getDrawableRes());
            tv.setText(fruit.getName());
            return view;
        }
    
    
        //-------------------------------------------------------------------------------------------
        // 下面这两个方法我们不需要管。
        @Override
        public Object getItem(int position) {
            return null;
        }
    
        @Override
        public long getItemId(int position) {
            return 0;
        }
    }
    

    优化

    为什么需要优化?

    ListView并不会一次性创建所有的itemView,而是只在itemView要显示到屏幕上时再调用getView方法获取itemView对象。在滑动屏幕时,ListView会不断的调用Adapter中的getView方法获取对象,就会有两个问题。

    1. getView中每次都要创建新的View对象,而view的inflate过程是很耗费CPU和内存资源的。
    2. 每次getView时,都会将view中的控件使用findViewById()方法查找一遍,这个方法也比较耗费资源。

    所以在快速滑动ListView时,就会有可能出现界面卡顿的情况。

    /**
     * 原来的getView方法每次都是重新加载布局
     */
    @Override
    @SuppressLint("ViewHolder")
    public View getView(int position, View convertView, ViewGroup parent) {
        // 1. 找到布局文件,加载
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_fruit_first, parent, false);
        // 2. findViewById,将数据塞进去
        ImageView iv = view.findViewById(R.id.iv_fruit_first);
        TextView tv = view.findViewById(R.id.tv_fruit_first);
        Fruit fruit = data.get(position);
        iv.setImageResource(fruit.getDrawableResId());
        tv.setText(fruit.getName());
        return view;
    }
    

    getView()方法的优化:

    第一步:复用convertView

    什么是convertView?

    在滑动过程中,被移除的itemView,并不会被直接GC,而是存在于ListView的一个缓冲池中,以备复用。

    /**
     * 当某个item想要展示在屏幕上时,就会调用对应position的getView方法
     *
     * @param position    在ListView中的索引
     * @param convertView 复用的View
     * @param parent      当前View的容器
     * @return 要展示在界面上的view
     */
    @Override
    @SuppressLint("ViewHolder")
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView==null){
            // 如果convertView是null,表明这个布局需要重新渲染
            convertView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_fruit_first, parent, false);
        }else {
            // 否则,我们可以复用convertView
        }
        ImageView iv = convertView.findViewById(R.id.iv_fruit_first);
        TextView tv = convertView.findViewById(R.id.tv_fruit_first);
        Fruit fruit = data.get(position);
        iv.setImageResource(fruit.getDrawableResId());
        tv.setText(fruit.getName());
        return convertView;
    }
    

    第二步:复用convertView中的控件

    借助view的tag和对象,使用ViewHolder这个内部类,来减少findViewById()方法的使用

    static class ViewHolder {
        ImageView iv;
        TextView tv;
    }
    
    /**
     * 当某个item想要展示在屏幕上时,就会调用对应position的getView方法
     *
     * @param position    在ListView中的索引
     * @param convertView 复用的View
     * @param parent      当前View的容器
     * @return 要展示在界面上的view
     */
    @Override
    @SuppressLint("ViewHolder")
    public View getView(int position, View convertView, ViewGroup parent) {
    
        if (convertView == null) {
            // 如果convertView是null,表明这个布局需要重新渲染
            convertView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_fruit_first, parent, false);
            // 创建ViewHolder对象,将查找的控件对象赋值给ViewHolder中的成员变量,给convertView设置tag。
            // 达到else分支里面可以复用的目的。
            ViewHolder holder = new ViewHolder();
            holder.ivFruit = convertView.findViewById(R.id.iv_fruit_first);
            holder.tvFruit = convertView.findViewById(R.id.tv_fruit_first);
            convertView.setTag(holder);
            // 处理完了convertView和holder。不要忘记设置数据
            Fruit fruit = data.get(position);
            holder.ivFruit.setImageResource(fruit.getDrawableResId());
            holder.tvFruit.setText(fruit.getName());
            return convertView;
        } else {
            // 否则,我们可以复用convertView
            // getView最根本的目的是两个,一个是return一个view显示;另一个是将这个view中的控件显示正常的数据
            // 要想获取控件,需要先获取ViewHolder对象,通过convertView的getTag方法获取
            ViewHolder holder = (ViewHolder) convertView.getTag();
            Fruit fruit = data.get(position);
            holder.ivFruit.setImageResource(fruit.getDrawableResId());
            holder.tvFruit.setText(fruit.getName());
            return convertView;
        }
    }
    

    最终版本:

    在之前的基础上,将代码中重复的部分处理掉。

    static class ViewHolder {
        ImageView iv;
        TextView tv;
    }
    
    /**
     * 当某个item想要展示在屏幕上时,就会调用对应position的getView方法
     *
     * @param position    在ListView中的索引
     * @param convertView 复用的View
     * @param parent      当前View的容器
     * @return 要展示在界面上的view
     */
    @Override
    @SuppressLint("ViewHolder")
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            convertView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_fruit_first, parent, false);
            holder = new ViewHolder();
            holder.ivFruit = convertView.findViewById(R.id.iv_fruit_first);
            holder.tvFruit = convertView.findViewById(R.id.tv_fruit_first);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        Fruit fruit = data.get(position);
        holder.ivFruit.setImageResource(fruit.getDrawableResId());
        holder.tvFruit.setText(fruit.getName());
        return convertView;
    }
    

    ListView的点击事件

    // ListView中的Item的点击事件
    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        /**
         * ListView中的Item的点击事件的回调方法
         * @param parent    parent其实就是当前这个listView
         * @param view      点击的那个Item对应的View,也就是Adapter的getView方法返回的view
         * @param position  点击的那个Item对应的索引
         * @param id        就是Adapter的getItemId()方法返回的值,但是我们一般不用
         */
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Log.i(TAG, "onItemClick: position: " + position);
            Log.i(TAG, "onItemClick: id: " + id);
            // 点击Item之后,弹出Toast,显示对应的View上的水果的名称
            // 使用parent/listView.getItemAtPosition(position)就是调用Adapter中的getItem()方法
            // 我们一般不用这种方式,因为数据我们能轻易获取,有了position了,使用data.get(position)更方便
            //Fruit fruit = (Fruit) parent.getItemAtPosition(position);
            //String msg = fruit.getName();
            // 这是直接使用data的用法
            String msg = adapter.data.get(position).getName();
            Toast.makeText(SecondActivity.this, msg, Toast.LENGTH_SHORT).show();
        }
    });
    

    参考资料

    相关文章

      网友评论

          本文标题:ListView

          本文链接:https://www.haomeiwen.com/subject/cbwfertx.html