带有分隔线和上下文工具列的RecyclerView Android

时间:2020-02-23 14:29:29  来源:igfitidea点击:

今天,我们将开发带有上下文工具列的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等应用程序中看到。