带有分隔线和上下文工具列的RecyclerView Android
今天,我们将开发带有上下文工具列的RecyclerView Android应用,让我们选择,删除或者标记RecyclerView的行。
此外,我们将在RecyclerView行之间放置分隔线。
带有分频器和上下文工具列演示的RecyclerView Android
我们将开发一个显示所选行数的应用程序。
我们的应用程序将允许我们删除,标记,刷新和选择所有行。
以下是本教程结束时我们将要实现的目标的预览。
RecyclerView Android示例
当在列表中长按一行时,ActionMode用于显示上下文工具列。
这使我们能够提供一组替代工具列图标。
我们将实施右上角的四种操作模式。
- 重新载入列表
- 标记行文字
- 删除行
- 选择所有行
要实现上下文工具列和上述操作,我们需要在MainActivity.java类中实现ActionMode.Callback接口。
ActionMode.Callback接口包含4个我们将要覆盖的方法。
onCreateActionMode:在此方法中将menu.xml文件放大。
onPrepareActionMode:每次显示上下文工具列时调用此方法。
onActionItemClicked:每次单击上下文工具列中的菜单项时,都会调用此方法。
onDestroyActionMode:当上下文工具列关闭时调用。
RecyclerView android依赖项
首先,在gradle构建文件中添加以下依赖项。
compile 'com.android.support:design:25.3.1' compile 'com.android.support:recyclerview-v7:25.3.1'
如下所示,在" Manifest.xml"文件中将活动的主题设置为" AppTheme.NoActionBar"。
<activity
android:name=".MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"
<category android:name="android.intent.category.LAUNCHER"
</intent-filter>
</activity>
RecyclerView Android示例项目结构
下面给出了" activity_main.xml"的代码。
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main"
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:visibility="gone"
android:layout_margin="@dimen/fab_margin"
app:backgroundTint="@color/colorPrimary"
app:srcCompat="@android:drawable/ic_input_add"
</android.support.design.widget.CoordinatorLayout>
下面给出了" content_main.xml"的代码:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/activity_main">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
</android.support.constraint.ConstraintLayout>
RecyclerView每行的布局代码在文件recyclerview_list_row.xml中给出。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:id="@+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_list_row"
android:clickable="true"
android:focusable="true"
android:orientation="vertical"
android:padding="@dimen/fab_margin">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:textSize="16sp"
android:textStyle="bold"
</RelativeLayout>
所述的RelativeLayout的背景是一个StateListDrawable(bg_list_row.xml)当选择/取消选择的行的是会改变其背景。
下面给出了" bg_list_row.xml"的代码:
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="https://schemas.android.com/apk/res/android"> <item android:drawable="@color/row_activated" android:state_activated="true" <item android:drawable="@android:color/transparent" </selector>
上下文工具列内将显示的菜单在" menu_action_mode.xml"文件中定义,如下所示。
<menu xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_delete"
android:icon="@drawable/ic_delete"
android:orderInCategory="300"
android:title="Delete"
app:showAsAction="always"
<item
android:id="@+id/action_color"
android:icon="@drawable/ic_color_mark"
android:orderInCategory="200"
android:title="Color"
app:showAsAction="always"
<item
android:id="@+id/action_refresh"
android:icon="@drawable/ic_refresh"
android:orderInCategory="100"
android:title="Refresh"
app:showAsAction="always"
<item
android:id="@+id/action_select_all"
android:icon="@drawable/ic_select_all"
android:orderInCategory="400"
android:title="ALL"
app:showAsAction="always"
</menu>
我们创建了一个自定义ItemDecoration,用于显示每行的分隔线。
下面给出了DividerItemDecoration.java的代码。
package com.theitroad.recyclerviewdividersandselectors;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("wrong orientation");
}
mOrientation = orientation;
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
上面的代码根据方向在每个RecyclerView行之后创建一个分隔线(类似于ListView)。
下面给出了保存每一行数据的Model.java代码。
package com.theitroad.recyclerviewdividersandselectors;
public class Model {
String text;
boolean colored;
public Model(String text, boolean colored) {
this.text = text;
this.colored = colored;
}
}
下面给出了RecyclerViewAdapter.java的代码:
package com.theitroad.recyclerviewdividersandselectors;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.SparseBooleanArray;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class RecyclerViewAdapter extends RecyclerView.Adapter {
private Context mContext;
private List modelList;
private ClickAdapterListener listener;
private SparseBooleanArray selectedItems;
private static int currentSelectedIndex = -1;
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener {
public TextView textView;
public RelativeLayout relativeLayout;
public MyViewHolder(View view) {
super(view);
textView = (TextView) view.findViewById(R.id.textView);
relativeLayout = (RelativeLayout) view.findViewById(R.id.relativeLayout);
view.setOnLongClickListener(this);
}
@Override
public boolean onLongClick(View view) {
listener.onRowLongClicked(getAdapterPosition());
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
return true;
}
}
public RecyclerViewAdapter(Context mContext, List modelList, ClickAdapterListener listener) {
this.mContext = mContext;
this.modelList = modelList;
this.listener = listener;
selectedItems = new SparseBooleanArray();
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recyclerview_list_row, parent, false);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
String text = modelList.get(position).text;
holder.textView.setText(text);
if (modelList.get(position).colored)
holder.textView.setTextColor(mContext.getResources().getColor(android.R.color.holo_red_dark));
holder.itemView.setActivated(selectedItems.get(position, false));
applyClickEvents(holder, position);
}
private void applyClickEvents(MyViewHolder holder, final int position) {
holder.relativeLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
listener.onRowClicked(position);
}
});
holder.relativeLayout.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
listener.onRowLongClicked(position);
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
return true;
}
});
}
@Override
public int getItemCount() {
return modelList.size();
}
public void toggleSelection(int pos) {
currentSelectedIndex = pos;
if (selectedItems.get(pos, false)) {
selectedItems.delete(pos);
} else {
selectedItems.put(pos, true);
}
notifyItemChanged(pos);
}
public void selectAll() {
for (int i = 0; i < getItemCount(); i++)
selectedItems.put(i, true);
notifyDataSetChanged();
}
public void clearSelections() {
selectedItems.clear();
notifyDataSetChanged();
}
public int getSelectedItemCount() {
return selectedItems.size();
}
public List getSelectedItems() {
List items =
new ArrayList(selectedItems.size());
for (int i = 0; i < selectedItems.size(); i++) {
items.add(selectedItems.keyAt(i));
}
return items;
}
public void removeData(int position) {
modelList.remove(position);
resetCurrentIndex();
}
public void updateData(int position) {
modelList.get(position).colored = true;
resetCurrentIndex();
}
private void resetCurrentIndex() {
currentSelectedIndex = -1;
}
public interface ClickAdapterListener {
void onRowClicked(int position);
void onRowLongClicked(int position);
}
}
以下代码段用于更改StateListDrawable的状态。
holder.itemView.setActivated(selectedItems.get(position, false));
将根据单击的菜单项从MainActivity.java中调用方法selectAll(),removeData()和updateData()。
下面给出了MainActivity.java的代码。
package com.theitroad.recyclerviewdividersandselectors;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
import static android.view.View.GONE;
public class MainActivity extends AppCompatActivity implements RecyclerViewAdapter.ClickAdapterListener {
RecyclerView recyclerView;
LinearLayoutManager layoutManager;
ArrayList dataModel;
RecyclerViewAdapter mAdapter;
private ActionModeCallback actionModeCallback;
private ActionMode actionMode;
FloatingActionButton fab;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
fab.setVisibility(GONE);
populateDataAndSetAdapter();
}
});
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
actionModeCallback = new ActionModeCallback();
populateDataAndSetAdapter();
}
@Override
public void onRowClicked(int position) {
enableActionMode(position);
}
@Override
public void onRowLongClicked(int position) {
enableActionMode(position);
}
private void enableActionMode(int position) {
if (actionMode == null) {
actionMode = startSupportActionMode(actionModeCallback);
}
toggleSelection(position);
}
private void toggleSelection(int position) {
mAdapter.toggleSelection(position);
int count = mAdapter.getSelectedItemCount();
if (count == 0) {
actionMode.finish();
actionMode = null;
} else {
actionMode.setTitle(String.valueOf(count));
actionMode.invalidate();
}
}
private void selectAll() {
mAdapter.selectAll();
int count = mAdapter.getSelectedItemCount();
if (count == 0) {
actionMode.finish();
} else {
actionMode.setTitle(String.valueOf(count));
actionMode.invalidate();
}
actionMode = null;
}
private class ActionModeCallback implements ActionMode.Callback {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.menu_action_mode, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Log.d("API123", "here");
switch (item.getItemId()) {
case R.id.action_delete:
//delete all the selected rows
deleteRows();
mode.finish();
return true;
case R.id.action_color:
updateColoredRows();
mode.finish();
return true;
case R.id.action_select_all:
selectAll();
return true;
case R.id.action_refresh:
populateDataAndSetAdapter();
mode.finish();
return true;
default:
return false;
}
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mAdapter.clearSelections();
actionMode = null;
}
}
private void deleteRows() {
List selectedItemPositions =
mAdapter.getSelectedItems();
for (int i = selectedItemPositions.size() - 1; i >= 0; i--) {
mAdapter.removeData(selectedItemPositions.get(i));
}
mAdapter.notifyDataSetChanged();
if (mAdapter.getItemCount() == 0)
fab.setVisibility(View.VISIBLE);
actionMode = null;
}
private void updateColoredRows() {
List selectedItemPositions =
mAdapter.getSelectedItems();
for (int i = selectedItemPositions.size() - 1; i >= 0; i--) {
mAdapter.updateData(selectedItemPositions.get(i));
}
mAdapter.notifyDataSetChanged();
actionMode = null;
}
private void populateDataAndSetAdapter() {
dataModel = new ArrayList();
dataModel.add(new Model("Item 1", false));
dataModel.add(new Model("Item 2", false));
dataModel.add(new Model("Item 3", false));
dataModel.add(new Model("Item 4", false));
dataModel.add(new Model("Item 5", false));
dataModel.add(new Model("Item 6", false));
dataModel.add(new Model("Item 7", false));
dataModel.add(new Model("Item 8", false));
dataModel.add(new Model("Item 9", false));
dataModel.add(new Model("Item 10", false));
dataModel.add(new Model("Item 11", false));
dataModel.add(new Model("Item 12", false));
mAdapter = new RecyclerViewAdapter(this, dataModel, this);
recyclerView.setAdapter(mAdapter);
}
}
以下代码用于在行之间添加分隔符。
recyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
每次单击RecyclerView行时,都会调用onRowClicked()和onRowLongClicked()。
enableActionMode()用于显示上下文工具列。
上下文工具列显示基于适配器类中的" getSelectedItemCount()"选择的行数。
如果所有行都被删除,我们将显示一个浮动操作按钮,该按钮使用户可以再次使用伪数据填充RecyclerView。
上下文工具列通常在Whatsapp和Inbox等应用程序中看到。

