Android:RecyclerView
RecyclerView
是Android5.0之后V7包中的新特性。与 ListView
相似,但是比 ListView
更灵活,支持Android 2.1版本以上。
正如它的名字:当一个item隐藏的时候,隐藏的item会被再回收重用,绑定新的数据。而不是被销毁为新的item的创建新的布局。
RecylerView分为6个主要组件
Adapter
:提供数据,类似ListView
的ItemAnimator
:item的添加、删除、修改、移动的动画效果ItemDecoration
:添加图画或修改Item布局LayoutManager
:指定Item如何布局(List、Grid…)ViewHolder
:每个Item View的基础类RecylerView
:将所有的组件绑定
二、RecyclerView展示
1.准备
Gradle添加 RecyclerView
的supprt-v7包依赖
compile 'com.android.support:recyclerview-v7:21.0.3'
本篇文章也使用 CardViews
,因此建议添加对应的依赖
compile 'com.android.support:cardview-v7:21.0.3'
2.BaseItem
BaseItem
包含一个 title
和一个 subtitle
属性。用来表示列表中每个 item
public class Item{
private String title;
private String subtitle;
Item(String title , String subtitle){
this.title = title;
this.subtitle = subtitle;
}
public String getTitle() {
return title;
}
public String getSubtitle() {
return subtitle;
}
}
3.Item Layout
使用 CardView
作为Item的展示Layout。 CardView
是具有装饰的 FrameLayout
。
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
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="match_parent"
app:contentPadding="8dp"
app:cardUseCompatPadding="true" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
style="@style/Base.TextAppearance.AppCompat.Headline" />
<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
style="@style/Base.TextAppearance.AppCompat.Subhead" />
</LinearLayout>
</android.support.v7.widget.CardView>
4.adapter
-
定义
ViewHolder
类,必须继承RecyclerView.ViewHolder
。同时需要存储绑定数据时需要使用到的View的对象。例子中我们需要两个TextView
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
@SuppressWarnings("unused")
private static final String TAG = Adapter.class.getSimpleName();
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView title;
TextView subtitle;
public ViewHolder(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.title);
subtitle = (TextView) itemView.findViewById(R.id.subtitle);
}
}
} -
使用
Collection
存储对象的集合,简单起见,例子中使用ArrayList
来存储列表数据。public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
@SuppressWarnings("unused")
private static final String TAG = Adapter.class.getSimpleName();
private static final int ITEM_COUNT = 50;
private List<Item> items;
public Adapter() {
super();
// 创建item的列表
items = new ArrayList<>();
for (int i = 0; i < ITEM_COUNT; ++i) {
items.add(new Item("Item " + i, "This is the item number " + i));
}
}
// 省略ViewHolder的定义
} -
实现
RecyclerView.Adapter
的方法。
-
onCreateViewHolder(ViewGroup parent, int viewType)
创建View,返回匹配的ViewHolder
-
onBindViewHolder(ViewHolder holder, int position)
根据Item的position,给ViewHolder绑定数据
-
getItemCount()
返回Adapter的item的数量
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
// Attributes and constructor omitted
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final Item item = items.get(position);
holder.title.setText(item.getTitle());
holder.subtitle.setText(item.getSubtitle());
}
@Override
public int getItemCount() {
return items.size();
}
// 省略ViewHolder的定义
}
5. Bind everything together
-
在Activity的布局文件中添加
RecyclerView
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout> -
使用
LinearLayoutManager
,DefaultItemAnimator
public class MainActivity extends ActionBarActivity {
@SuppressWarnings("unused")
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setAdapter(new Adapter());
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setLayoutManager(new LinearLayoutManager(this));
}
} -
编译、运行
三、相同RecyclerView显示不同类型的View
假设现在要显示两种类型的Item。如一个远程音乐的列表和一些离线的专辑。可以为他们指定行为,显示指定的信息。
-
在Item中添加
active
属性public class Item {
private String title;
private String subtitle;
private boolean active;
Item(String title, String subtitle, boolean active) {
this.title = title;
this.subtitle = subtitle;
this.active = active;
}
public String getTitle() {
return title;
}
public String getSubtitle() {
return subtitle;
}
public boolean isActive() {
return active;
}
} -
修改创建Item的方法,随机设定Item的
active
值,根据active
来改变subtitle状态public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
// 忽略参数
public Adapter() {
super();
// 创建Item
Random random = new Random();
items = new ArrayList<>();
for (int i = 0; i < ITEM_COUNT; ++i) {
items.add(new Item("Item " + i, "This is the item number " + i, random.nextBoolean()));
}
}
// 忽略onCreateViewHolder
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final Item item = items.get(position);
holder.title.setText(item.getTitle());
holder.subtitle.setText(item.getSubtitle() + ", which is " +
(item.isActive() ? "active" : "inactive"));
}
// …
} -
学会显示不同String是个好的开始,但是我们不仅仅要满足于此。在Adapter中你可能会注意到
onCreateViewHolder(ViewGroup parent, int viewType)
中的viewType我们没有使用。这里的viewType
可以用来实现这样的需求:修改ViewHolder的实例。我们必须告诉Adapter
来确定Item的类型。通过复写getItemViewType(int position)
方法实现。public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
// …
private static final int TYPE_INACTIVE = 0;
private static final int TYPE_ACTIVE = 1;
// …
@Override
public int getItemViewType(int position) {
final Item item = items.get(position);
return item.isActive() ? TYPE_ACTIVE : TYPE_INACTIVE;
}
// …
} -
现在根据你的需求有多种可能:为每种类型的View创建不同的
ViewHolder
或为相同的ViewHolder
inflate 不同的Layout。简单起见,这里使用相同的ViewHolder
inflate 不同的Layout。我们对inactive
的Item继续使用现在的Layout,对active
的Item创建一个新的Layout
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
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="match_parent"
app:contentPadding="8dp"
app:cardUseCompatPadding="true" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="@color/material_deep_teal_500"
style="@style/Base.TextAppearance.AppCompat.Headline" />
<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:textColor="@color/material_blue_grey_900"
style="@style/Base.TextAppearance.AppCompat.Subhead" />
</LinearLayout>
</android.support.v7.widget.CardView>
- 最后也是最重要:在
onCreateViewHolder
方法中根据viewType
值Inflate不同的Layout
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
// …
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final int layout = viewType == TYPE_INACTIVE ? R.layout.item : R.layout.item_active;
View v = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false);
return new ViewHolder(v);
}
// …
}
现在我们可以区分 active
和 inactive
Item了
四、Layout managers
1.LinearLayoutManager
这个也是上面我们用的Manager,复制了 ListView
的特性。 Layout managers
占用三个参数
Context
不能为空int orientation
vertical(默认值)、horizontalboolean reverseLayout
是否反转Layout
下面是 反转
水平方向
的LinearLayoutManager
设置反转Layout时显示最右边无需滑动
2.GridLayoutManager
类似 GridView
。占用四个参数
Context
非空int spanCount
非空int orientation
vertical(默认值)、horizontalboolean reverseLayout
是否反转Layout
下面是 spanCount
为3, vertical
方向, 非反转
的GridLayoutManager
注意
:GridLayoutManager的 reverseLayout
属性只反转设定的反向。如设置orientation为 vertical
时,只垂直反转,水平方向不变。
3.StaggeredGridLayoutManager
StaggeredGridLayoutManager是 GridLayoutManager
的加强版。截止到 support-v7 21.0.0.3
版本的包中,StaggeredGridLayoutManager仍然还有很多 bug 和 困扰的问题 。和其他几种LayoutManager相比, StaggeredGridLayoutManager
看上去像是匆忙完成的。同时需要注意的是它不需要 Context
,但是 orientation
不能省略。同 GridLayoutManager
一样,需要指定 span
的数量。构造器中不提供 reverse
参数,可以用代码来实现。
下面是固定Span的Grid,我们可以让Item占用整行或整列。让我们看看如何实现。用之前例子中创建的 active/inactive
属性的item list,让 active
属性的item占满整行。在 adapter
绑定数据的时候,来实现。
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
// …
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final Item item = items.get(position);
holder.title.setText(item.getTitle());
holder.subtitle.setText(item.getSubtitle() + ", which is " + (item.isActive() ? "active" : "inactive"));
// Span the item if active
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams sglp = (StaggeredGridLayoutManager.LayoutParams) lp;
sglp.setFullSpan(item.isActive());
holder.itemView.setLayoutParams(sglp);
}
}
// …
}
在代码判断如果LayoutManager是StaggeredGridLayoutManager的实例,来修改Layout参数。效果如图
五、Item点击响应
与ListView 不同的是RecyclerView没有提供相关的点击响应事件的API,但是依然很容易来实现。现在我们需要监听每个Item的 click
和 long-click
事件.每个Item是由 ViewHolder
来显示的,每个 ViewHolder
是由根 View
来初始化的。OK,用 View
来作为 click
和 long-click
事件的回调。最后只需要把 ViewHolder
和它的position对应起来即可, RecyclerView.ViewHolder
已经提供对应的方法
getPosition()
( getPosition() 方法废弃, 根据你的情况用 getLayoutPosition()
或 getAdapterPosition()
来替代 )来获取当前绑定的Item的postion
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
// …
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener,
View.OnLongClickListener {
@SuppressWarnings("unused")
private static final String TAG = ViewHolder.class.getSimpleName();
TextView title;
TextView subtitle;
public ViewHolder(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.title);
subtitle = (TextView) itemView.findViewById(R.id.subtitle);
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
@Override
public void onClick(View v) {
Log.d(TAG, "Item clicked at position " + getPosition());
}
@Override
public boolean onLongClick(View v) {
Log.d(TAG, "Item long-clicked at position " + getPosition());
return true;
}
}
}
六、选择处理
long-click
事件一个常用的模式是触发选择模式。再一次, RecyclerView
没有帮助我们实现,但是实现起来也很简单。
分为三步
- 维护一个选择状态
- 更新选中的Item的View
- 开启选择模式
为了说明,我们添加一个选择Item的方式,然后移除选中的Item。
1.选择状态
我们需要修改 Adapter
来维护一个选中的item list。 Adapter
需要提供一下列表参数
- 选中的元素列表
- 改变给定元素的选择状态
我们加入额外的方法几个方法:
- 检查指定的元素是否被选中
- 清空选中
- 提供选中元素的数量
这里并不是随便选择这些方法,在下个部分我们需要它们。
我们注意到这五个方法都不是特定的Item,我们可以用普通方式写这些方法并重用 Adapter
的行为。
public abstract class SelectableAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
@SuppressWarnings("unused")
private static final String TAG = SelectableAdapter.class.getSimpleName();
private SparseBooleanArray selectedItems;
public SelectableAdapter() {
selectedItems = new SparseBooleanArray();
}
/**
* 指定对应位置的Item是否被选中
* @param Item的position
* @return 如果是选中返回true,否则返回false
*/
public boolean isSelected(int position) {
return getSelectedItems().contains(position);
}
/**
* 给定postion的Item的选中状态触发器
* @param position item 的 postion
*/
public void toggleSelection(int position) {
if (selectedItems.get(position, false)) {
selectedItems.delete(position);
} else {
selectedItems.put(position, true);
}
notifyItemChanged(position);
}
/**
* 清空所有Item的选中状态
*/
public void clearSelection() {
List<Integer> selection = getSelectedItems();
selectedItems.clear();
for (Integer i : selection) {
notifyItemChanged(i);
}
}
/**
* 选中Item的数量
* @return 选中Item的数量
*/
public int getSelectedItemCount() {
return selectedItems.size();
}
/**
* 被选中的Item的Id list
* @return 被选中的Item的Id list
*/
public List<Integer> getSelectedItems() {
List<Integer> items = new ArrayList<>(selectedItems.size());
for (int i = 0; i < selectedItems.size(); ++i) {
items.add(selectedItems.keyAt(i));
}
return items;
}
}
最后 Adapter
继承我们创建 SelectableAdapter
public class Adapter extends SelectableAdapter<Adapter.ViewHolder> {
// …
}
2.更新Item的View
通知用户选中了一个Item,我们经常看到选中的View被覆盖某个颜色。在 item.xml
和 item_active.xml
中,我们添加一个 invisible
并带有颜色的View。因为这个View应该填充整个 CardView
,我们应该改变Layout的一些属性(将CardView的padding属性移动到LinearLayout中),View的颜色应该为透明的。
我们要用android Framework的 selectableItemBackground
为 CardView
的foreground设定属性值,来添加一个好看的触摸反馈。在Android 5.0以及以上,显示涟漪的效果,Android 5.0以下版本显示灰色。
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
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="match_parent"
android:foreground="?android:attr/selectableItemBackground"
app:cardUseCompatPadding="true" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="8dp" >
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
style="@style/Base.TextAppearance.AppCompat.Headline" />
<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
style="@style/Base.TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<View
android:id="@+id/selected_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/selected_overlay"
android:visibility="invisible" />
</android.support.v7.widget.CardView>
下一步决定什么时候显示这个效果,很明显需要 Adapters
的 onBindViewHolder()
方法中。在 ViewHolder
中添加overlay view的属性
public class Adapter extends SelectableAdapter<Adapter.ViewHolder> {
// …
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final Item item = items.get(position);
holder.title.setText(item.getTitle());
holder.subtitle.setText(item.getSubtitle() + ", which is " + (item.isActive() ? "active" : "inactive"));
// Span the item if active
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams sglp = (StaggeredGridLayoutManager.LayoutParams) lp;
sglp.setFullSpan(item.isActive());
holder.itemView.setLayoutParams(sglp);
}
// 高亮选中的Item
holder.selectedOverlay.setVisibility(isSelected(position) ? View.VISIBLE : View.INVISIBLE);
}
// …
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener,
View.OnLongClickListener {
@SuppressWarnings("unused")
private static final String TAG = ViewHolder.class.getSimpleName();
TextView title;
TextView subtitle;
View selectedOverlay;
public ViewHolder(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.title);
subtitle = (TextView) itemView.findViewById(R.id.subtitle);
selectedOverlay = itemView.findViewById(R.id.selected_overlay);
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
// …
}
}
3.开启选中模式
最后一步可能有点复杂,但也不是很难。我们需要将 click
和 long-click
返回给 Activity
。用 ViewHolder
暴露一个 Listener
,通过 Adapter
传递它,实现需求。
public class Adapter extends SelectableAdapter<Adapter.ViewHolder> {
// …
private ViewHolder.ClickListener clickListener;
public Adapter(ViewHolder.ClickListener clickListener) {
super();
this.clickListener = clickListener;
// 创建Item
Random random = new Random();
items = new ArrayList<>();
for (int i = 0; i < ITEM_COUNT; ++i) {
items.add(new Item("Item " + i, "This is the item number " + i, random.nextBoolean()));
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final int layout = viewType == TYPE_INACTIVE ? R.layout.item : R.layout.item_active;
View v = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false);
return new ViewHolder(v, clickListener);
}
// …
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener,
View.OnLongClickListener {
@SuppressWarnings("unused")
private static final String TAG = ViewHolder.class.getSimpleName();
TextView title;
TextView subtitle;
View selectedOverlay;
private ClickListener listener;
public ViewHolder(View itemView, ClickListener listener) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.title);
subtitle = (TextView) itemView.findViewById(R.id.subtitle);
selectedOverlay = itemView.findViewById(R.id.selected_overlay);
this.listener = listener;
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
@Override
public void onClick(View v) {
if (listener != null) {
listener.onItemClicked(getPosition());
}
}
@Override
public boolean onLongClick(View v) {
if (listener != null) {
return listener.onItemLongClicked(getPosition());
}
return false;
}
public interface ClickListener {
public void onItemClicked(int position);
public boolean onItemLongClicked(int position);
}
}
}
我们用 ActionMode
来区分选择模式和正常模式,当选择模式被激活时显示不同的 ActionBar
。实现 ActionMode.Callback
来激活选择模式。简单起见, Activity
使用一个内部类来实现其接口,同时实现我们我们新创建的Click接口 Adapter.ViewHolder.ClickListener
。我们需要 Callback
来访问 Adapter
,所以把 Adapter
作为 Activity
的一个属性。
我们的Activity看上去变得复杂了。
public class MainActivity extends ActionBarActivity implements Adapter.ViewHolder.ClickListener {
@SuppressWarnings("unused")
private static final String TAG = MainActivity.class.getSimpleName();
private Adapter adapter;
private ActionModeCallback actionModeCallback = new ActionModeCallback();
private ActionMode actionMode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
adapter = new Adapter(this);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setAdapter(adapter);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
}
@Override
public void onItemClicked(int position) {
if (actionMode != null) {
toggleSelection(position);
}
}
@Override
public boolean onItemLongClicked(int position) {
if (actionMode == null) {
actionMode = startSupportActionMode(actionModeCallback);
}
toggleSelection(position);
return true;
}
/**
* 触发Item的选中状态
*
* 如果选中的Item数量为0, 则关闭选中状态模式
* 注意:actionMode不能为空
*
* @param position 触发选中状态的Item位置
*/
private void toggleSelection(int position) {
adapter.toggleSelection(position);
int count = adapter.getSelectedItemCount();
if (count == 0) {
actionMode.finish();
} else {
actionMode.setTitle(String.valueOf(count));
actionMode.invalidate();
}
}
private class ActionModeCallback implements ActionMode.Callback {
@SuppressWarnings("unused")
private final String TAG = ActionModeCallback.class.getSimpleName();
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate (R.menu.selected_menu, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_remove:
// TODO: 具体的移除Item操作
Log.d(TAG, "menu_remove");
mode.finish();
return true;
default:
return false;
}
}
@Override
public void onDestroyActionMode(ActionMode mode) {
adapter.clearSelection();
actionMode = null;
}
}
}
下图显示 Item5
被选中时的截图
七、改变数据
想要更新View,, Adapter
必须来发出数据发生了变化的通知。在 ListView
上,adapter只有一个可以使用的方法 notifyDataSetChanged()
。然而这个方法还不很理想:需要刷新所有的View,因为我们不能够确切知道什么发生了变化。而 RecyclerView.Adapter
提供多种方法:
- notifyItemChanged(int position)
- notifyItemInserted(int position)
- notifyItemRemoved(int position)
- notifyItemMoved(int fromPosition, int toPosition)
- notifyItemRangeChanged(int positionStart, int itemCount)
- notifyItemRangeInserted(int positionStart, int itemCount)
- notifyItemRangeRemoved(int positionStart, int itemCount)
- notifyDataSetChanged()
我们可以知道Item或Items的变化、添加、移除、移动。
我们在 Adapter
中向外部类提供移除Item、Item List的两个 public
方法。如果直接移除一个Item,直接从列表移除即可。但是移除item list 并没有简单。举个例子,如果现在需要移除 [5 , 8 , 9]
三个位置的Item,当移除5位置的Item后,我们Item的list长度就会变短,因此list数据中原来的8变成了7。结果我们移除 [5 , 8 , 9]
三个位置的数据实际对应的是[5 , 7 , 7]。再考虑到一种情况如果是移除 [8 , 9 , 5]
三个位置的Item呢?
因此我们需要将提供需要移除的Item List进行逆序排序。这样我们就能简单调用 notifyItemRangeRemoved()
方法来移除item list。
public class Adapter extends SelectableAdapter<Adapter.ViewHolder> {
// …
public void removeItem(int position) {
items.remove(position);
notifyItemRemoved(position);
}
public void removeItems(List<Integer> positions) {
// list逆序排序
Collections.sort(positions, new Comparator<Integer>() {
@Override
public int compare(Integer lhs, Integer rhs) {
return rhs - lhs;
}
});
// 拆分排列后的list
while (!positions.isEmpty()) {
if (positions.size() == 1) {
removeItem(positions.get(0));
positions.remove(0);
} else {
int count = 1;
while (positions.size() > count && positions.get(count).equals(positions.get(count - 1) - 1)) {
++count;
}
if (count == 1) {
removeItem(positions.get(0));
} else {
removeRange(positions.get(count - 1), count);
}
for (int i = 0; i < count; ++i) {
positions.remove(0);
}
}
}
}
private void removeRange(int positionStart, int itemCount) {
for (int i = 0; i < itemCount; ++i) {
items.remove(positionStart);
}
notifyItemRangeRemoved(positionStart, itemCount);
}
// …
}
最后在 Activity
中调用这两个方法,在 menu
菜单中创建 Remove
的操作。我们只需要在 Remove
菜单中调用 removeItems()
方法就可以了。我们可以在 click
中调用 removeItem()
方法来测试移除单个Item。
public class MainActivity extends ActionBarActivity implements Adapter.ViewHolder.ClickListener {
// …
@Override
public void onItemClicked(int position) {
if (actionMode != null) {
toggleSelection(position);
} else {
adapter.removeItem(position);
}
}
// …
private class ActionModeCallback implements ActionMode.Callback {
// …
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_remove:
adapter.removeItems(adapter.getSelectedItems());
mode.finish();
return true;
default:
return false;
}
}
// …
}
}
这样我们就实现了移除Item功能了。与传统 notifyDataSetChanged()
的方法相比,我们还有额外的动画效果