带有分隔线和上下文工具列的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等应用程序中看到。