列表复用
列表的一个重大职责是复用 View, 因为我们的可见区域是有限的,要不断的回收再利用。我画了一张列表在手机上展示的关系示意图:
列表展示重用示意图
如上图,手机屏幕是呈现给用户的窗口,这个窗口是一个固定宽高的区域(上图绿色区域),而一个列表是可以无限长度的(分页加载),我们不需要创建这么多的子 View ,这样极大的浪费内存。所以这里我们可以利用适配器来完成列表(ListView)的列表项(item)的复用。在屏幕滚动的同时,如上面箭头所示意那样我们可以将看过的 View 拿来继续复用,这样可以保证列表项是无限的,而我们创建的 View 是有限的几个即可。
ListView 和 GrideView
在 Android 5.0 之前 ListView 和 GrideView 是列表布局的重要控件,而在 5.0 之后推出了号称更快的 RecyclerView, 这是我们后面研究的重点。
适配器是一个连接数据和AdapterView的桥梁,通过它能有效地实现数据与 AdapterView 的分离设置,使 AdapterView 与数据的绑定更加简便,修改更加方便。将数据源的数据适配到 ListView 中的常用适配器有:ArrayAdapter、SimpleAdapter 和 SimpleCursorAdapter, 实际工作中,常用自定义适配器。即继承于BaseAdapter的自定义适配器类。
首先定义布局和实体类:
1
2
3
4
| <ListView
android:id="@+id/main_button_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
|
如果你要显示成网格,只需要修改这里为 <GridView>
即可,下面其他步骤全部一致。
1
2
3
4
5
6
7
8
9
10
| public class MainMenu {
public String menuName;
public Class<? extends BaseActivity> turnToClass;
public MainMenu(String menuName, Class<? extends BaseActivity> turnToClass) {
this.menuName = menuName;
this.turnToClass = turnToClass;
}
}
|
继承自 BaseAdapter 定义适配器内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| public class MainMenuAdapter extends BaseAdapter {
private List<MainMenu> mMenusData;
public MainMenuAdapter(List<MainMenu> menus){
mMenusData = menus;
}
@Override
public int getCount() {
if(mMenusData == null) return 0;
return mMenusData.size();
}
@Override
public MainMenu getItem(int position) {
return mMenusData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if(convertView == null){
viewHolder = new ViewHolder();
convertView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_main_button, null, true);
viewHolder.turnButton = convertView.findViewById(R.id.btn_item);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
MainMenu menu = getItem(position);
viewHolder.turnButton.setText(menu.menuName);
return convertView;
}
static class ViewHolder{
Button turnButton;
}
}
|
注意上面的 getView()
中的内容,是实现 item 重用的关键。
然后创建按钮的集合并绑定到适配器,也就是说将数据绑定到适配器并创建适配器实例。
1
2
3
4
5
6
7
8
9
| private List<MainMenu> mButtonsData = new ArrayList<>();
private void initButtonsData(){
mButtonsData.add(new MainMenu("Thread 创建线程", TestThreadCreateActivity.class));
mButtonsData.add(new MainMenu("Runnable 创建线程", TestRunnableCreateActivity.class));
mButtonsData.add(new MainMenu("runOnUiThread 切换到 UI 线程", TestRunOnUiThreadActivity.class));
mButtonsData.add(new MainMenu("View.post(Runnable) 切换到 UI 线程", TestViewPostActivity.class));
mButtonsData.add(new MainMenu("postDelayed 切换到 UI 线程", TestPostDelayedActivity.class));
mButtonsData.add(new MainMenu("AsyncTask 使用", TestAsyncTaskActivity.class));
}
|
1
2
| mAdapter = new MainMenuAdapter(mButtonsData);
mListView.setAdapter(mAdapter);
|
列表通过 setAdapter()
方法和适配器进行关联。接下来我们再给列表的每一个 item 绑定一个点击事件:
1
2
3
4
5
6
| mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
turnTo(mAdapter.getItem(position).turnToClass);
}
});
|
这里要注意的一点是,因为我们的 item 里面有 <Button>
所以这里的 ItemClick 事件不会回调,需要给 <Button>
添加如下属性来取消它自身的可点击性:
1
2
3
| android:focusable="false"
android:clickable="false"
android:focusableInTouchMode="false"
|
示例运行结果
RecyclerView
据 Google 的文档上说 RecyclerView 做了很多优化的地方,所以推荐使用 RecyclerView 来实现诸如 列表,网格,瀑布流,不规则等布局样式。而且 RecyclerView 有一个很大的特点就是可以实现局部刷新。
首先要添加支持库:
1
2
3
| dependencies {
implementation 'com.android.support:recyclerview-v7:28.0.0'
}
|
在布局中添加 RecyclerView:
1
2
3
4
5
6
7
| <?xml version="1.0" encoding="utf-8"?>
<!-- A RecyclerView with some commonly used attributes -->
<android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
|
创建 RecyclerView 适配器: RecyclerView 适配器需要去重写(扩展)RecyclerView.Adapter
类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private String[] mDataset;
// 注意这个必须继承自 RecyclerView.ViewHolder
public static class MyViewHolder extends RecyclerView.ViewHolder {
public TextView textView;
public MyViewHolder(TextView v) {
super(v);
textView = v;
}
}
// 构造函数,传入数据
public MyAdapter(String[] myDataset) {
mDataset = myDataset;
}
//创建布局内容并返回 ViewHolder
@Override
public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// 创建一个 item 内容
TextView v = (TextView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_text_view, parent, false);
...
MyViewHolder vh = new MyViewHolder(v);
//这里可以根据 viewType 来返回不同的 ViewHolder, 例如:
/*switch(viewType){
case VIEW_TYPE1:
return vh;
case VIEW_TYPE2:
return vh2;
}*/
return vh;
}
// 在这里更新视图内容,比如设置文本,图片等
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.textView.setText(mDataset[position]);
}
// 返回数据大小
@Override
public int getItemCount() {
return mDataset.length;
}
}
|
和 ListView 不同的是 RecyclerView 的 onCreateViewHolder()
方法中的 ViewHolder 对象必须是继承自 RecyclerView.ViewHolder
的实例,在这个方法中我们可以根据不同的视图要求,创建不同的(多个) ViewHolder 根据 viewType 来返回。
设置 RecyclerView 的布局方式,然后将适配器绑定到 RecyclerView 对象上:
1
2
3
4
5
6
7
8
9
10
11
12
| recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// 这个设置是如果你知道你的所有 item 布局是一致的则设置为 true 可以提高性能
recyclerView.setHasFixedSize(true);
// 这里使用线性布局方式显示
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
// 创建适配器并绑定到 RecyclerView
mAdapter = new MyAdapter(myDataset);
recyclerView.setAdapter(mAdapter);
|
当然我们也可以设置列表间隔,而且很灵活很方便,如果我们的数据发生变化,可以调用 notifyItemChanged()
方法来更新界面显示。
除了线性布局 LinearLayoutManager
, 还有 GridLayoutManager
(网格) , StaggeredGridLayoutManager
(瀑布流)。