Android 中的 ListView 实现





5.00/5 (2投票s)
本文详细描述了如何在 Android 中实现 ListView,并提供足够的信息。
引言
有时,需要在 Android 中显示一组可以动态操作的可滚动项。这些可滚动项收集在一个 List
(ArrayList
) 对象中,该对象包含所有希望正确显示的项。例如,在开发的应用程序中,用户就是可滚动项。主要目标是通过可滚动可视化在屏幕上显示 List 中的项(用户)。为此,需要两个对象,即 ListView
和 ArrayAdapter
。
本文开发了一个显示项目列表的应用程序。这些项目是一组用户,他们具有一些属性,即图像、用户名、电话号码和电子邮件地址。本文将正确显示这些属性。
ListView
是一个 Java public
类,它扩展自 AbsListView
,用于分组多个项并将它们显示在垂直可滚动的列表中。应注意,当需要时,列表会根据数据量自动滚动。必须理解,ListView
仅负责视觉上呈现这些项。
ArrayAdapter
是另一个 Java public
类,它扩展了 BaseAdapter
类,该类将 List 中的项进行适配,并为 ListView
提供它们。ArrayAdapter
在概念上是项 List
和 ListView
对象之间的桥梁。事实上,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
调用 ArrayAdapter
的 getView()
方法来更新特定的一个项(用户),方法如下:
@Override
public View getView(int position,View convertView,ViewGroup parent){
}
每当需要更新项时,ListView
都会调用 ArrayAdapter
类中的 getView()
方法。此方法在两种情况下被调用:初始化(或失效)和回收情况。下一张图片用于描述 Item 0 的初始化情况:
如上图所示,ListView
调用 getView()
方法并传入指定的输入参数。第一个和第二个参数分别设置为 0
和 null
,第三个参数是一个空的 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
中设置项的 List
(ArrayList<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
。使用 ViewHolder
,getView()
方法修改如下:
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 日:初始版本