65.9K
CodeProject 正在变化。 阅读更多。
Home

Android 中的 ListView 实现

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2020 年 10 月 25 日

CPOL

8分钟阅读

viewsIcon

9067

downloadIcon

287

本文详细描述了如何在 Android 中实现 ListView,并提供足够的信息。

引言

有时,需要在 Android 中显示一组可以动态操作的可滚动项。这些可滚动项收集在一个 List (ArrayList) 对象中,该对象包含所有希望正确显示的项。例如,在开发的应用程序中,用户就是可滚动项。主要目标是通过可滚动可视化在屏幕上显示 List 中的项(用户)。为此,需要两个对象,即 ListViewArrayAdapter

本文开发了一个显示项目列表的应用程序。这些项目是一组用户,他们具有一些属性,即图像、用户名、电话号码和电子邮件地址。本文将正确显示这些属性。

ListView 是一个 Java public 类,它扩展自 AbsListView,用于分组多个项并将它们显示在垂直可滚动的列表中。应注意,当需要时,列表会根据数据量自动滚动。必须理解,ListView 仅负责视觉上呈现这些项。

ArrayAdapter 是另一个 Java public 类,它扩展了 BaseAdapter 类,该类将 List 中的项进行适配,并为 ListView 提供它们。ArrayAdapter 在概念上是项 ListListView 对象之间的桥梁。事实上,ArrayAdapter 负责为 ListView 提供位于特定位置的视图。

ListView 实现

本节包含一些将在下面介绍的子节。

创建 ListView

创建 ListView 包括两个单独的步骤,这些步骤在本小节中进行了介绍。第一步是设计一个 XML 布局来表示每个项(用户)的视图模板。在开发的应用程序中,每个用户应具有用户名、电话号码、电子邮件地址和个人图片。因此,应正确设计 XML 布局,使其能够表示每个项(用户)的相关数据。为此,XML 布局(位于 /res/layout/item.xml)设计如下:

<!--?xml version="1.0" encoding="utf-8"?-->
<linearlayout android:layout_height="match_parent" android:layout_width="match_parent" 
 android:orientation="horizontal" android:paddingbottom="3dp" android:paddingleft="3dp" 
 android:paddingright="3dp" android:paddingtop="3dp" 
 xmlns:android="http://schemas.android.com/apk/res/android">
    <linearlayout android:layout_gravity="center_horizontal|center_vertical" 
     android:layout_height="match_parent" android:layout_width="wrap_content" 
     android:orientation="vertical">
        <linearlayout android:layout_gravity="center_horizontal|center_vertical" 
         android:layout_height="wrap_content" android:layout_width="wrap_content" 
         android:orientation="vertical">
            <imageview android:id="@+id/iv" android:layout_height="wrap_content" 
             android:layout_width="wrap_content">
        </imageview></linearlayout>
        <linearlayout android:layout_gravity="center_horizontal|center_vertical" 
         android:layout_height="wrap_content" android:layout_width="wrap_content" 
         android:orientation="vertical">
            <textview android:id="@+id/tvu" android:layout_height="wrap_content" 
             android:layout_width="wrap_content" android:text="" 
             android:textcolor="#000000" android:textsize="20dp">
        </textview></linearlayout>
    </linearlayout>
    <linearlayout android:layout_gravity="center_horizontal|center_vertical" 
     android:layout_height="match_parent" android:layout_width="wrap_content" 
     android:orientation="vertical" android:paddingleft="10dp">
        <textview android:id="@+id/tve" android:layout_height="wrap_content" 
         android:layout_width="wrap_content" android:text="" 
         android:textcolor="#000000" android:textsize="20dp">
        <textview android:id="@+id/tvp" android:layout_height="wrap_content" 
         android:layout_width="wrap_content" android:text="" 
         android:textcolor="#000000" android:textsize="20dp">
    </textview></textview></linearlayout>
</linearlayout>

可以看出,该设计具有自由结构,并且可以为 ListView 中的每个项考虑任何视图模板。

第二步,应将 ListView 作为普通视图添加到主活动的布局中,如下面的代码所示:

<!--?xml version="1.0" encoding="utf-8"?-->
<relativelayout android:layout_height="match_parent" 
 android:layout_width="match_parent" 
 android:paddingbottom="@dimen/activity_vertical_margin" 
 android:paddingleft="@dimen/activity_horizontal_margin" 
 android:paddingright="@dimen/activity_horizontal_margin" 
 android:paddingtop="@dimen/activity_vertical_margin" 
 tools:context="com.example.listviewapplication.MainActivity" 
 xmlns:android="http://schemas.android.com/apk/res/android" 
 xmlns:tools="http://schemas.android.com/tools">

    <listview android:id="@+id/lv" android:layout_height="match_parent" 
     android:layout_width="match_parent">
        
    </listview>
</relativelayout>

请注意,ListView 必须像上面那样插入到活动的 الرئيسية XML 布局中。现在,ListView 已完全设计和创建,可以在应用程序中使用,这将在后续小节中进行介绍。

定义 ArrayAdapter

本小节描述了如何定义合适的 ArrayAdapter。为此,通过扩展 ArrayAdapter 类的普通类来定义 myAdapter 类,如下所示:

public class myAdapter extends ArrayAdapter<user> {
    public ArrayList<user> users;
    public myAdapter(Context context, int resource,ArrayList<user> users) {
        super(context, 0, users);
        this.users=users;
    }
}

应注意,ArrayAdapter 应具有构造方法,该构造方法会调用其父对象。可以看出,ArrayAdapter 直接使用包含用户数据的 ArrayList<user>

如前所述,ArrayAdapter 负责为 ListView 绑定项(来自 ArrayList<user> 的用户)。ListView 调用 ArrayAdaptergetView() 方法来更新特定的一个项(用户),方法如下:

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

每当需要更新项时,ListView 都会调用 ArrayAdapter 类中的 getView() 方法。此方法在两种情况下被调用:初始化(或失效)和回收情况。下一张图片用于描述 Item 0 的初始化情况:

如上图所示,ListView 调用 getView() 方法并传入指定的输入参数。第一个和第二个参数分别设置为 0null,第三个参数是一个空的 viewgroup,其模板与项的 XML 布局(应用程序中的 item.xml)相同。

在初始化情况下,ArrayAdapter 应根据视图模板(父参数)和指定项的位置信息(users[position] 包含数据)正确地填充视图。为此,getView() 方法会像这里所示那样被重写:

public View getView(int position,View convertView,ViewGroup parent){
    User user=users.get(position);
    View rowView=convertView;
    if(convertView==null){
        rowView= LayoutInflater.from(getContext()).inflate(R.layout.item,parent,false);
    }
    ImageView iv = (ImageView) rowView.findViewById(R.id.iv);
    TextView tvu = (TextView) rowView.findViewById(R.id.tvu);
    TextView tvp = (TextView) rowView.findViewById(R.id.tvp);
    TextView tve = (TextView) rowView.findViewById(R.id.tve);
    iv.setImageResource(user.imageSource);
    tvu.setText(user.username);
    tvp.setText("Phone = "+user.phoneNumber);
    tve.setText("Email = "+user.emailAddress);
    return rowView;
}

可以轻松地看出,ArrayAdapter 利用位置索引及其相关的用户属性来更新 rowView 的视图。rowView 通过填充父视图并考虑项的 XML 布局来获得。然后,适配器通过设置这些文本来使用项的数据(用户名、电话号码、电子邮件地址和图像资源)在 rowView 的子视图中。

将 ArrayAdapter 附加到 ListView

本小节解释了如何将实现的 ArrayAdapter (myAdapter) 附加到创建的 ListView。为此,代码如下所示,并在主活动中实现:

public class MainActivity extends AppCompatActivity {
    public ListView listView;
    public myAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        User user=new User("person 1","9999","A@B.com",R.drawable.pic1);
        ArrayList<user> users=new ArrayList<user>();
        users.add(user);
        user=new User("person 2","9998","C@B.com",R.drawable.pic2);
        users.add(user);
        adapter=new myAdapter(getApplicationContext(),0,users);
        listView=(ListView)findViewById(R.id.lv);
        listView.setAdapter(adapter);
    }
}

通过在 ListView 作用域中调用 setAdapter() 方法来附加 ArrayAdapter,如上所示。此方法获取相应的 ArrayAdapter,以便在需要时自动调用其 getView() 方法。

请注意,在评估其 setAdapter() 方法后,ListView 会要求 ArrayAdapter 最初显示项。因此,必须在 ArrayAdapter 中设置项的 ListArrayList<user> </user>users),以通知 ListView 现有项。user 类也考虑如下:

public class User {
    public String username;
    public String phoneNumber;
    public String emailAddress;
    public int imageSource;
    public User(String username,String phoneNumber,String emailAddress,int imageSource){
        this.username=username;
        this.phoneNumber=phoneNumber;
        this.emailAddress=emailAddress;
        this.imageSource=imageSource;
    }
}

结果如下面的图片所示:

上图正确显示了存储在 ArrayAdapter 中的项。

ListView 回收

必须强调的是,在初始(失效)情况下只填充了少数几个项,而其他项将通过回收技巧显示。Android 框架利用此技巧来防止在项数量非常大的情况下出现内存不足。为了示意性地描述此技巧,将在下面提供以下图片:

上图显示 ListView 显示了 7 个项(请注意,这个特定的数字仅用作示例值),而 ArrayAdapter 中有 10 个项。现在,ListView 变得可滚动,以便能够显示所有项。假设我们在上图中向下滚动屏幕以查看 Item 8,该项在初始情况下并未填充。在这种情况下,Item 0 被完全丢弃,并且需要显示 Item 8,如下面的图片所示:

在这种情况下,ListView 回收 Item 0,并将其放置在视图的底部。此技巧称为 ListView 回收,并在下图示意性地展示:

ListView 回收是另一个要求 ArrayAdapter 更新回收项的情况。事实上,ListView 调用 getView() 方法来更新视图,如下图所示:

根据上图,ArrayAdapter 应更新 convertView,该更新通过两种方法实现:基于 findViewById 的方法和 View Holder-based 方法。这些方法将在后续小节中分别介绍。

ListView 回收:基于 findViewById 的方法

此方法仅根据新项的位置更新 convertView 数据。可以轻松地按如下方式实现此方法:

public View getView(int position,View convertView,ViewGroup parent){
    User user=users.get(position);
    View rowView=convertView;
    if(convertView==null){
        rowView= LayoutInflater.from(getContext()).inflate(R.layout.item,parent,false);
    }else{
        rowView=convertView;
        Toast.makeText(getContext(),"hi",Toast.LENGTH_LONG).show();
    }
    ImageView iv = (ImageView) rowView.findViewById(R.id.iv);
    TextView tvu = (TextView) rowView.findViewById(R.id.tvu);
    TextView tvp = (TextView) rowView.findViewById(R.id.tvp);
    TextView tve = (TextView) rowView.findViewById(R.id.tve);
    iv.setImageResource(user.imageSource);
    tvu.setText(user.username);
    tvp.setText("Phone = "+user.phoneNumber);
    tve.setText("Email = "+user.emailAddress);
    return rowView;
}

事实上,ArrayAdapter 可以通过检查其第二个输入参数 convertView 的空值来轻松区分回收和初始化情况。如果它不为 null,则发生回收,此时 ArrayAdapter 应更新回收视图(即 convertView)的值。因此,通过将 rowView 设置为 convertView 的实例,可以轻松更新值,如上面的代码所示。下图显示了对上述代码进行评估的结果:

上图揭示了所实现代码的正确功能。尽管基于 findViewById 的方法解决了回收问题,但此方法相对较慢。为了解决此问题,开发了 ViewHolder-based 方法,该方法将在下一小节中提出。

ListView 回收:基于 ViewHolder 的方法

前一种方法的缓慢速度严重影响了应用程序在回收情况下的性能。ViewHolder-based 方法利用已填充项的标签来获取其子视图,而不是使用 findViewById 方法。在 ViewHolder-based 方法中,通过在初始化情况下将每个已填充项的内部视图保存在 ViewHolder 对象中。然后,可以通过获取 ViewHolder 轻松获得需要更新其值的内部视图。为了更好地理解,下面给出了开发应用程序的 ViewHolder 类:

public  class  ViewHolder {
    public ImageView iv;
    public TextView tvu,tvp,tve;
    public ViewHolder(ImageView iv,TextView tvu,TextView tvp,TextView tve){
        this.iv=iv;
        this.tvu=tvu;
        this.tve=tve;
        this.tvp=tvp;
    }
}

事实上,ViewHolder 轻松地包含了项布局的内部视图,在开发的应用程序中,这些视图是一个 imageview 和三个 textviews。使用 ViewHoldergetView() 方法修改如下:

public View getView(int position,View convertView,ViewGroup parent){
    User user=users.get(position);
    View rowView=convertView;
    ImageView iv;
    TextView tvu,tvp,tve;
    if(convertView==null){
        rowView= LayoutInflater.from(getContext()).inflate(R.layout.item,parent,false);
        iv = (ImageView) rowView.findViewById(R.id.iv);
        tvu = (TextView) rowView.findViewById(R.id.tvu);
        tvp = (TextView) rowView.findViewById(R.id.tvp);
        tve = (TextView) rowView.findViewById(R.id.tve);
        ViewHolder viewHolder=new ViewHolder(iv,tvu,tvp,tve);
        rowView.setTag(viewHolder);
    }else{
        rowView=convertView;
        ViewHolder viewHolder=(ViewHolder)rowView.getTag();
        iv=viewHolder.iv;
        tvu=viewHolder.tvu;
        tve=viewHolder.tve;
        tvp=viewHolder.tvp;
    }

    iv.setImageResource(user.imageSource);
    tvu.setText(user.username);
    tvp.setText("Phone = "+user.phoneNumber);
    tve.setText("Email = "+user.emailAddress);
    return rowView;
}

上面为每个已填充的项生成了一个 ViewHolder 实例,其中包含其所有内部视图。ViewHolder 实例作为标签保存在 rowView 中,如上所示。然后,可以通过在回收情况下获取 convertView 的标签来提供内部视图。其余代码与前面的相同。

Using the Code

通过导入已实现的类,可以轻松使用该代码。

关注点

本文贡献了与 ListView 相关的主要概念和方面,并在 Android 中实现了一个用户列表视图的实例。

历史

  • 2020 年 10 月 25 日:初始版本
© . All rights reserved.