Android中ListView的使用

ListView使用的步骤

在什么情况下会需要使用 ListView 组件呢? 当有很多项元素,而且每一项元素的布局都是相同的情况下就可以使用 ListView 组件了, ListView = List + View

ListView使用示例

接下来先看看一个 ListView 的使用示例,然后再看看其中关键的元素,最终的显示效果如下:

先看看这个示例涉及到类和布局文件有那些,项目的整体结构如下所示:

makefile 复制代码
E:.
│  AndroidManifest.xml
├─java
│  └─com
│      └─example
│          └─ademo
│              │  MainActivity.java
│              │
│              ├─adapter
│              │      FruitAdapter.java
│              │
│              └─vo
│                      EachItem.java
│
└─res
    ├─drawable
    │      ic_launcher_background.xml
    │      ic_launcher_foreground.xml
    ├─layout
    │      activity_main.xml
    │      fruit_item.xml
  • MainActivity 是项目的主 Activity
  • EachItem 是每一行需要展示的数据内容
  • FruitAdapter 是继承了 ArrayAdapter 的类,使用 ListView 的时候需要使用 Adapter
  • activity_main.xml 是主布局文件
  • fruit_item.xml 是每一行的布局形式
  • AndroidManifest.xml 是 android 项目的主配置文件

AndroidManifest.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools">  
  
    <application  
        android:allowBackup="true"  
        android:dataExtractionRules="@xml/data_extraction_rules"  
        android:fullBackupContent="@xml/backup_rules"  
        android:icon="@mipmap/ic_launcher"  
        android:label="@string/app_name"  
        android:roundIcon="@mipmap/ic_launcher_round"  
        android:supportsRtl="true"  
        android:theme="@style/Theme.ADemo"  
        tools:targetApi="31">  
        <activity  
            android:name=".MainActivity"  
            android:exported="true"  
            android:label="MainActivity">  
            <intent-filter>  
                <action android:name="android.intent.action.MAIN" />  
  
                <category android:name="android.intent.category.LAUNCHER" />  
            </intent-filter>  
        </activity>  
    </application>  
  
</manifest>

EachItem

EachItem 就是存储的每一项的数据

java 复制代码
package com.example.ademo.vo;  
  
public class EachItem {  
    private String name;
    // imageId 就是存储的图片的id
    private int imageId;  
    public EachItem(String name, int imageId)  
    {  
        this.name = name;  
        this.imageId = imageId;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public int getImageId() {  
        return imageId;  
    }  
}

fruit_item.xml

  • fruit_item.xml 就是每一项数据最终需要展示的样子
  • 也就是最终是要将每一个 EachItem 类型的数据映射到 fruit_item 这个布局上
    • fruit_item 布局是 LinearLayout 类型,左侧是一个 ImageView, 右侧是一个 TextView
    • 最终呈现的每一行的效果就是左侧是图片,右侧是文字
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/fruit_image"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content" />  
  
    <TextView  
        android:id="@+id/fruit_name"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_gravity="center_vertical"/>  
  
</LinearLayout>

FruitAdapter

这个 Adapter 的作用是什么呢? 可以先看看 FruitAdapter 的继承体系

下面是 FruitAdapter 类的内容,后面会解释这个类的作用

java 复制代码
package com.example.ademo.adapter;  
  
import android.content.Context;  
import android.view.LayoutInflater;  
import android.view.View;  
import android.view.ViewGroup;  
import android.widget.ArrayAdapter;  
import android.widget.ImageView;  
import android.widget.TextView;  
  
import androidx.annotation.NonNull;  
import androidx.annotation.Nullable;  
  
import com.example.ademo.R;  
import com.example.ademo.vo.EachItem;  
  
import java.util.List;  
  
public class FruitAdapter extends ArrayAdapter<EachItem> {  
  
    private int resourceId;  
	// 这里的 textViewResourceId 在该例子中就是 fruit_item.xml
    public FruitAdapter(@NonNull Context context, int textViewResourceId, List<EachItem> eachItems) {  
        super(context, textViewResourceId, eachItems);  
        resourceId = textViewResourceId;  
    }  
  
    @NonNull  
    @Override    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {  
        EachItem eachItem = getItem(position);  
        View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);  
        ImageView imageView = view.findViewById(R.id.fruit_image);  
        TextView textView = view.findViewById(R.id.fruit_name);  
        imageView.setImageResource(eachItem.getImageId());  
        textView.setText(eachItem.getName());  
        return view;  
    }  
}

FruitAdapter 顶层父类是 BaseAdapter, 其实前面也说过,ListView = List + View, ListView 只负责将数据一行行的按照要求进行展示,但是具体到每一行的数据应该是要展示成什么样子呢?这个就不是 ListView 来负责的,上面已经说过每一行数据最终是通过 fruit_item.xml 这个布局文件来呈现的,那么现在的问题就是怎么将 ListView 和 fruit_item 进行关联的? 其实就是通过 Adapter 来进行关联,所以 Adapter 做的事情就是将我们一行行的数据(比如这个例子中就是 EachItem 结构的数据) 和每一行展示的视图关联起来(这个视图就是 fruit_item.xml 文件)

activity_listview.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent">  
  
    <ListView  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:id="@+id/list_view"/>  
</LinearLayout>

MainActivity

java 复制代码
package com.example.ademo;  
  
import android.os.Bundle;  
import android.util.Log;  
import android.widget.ListView;  
  
import androidx.appcompat.app.AppCompatActivity;  
  
import com.example.ademo.adapter.FruitAdapter;  
import com.example.ademo.vo.EachItem;  
  
import java.util.ArrayList;  
import java.util.List;  
  
public class MainActivity extends AppCompatActivity {  
  
    private List<EachItem> eachItems = new ArrayList<>();  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        Log.d("MainActivity", "开始创建主Activity" + this);  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_listview);  
        initItems();
        // adapter 做的第一件事情就是关联 fruit_item
        FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, eachItems);  
        ListView listView = findViewById(R.id.list_view);
        // adapter 做的第二件事情就是和 ListView 进行关联 
        listView.setAdapter(adapter);  
    }  
  
    private void initItems() {  
        for (int i = 0; i < 20; i++) {  
            EachItem eachItem = new EachItem("苹果" + i, R.drawable.ic_launcher_background);  
            eachItems.add(eachItem);  
        }  
    }  
}

在 MainActivity 中也看到了 Adapter 做的事情其实就是桥梁,前面说过 ListView = List + View, Adapter 就是承担这个 "+" 的作用,将每一项的布局(View) 设置给 ListView 组件

ListView性能优化

性能瓶颈在 Adapter 的 getView 方法中,接下来看看这个方法

java 复制代码
public class FruitAdapter extends ArrayAdapter<EachItem> {  
  
    private int resourceId;  
  
    public FruitAdapter(@NonNull Context context, int textViewResourceId, List<EachItem> eachItems) {  
        super(context, textViewResourceId, eachItems);  
        resourceId = textViewResourceId;  
    }  
  
    @NonNull  
    @Override    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {  
        EachItem eachItem = getItem(position);  
        View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);  
        ImageView imageView = view.findViewById(R.id.fruit_image);  
        TextView textView = view.findViewById(R.id.fruit_name);  
        imageView.setImageResource(eachItem.getImageId());  
        textView.setText(eachItem.getName());  
        return view;  
    }  
}

getView 方法是在滑动页面的时候来加载数据的,即使前面的数据已经加载了,当将屏幕往上滑动的时候还是会调用这个方法,这个方法会通过 View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false); 这行代码来加载单个布局文件,当数据比较多的时候,对于已经加载的数据重复加载可能会存在瓶颈,所以可以通过 convertView 这个参数来优化,这个参数是将之前已经加载好的布局进行缓存,因此可以进行如下方式修改:

java 复制代码
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {  
    EachItem eachItem = getItem(position);  
    View view;  
    if (convertView == null) {  
        view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);  
    } else {  
        view = convertView;  
    }  
    ImageView imageView = view.findViewById(R.id.fruit_image);  
    TextView textView = view.findViewById(R.id.fruit_name);  
    imageView.setImageResource(eachItem.getImageId());  
    textView.setText(eachItem.getName());  
    return view;  
}

ListView数据变化后更新视图

在 BaseAdapter 中有如下方法

java 复制代码
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
	public void notifyDataSetChanged() {  
	    mDataSetObservable.notifyChanged();  
	}
}

所以当需要展示的数据变化后,只需要调用 Adapter 的 notifyDataSetChanged 方法即可

相关推荐
千里马学框架13 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台13 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone14 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc14 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo16 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770016 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼17 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone18 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen18 小时前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone18 小时前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui