You have seen Action Mode Menu in many apps like Whatsapp, Hike, Gmail, etc. while selecting multiple chats/emails on long press i.e. called Action Mode
It represents a contextual mode of the user interface. Action modes can be used to provide alternative interaction modes and replace parts of the normal UI until finished. Examples of good action modes include text selection and contextual actions.
For Using Action Mode we need to extend our class with ActionMode.Callback. Its a Callback interface for action modes. Supplied to startSupportActionMode(Callback)
(Callback)}, a Callback configures and handles events raised by a user’s interaction with an action mode.
An action mode’s lifecycle is as follows:
onCreateActionMode(ActionMode, Menu)
once on initial creation.onPrepareActionMode(ActionMode, Menu)
after creation and any time the ActionMode
is invalidated.onActionItemClicked(ActionMode, MenuItem)
any time a contextual action button is clicked.onDestroyActionMode(ActionMode)
when the action mode is closed.In this tutorial, we are going to learn how to implement Action Mode on ListView and RecyclerView item selection.
1. Create a new project in Android Studio by navigating to File ⇒ New ⇒ New Project and fill required details. By default my activity is MainActivity.java.
2. Open build.gradle and include this libraries show below:
1 2 3 4 5 6 |
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:recyclerview-v7:23.1.1' compile 'com.android.support:design:23.1.1' } |
3. Open strings.xml located under res=>values folder and add following strings.
1 2 3 4 5 6 7 8 9 10 11 12 |
<resources> <string name="app_name">Toolbar ActionMode Demo</string> <string name="hello_world">Hello world!</string> <string name="action_settings">Settings</string> <!-- Action Mode Menu Items --> <string name="delete">Delete</string> <string name="copy">Copy</string> <string name="forward">Forward</string> </resources> |
4. Create new xml file naming colors.xml under res=>values folder and add the following colors.
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#D32F2F</color> <color name="colorPrimaryDark">#C62828</color> <color name="colorAccent">#F44336</color> <color name="grey">#EEEEEE</color> </resources> |
5. Open styles.xml located under res=>values folder and add following styles.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- Customize your theme here. --> <item name="android:windowDisablePreview">true</item> <item name="windowActionBar">false</item> <item name="android:textColorPrimary">@android:color/white</item> <item name="android:textColorSecondary">@android:color/white</item> <item name="actionMenuTextColor">@android:color/white</item> <item name="android:windowNoTitle">true</item> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <!-- It should be true otherwise action mode will not overlay toolbar --> <item name="windowActionModeOverlay">true</item> <!-- For Custom Action Mode Background Color/Drawable --> <item name="actionModeBackground">@color/colorAccent</item> </style> </resources> |
Some important part about above styles.xml:
6. Create activity_main.xml and add the following code. In this i am using Tab Bar Layout for creating tabs.
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 |
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/coordinatorLayout" android:layout_width="fill_parent" android:layout_height="fill_parent"> <!-- AppBar Layout --> <android.support.design.widget.AppBarLayout android:id="@+id/appBarLayout" android:layout_width="fill_parent" android:layout_height="wrap_content" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="fill_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:layout_scrollFlags="scroll|enterAlways" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" /> <!-- Tab Layout for creating tabs --> <android.support.design.widget.TabLayout android:id="@+id/tabLayout" android:layout_width="fill_parent" android:layout_height="wrap_content" app:tabIndicatorColor="@android:color/white" /> </android.support.design.widget.AppBarLayout> <!-- Helps handing the Fragments for each Tab --> <android.support.v4.view.ViewPager android:id="@+id/viewPager" android:layout_width="fill_parent" android:layout_height="fill_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> </android.support.design.widget.CoordinatorLayout> |
7. Create list_view_fragment.xml for ListView fragment and add ListView to it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@color/grey" android:gravity="center" android:orientation="vertical"> <!-- List View --> <ListView android:id="@+id/list_view" android:layout_width="fill_parent" android:layout_height="fill_parent" android:scrollbars="none" /> </LinearLayout> |
8. Like above we also need to create recycler_view_fragment.xml for RecyclerView fragment and add the RecyclerView to it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@color/grey" android:gravity="center" android:orientation="vertical"> <!-- Recycler View --> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:scrollbars="none" /> </LinearLayout> |
9. Now we need to create custom row for both ListView and RecyclerView. So we can use same xml for both, so create new xml file naming item_row.xml.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="10dp"> <TextView android:text="Title" android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/black" android:textSize="16sp" /> <TextView android:text="Sub Title" android:id="@+id/sub_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/darker_gray" android:textSize="13sp" /> </LinearLayout> |
10. Now come to MainActivity.java and add the following code for adding tabs and fragments to it.
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 |
package com.toolbar_actionmode_demo; import android.os.Bundle; import android.support.design.widget.TabLayout; import android.support.v4.app.Fragment; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; public class MainActivity extends AppCompatActivity { private static ViewPagerAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager); setupViewPager(viewPager);//Set up View Pager TabLayout tabLayout = (TabLayout) findViewById(R.id.tabLayout); tabLayout.setupWithViewPager(viewPager);//setting tab over viewpager } //Setting View Pager private void setupViewPager(ViewPager viewPager) { adapter = new ViewPagerAdapter(getSupportFragmentManager()); adapter.addFrag(new ListView_Fragment(), "ListView"); adapter.addFrag(new RecyclerView_Fragment(), "RecyclerView"); viewPager.setAdapter(adapter); } //Return current fragment on basis of Position public Fragment getFragment(int pos) { return adapter.getItem(pos); } } |
11. Create new ViewPagerAdapter.java class for ViewPager tab fragments.
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 |
package com.toolbar_actionmode_demo; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import java.util.ArrayList; import java.util.List; /** * Created by SONU on 27/03/16. */ public class ViewPagerAdapter extends FragmentPagerAdapter { private final List<Fragment> mFragmentList = new ArrayList<>();//fragment array list private final List<String> mFragmentTitleList = new ArrayList<>();//title array list public ViewPagerAdapter(FragmentManager manager) { super(manager); } @Override public Fragment getItem(int position) { return mFragmentList.get(position); } @Override public int getCount() { return mFragmentList.size(); } //adding fragments and title method public void addFrag(Fragment fragment, String title) { mFragmentList.add(fragment); mFragmentTitleList.add(title); } @Override public CharSequence getPageTitle(int position) { return mFragmentTitleList.get(position); } } |
12. For Custom ListView and RecyclerView we need to create getter and setter class naming Item_Model.java.
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 |
package com.toolbar_actionmode_demo; import java.io.Serializable; /** * Created by SONU on 27/03/16. */ public class Item_Model implements Serializable { /* Model class for List and Recycler Items */ private String title, subTitle; public Item_Model(String title, String subTitle) { this.title = title; this.subTitle = subTitle; } public String getTitle() { return title; } public String getSubTitle() { return subTitle; } } |
13. Now create ListView_Fragment.java and add the following code. In this i am implementing ActionMode on ListView long click item and starting Action Mode.
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
package com.toolbar_actionmode_demo; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v7.app.AppCompatActivity; import android.support.v7.view.ActionMode; import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; import android.widget.Toast; import java.util.ArrayList; /** * Created by SONU on 27/03/16. */ public class ListView_Fragment extends Fragment { private static View view; private static ListView_Adapter adapter; private static ListView listView; //Action Mode for toolbar private ActionMode mActionMode; private static ArrayList<Item_Model> item_models; public ListView_Fragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { view = inflater.inflate(R.layout.list_view_fragment, container, false); populateListView(); implementListViewClickListeners(); return view; } //Populate ListView with dummy data private void populateListView() { listView = (ListView) view.findViewById(R.id.list_view); item_models = new ArrayList<>(); for (int i = 1; i <= 40; i++) item_models.add(new Item_Model("Title " + i, "Sub Title " + i)); adapter = new ListView_Adapter(getActivity(), item_models); listView.setAdapter(adapter); adapter.notifyDataSetChanged(); } //Implement item click and long click over list view private void implementListViewClickListeners() { listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<> parent, View view, int position, long id) { //If ActionMode not null select item if (mActionMode != null) onListItemSelect(position); } }); listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<> parent, View view, int position, long id) { //Select item on long click onListItemSelect(position); return true; } }); } //List item select method private void onListItemSelect(int position) { adapter.toggleSelection(position);//Toggle the selection boolean hasCheckedItems = adapter.getSelectedCount() > 0;//Check if any items are already selected or not if (hasCheckedItems && mActionMode == null) // there are some selected items, start the actionMode mActionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(new Toolbar_ActionMode_Callback(getActivity(), null, adapter, item_models, true)); else if (!hasCheckedItems && mActionMode != null) // there no selected items, finish the actionMode mActionMode.finish(); if (mActionMode != null) //set action mode title on item selection mActionMode.setTitle(String.valueOf(adapter .getSelectedCount()) + " selected"); } //Set action mode null after use public void setNullToActionMode() { if (mActionMode != null) mActionMode = null; } //Delete selected rows public void deleteRows() { SparseBooleanArray selected = adapter .getSelectedIds();//Get selected ids //Loop all selected ids for (int i = (selected.size() - 1); i >= 0; i--) { if (selected.valueAt(i)) { //If current id is selected remove the item via key item_models.remove(selected.keyAt(i)); adapter.notifyDataSetChanged();//notify adapter } } Toast.makeText(getActivity(), selected.size() + " item deleted.", Toast.LENGTH_SHORT).show();//Show Toast mActionMode.finish();//Finish action mode after use } } |
14. Now for ListView adapter create new adapter class naming ListView_Adapter.java and add the following code. In this last some methods do the main thing for item selection.
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
package com.toolbar_actionmode_demo; import android.content.Context; import android.graphics.Color; import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import java.util.ArrayList; /** * Created by SONU on 29/03/16. */ public class ListView_Adapter extends BaseAdapter { private Context context; private ArrayList<Item_Model> item_modelArrayList; private SparseBooleanArray mSelectedItemsIds; public ListView_Adapter(Context context, ArrayList<Item_Model> item_modelArrayList) { this.context = context; this.item_modelArrayList = item_modelArrayList; mSelectedItemsIds = new SparseBooleanArray(); } @Override public int getCount() { return item_modelArrayList.size(); } @Override public Item_Model getItem(int position) { return item_modelArrayList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); ViewHolder holder; if (convertView == null) { convertView = inflater.inflate(R.layout.item_row, parent, false); holder = new ViewHolder(); holder.title = (TextView) convertView.findViewById(R.id.title); holder.sub_title = (TextView) convertView.findViewById(R.id.sub_title); convertView.setTag(holder); } else holder = (ViewHolder) convertView.getTag(); holder.title.setText(item_modelArrayList.get(position).getTitle()); holder.sub_title.setText(item_modelArrayList.get(position).getSubTitle()); /** Change background color of the selected items in list view **/ convertView .setBackgroundColor(mSelectedItemsIds.get(position) ? 0x9934B5E4 : Color.TRANSPARENT); return convertView; } private class ViewHolder { TextView title, sub_title; } /*** * Methods required for do selections, remove selections, etc. */ //Toggle selection methods public void toggleSelection(int position) { selectView(position, !mSelectedItemsIds.get(position)); } //Remove selected selections public void removeSelection() { mSelectedItemsIds = new SparseBooleanArray(); notifyDataSetChanged(); } //Put or delete selected position into SparseBooleanArray public void selectView(int position, boolean value) { if (value) mSelectedItemsIds.put(position, value); else mSelectedItemsIds.delete(position); notifyDataSetChanged(); } //Get total selected count public int getSelectedCount() { return mSelectedItemsIds.size(); } //Return all selected ids public SparseBooleanArray getSelectedIds() { return mSelectedItemsIds; } } |
15. Now this is main class in which Action Mode things happen. So create new class name Toolbar_ActionMode_
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
package com.toolbar_actionmode_demo; import android.content.Context; import android.os.Build; import android.support.v4.app.Fragment; import android.support.v4.view.MenuItemCompat; import android.support.v7.view.ActionMode; import android.util.Log; import android.util.SparseBooleanArray; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; import java.util.ArrayList; /** * Created by SONU on 22/03/16. */ public class Toolbar_ActionMode_Callback implements ActionMode.Callback { private Context context; private RecyclerView_Adapter recyclerView_adapter; private ListView_Adapter listView_adapter; private ArrayList<Item_Model> message_models; private boolean isListViewFragment; public Toolbar_ActionMode_Callback(Context context, RecyclerView_Adapter recyclerView_adapter, ListView_Adapter listView_adapter, ArrayList<Item_Model> message_models, boolean isListViewFragment) { this.context = context; this.recyclerView_adapter = recyclerView_adapter; this.listView_adapter = listView_adapter; this.message_models = message_models; this.isListViewFragment = isListViewFragment; } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { mode.getMenuInflater().inflate(R.menu.menu_main, menu);//Inflate the menu over action mode return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { //Sometimes the meu will not be visible so for that we need to set their visibility manually in this method //So here show action menu according to SDK Levels if (Build.VERSION.SDK_INT < 11) { MenuItemCompat.setShowAsAction(menu.findItem(R.id.action_delete), MenuItemCompat.SHOW_AS_ACTION_NEVER); MenuItemCompat.setShowAsAction(menu.findItem(R.id.action_copy), MenuItemCompat.SHOW_AS_ACTION_NEVER); MenuItemCompat.setShowAsAction(menu.findItem(R.id.action_forward), MenuItemCompat.SHOW_AS_ACTION_NEVER); } else { menu.findItem(R.id.action_delete).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); menu.findItem(R.id.action_copy).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); menu.findItem(R.id.action_forward).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); } return true; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.action_delete: //Check if current action mode is from ListView Fragment or RecyclerView Fragment if (isListViewFragment) { Fragment listFragment = new MainActivity().getFragment(0);//Get list view Fragment if (listFragment != null) //If list fragment is not null ((ListView_Fragment) listFragment).deleteRows();//delete selected rows } else { //If current fragment is recycler view fragment Fragment recyclerFragment = new MainActivity().getFragment(1);//Get recycler view fragment if (recyclerFragment != null) //If recycler fragment not null ((RecyclerView_Fragment) recyclerFragment).deleteRows();//delete selected rows } break; case R.id.action_copy: //Get selected ids on basis of current fragment action mode SparseBooleanArray selected; if (isListViewFragment) selected = listView_adapter .getSelectedIds(); else selected = recyclerView_adapter .getSelectedIds(); int selectedMessageSize = selected.size(); //Loop to all selected items for (int i = (selectedMessageSize - 1); i >= 0; i--) { if (selected.valueAt(i)) { //get selected data in Model Item_Model model = message_models.get(selected.keyAt(i)); String title = model.getTitle(); String subTitle = model.getSubTitle(); //Print the data to show if its working properly or not Log.e("Selected Items", "Title - " + title + "n" + "Sub Title - " + subTitle); } } Toast.makeText(context, "You selected Copy menu.", Toast.LENGTH_SHORT).show();//Show toast mode.finish();//Finish action mode break; case R.id.action_forward: Toast.makeText(context, "You selected Forward menu.", Toast.LENGTH_SHORT).show();//Show toast mode.finish();//Finish action mode break; } return false; } @Override public void onDestroyActionMode(ActionMode mode) { //When action mode destroyed remove selected selections and set action mode to null //First check current fragment action mode if (isListViewFragment) { listView_adapter.removeSelection(); // remove selection Fragment listFragment = new MainActivity().getFragment(0);//Get list fragment if (listFragment != null) ((ListView_Fragment) listFragment).setNullToActionMode();//Set action mode null } else { recyclerView_adapter.removeSelection(); // remove selection Fragment recyclerFragment = new MainActivity().getFragment(1);//Get recycler fragment if (recyclerFragment != null) ((RecyclerView_Fragment) recyclerFragment).setNullToActionMode();//Set action mode null } } } |
16. For action mode menu create menu inside menu folder and add the following menus to it.
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 |
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> <!-- Toolbar ActionMode Menu Items --> <item android:id="@+id/action_delete" android:icon="@mipmap/ic_action_delete" android:orderInCategory="100" android:title="@string/delete" app:showAsAction="always" /> <item android:id="@+id/action_copy" android:icon="@mipmap/ic_action_copy" android:orderInCategory="100" android:title="@string/copy" app:showAsAction="always" /> <item android:id="@+id/action_forward" android:icon="@mipmap/ic_action_forward" android:orderInCategory="100" android:title="@string/forward" app:showAsAction="always" /> </menu> |
17. Now for RecyclerView create new java class naming RecyclerView_Fragment.java and add the following code. It is mostly same as ListView_Fragment.java.
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
package com.toolbar_actionmode_demo; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v7.app.AppCompatActivity; import android.support.v7.view.ActionMode; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; import java.util.ArrayList; /** * Created by SONU on 27/03/16. */ public class RecyclerView_Fragment extends Fragment { private static View view; private static RecyclerView recyclerView; private static ArrayList<Item_Model> item_models; private static RecyclerView_Adapter adapter; private ActionMode mActionMode; public RecyclerView_Fragment() { } @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { view = inflater.inflate(R.layout.recycler_view_fragment, container, false); populateRecyclerView(); implementRecyclerViewClickListeners(); return view; } //Populate ListView with dummy data private void populateRecyclerView() { recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); item_models = new ArrayList<>(); for (int i = 1; i <= 40; i++) item_models.add(new Item_Model("Title " + i, "Sub Title " + i)); adapter = new RecyclerView_Adapter(getActivity(), item_models); recyclerView.setAdapter(adapter); adapter.notifyDataSetChanged(); } //Implement item click and long click over recycler view private void implementRecyclerViewClickListeners() { recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), recyclerView, new RecyclerClick_Listener() { @Override public void onClick(View view, int position) { //If ActionMode not null select item if (mActionMode != null) onListItemSelect(position); } @Override public void onLongClick(View view, int position) { //Select item on long click onListItemSelect(position); } })); } //List item select method private void onListItemSelect(int position) { adapter.toggleSelection(position);//Toggle the selection boolean hasCheckedItems = adapter.getSelectedCount() > 0;//Check if any items are already selected or not if (hasCheckedItems && mActionMode == null) // there are some selected items, start the actionMode mActionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(new Toolbar_ActionMode_Callback(getActivity(),adapter, null, item_models, false)); else if (!hasCheckedItems && mActionMode != null) // there no selected items, finish the actionMode mActionMode.finish(); if (mActionMode != null) //set action mode title on item selection mActionMode.setTitle(String.valueOf(adapter .getSelectedCount()) + " selected"); } //Set action mode null after use public void setNullToActionMode() { if (mActionMode != null) mActionMode = null; } //Delete selected rows public void deleteRows() { SparseBooleanArray selected = adapter .getSelectedIds();//Get selected ids //Loop all selected ids for (int i = (selected.size() - 1); i >= 0; i--) { if (selected.valueAt(i)) { //If current id is selected remove the item via key item_models.remove(selected.keyAt(i)); adapter.notifyDataSetChanged();//notify adapter } } Toast.makeText(getActivity(), selected.size() + " item deleted.", Toast.LENGTH_SHORT).show();//Show Toast mActionMode.finish();//Finish action mode after use } } |
18. Create ViewHolder for RecyclerView adapter naming DemoViewHolder.java and add the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package com.toolbar_actionmode_demo; import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.TextView; /** * Created by SONU on 27/03/16. */ public class DemoViewHolder extends RecyclerView.ViewHolder { public TextView title, sub_title; public DemoViewHolder(View view) { super(view); this.title = (TextView) view.findViewById(R.id.title); this.sub_title = (TextView) view.findViewById(R.id.sub_title); } } |
19. For RecyclerView adapter create new java class naming RecyclerView_Adapter.java and add the code.
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
package com.toolbar_actionmode_demo; import android.content.Context; import android.graphics.Color; import android.support.v7.widget.RecyclerView; import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.ViewGroup; import java.util.ArrayList; /** * Created by SONU on 27/03/16. */ public class RecyclerView_Adapter extends RecyclerView.Adapter<DemoViewHolder> { private ArrayList<Item_Model> arrayList; private Context context; private SparseBooleanArray mSelectedItemsIds; public RecyclerView_Adapter(Context context, ArrayList<Item_Model> arrayList) { this.context = context; this.arrayList = arrayList; mSelectedItemsIds = new SparseBooleanArray(); } @Override public int getItemCount() { return (null != arrayList ? arrayList.size() : 0); } @Override public void onBindViewHolder(DemoViewHolder holder, int position) { //Setting text over text view holder.title.setText(arrayList.get(position).getTitle()); holder.sub_title.setText(arrayList.get(position).getSubTitle()); /** Change background color of the selected items in list view **/ holder.itemView .setBackgroundColor(mSelectedItemsIds.get(position) ? 0x9934B5E4 : Color.TRANSPARENT); } @Override public DemoViewHolder onCreateViewHolder( ViewGroup viewGroup, int viewType) { LayoutInflater mInflater = LayoutInflater.from(viewGroup.getContext()); ViewGroup mainGroup = (ViewGroup) mInflater.inflate( R.layout.item_row, viewGroup, false); return new DemoViewHolder(mainGroup); } /*** * Methods required for do selections, remove selections, etc. */ //Toggle selection methods public void toggleSelection(int position) { selectView(position, !mSelectedItemsIds.get(position)); } //Remove selected selections public void removeSelection() { mSelectedItemsIds = new SparseBooleanArray(); notifyDataSetChanged(); } //Put or delete selected position into SparseBooleanArray public void selectView(int position, boolean value) { if (value) mSelectedItemsIds.put(position, value); else mSelectedItemsIds.delete(position); notifyDataSetChanged(); } //Get total selected count public int getSelectedCount() { return mSelectedItemsIds.size(); } //Return all selected ids public SparseBooleanArray getSelectedIds() { return mSelectedItemsIds; } } |
20. For RecyclerView click listener we need to create a interface naming RecyclerClick_Listener.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package com.toolbar_actionmode_demo; import android.view.View; /** * Created by SONU on 15/03/16. */ public interface RecyclerClick_Listener { /** * Interface for Recycler View Click listener **/ void onClick(View view, int position); void onLongClick(View view, int position); } |
21. Now for implementing touch listener over Recycler View create RecyclerTouchListener.java and implement the interface that we created and add the following code to it.
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 |
package com.toolbar_actionmode_demo; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; /** * Created by SONU on 15/03/16. */ public class RecyclerTouchListener implements RecyclerView.OnItemTouchListener { private GestureDetector gestureDetector; private RecyclerClick_Listener clickListener; public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final RecyclerClick_Listener clickListener) { this.clickListener = clickListener; gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapUp(MotionEvent e) { return true; } @Override public void onLongPress(MotionEvent e) { View child = recyclerView.findChildViewUnder(e.getX(), e.getY()); if (child != null && clickListener != null) { clickListener.onLongClick(child, recyclerView.getChildPosition(child)); } } }); } @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { View child = rv.findChildViewUnder(e.getX(), e.getY()); if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) { clickListener.onClick(child, rv.getChildPosition(child)); } return false; } @Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } } |
22. Finally, all done – now you can also create ActionMode.
Thanks. 🙂
Subscribe to us and get the latest news.
30 Comments
Test
Wednesday, October 5th, 2016Nice Example ! Thanks !
Peter
Monday, December 5th, 2016Thanks for this excellent tutorial!
Ralf
Monday, December 19th, 2016How can I save the selected items? So that when I rotate the phone they are selected correctly.
Dr. Droid
Monday, December 19th, 2016Hi Ralf,
To save the selected items please check the below link:
http://stackoverflow.com/questions/20209015/how-do-i-preserve-the-state-of-a-selected-spinner-dropdown-item-on-orientation-c
Let me know if you need any assistance.
Thanks
Ralf
Tuesday, December 20th, 2016The problem is that when selecting multiples items from the listview or recyclerview, and then changing the orientation (example: portrait to landscape) of the device, the items are no longer selected. How do I keep them selected?
tks,
Dr. Droid
Wednesday, December 21st, 2016Hi Ralf,
For saving listview/recyclerview selection you need to save the selection state in onSaveInstanceState(Bundle outState) method only and get it back in
onCreate(Bundle state){
if( state != null ){
//Get your selected state.
}
}
There are so many solutions available over stackoverflow.
Thanks
Ralf
Wednesday, December 21st, 2016Hi Dr.Droid,
Thanks for help! =)
Bibin
Wednesday, March 29th, 2017Hi,
Is there any property to change the background colour of toolbar when action mode enabled. I mean dynamically change toolbar color when action mode enabled.
Dr. Droid
Wednesday, March 29th, 2017Hi Bibin,
In above article if you see the video, in video if user enable action mode then toolbar color is changing.
If you mean something different then let me know.
Thanks
johson
Thursday, May 4th, 2017private void implementListViewClickListeners() {
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View view, int position, long id) {
//If ActionMode not null select item
if (mActionMode != null)
onListItemSelect(position);
}
});
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
//Select item on long click
onListItemSelect(position);
return true;
}
});
}
i was told that i need an identifier in the AdapterView in ListView.java class
Dr. Droid
Thursday, May 4th, 2017Hi Johnson,
Sorry!! But i didn’t get your problem. Can you please make your question clear?
Thanks
Rishav Chudal
Friday, July 14th, 2017Hello there,
I have looked on your tutorial and I have concerned with other tutorials too. The code implementation is slightly different than yours but the concept is same.
I also have two tabs and I am trying to handle all possible touch given by the user. Suppose I am selecting items from the recycler view list of one tab & accidently or for purpose I click on the another tab. Then I have implemented action mode to be destroyed. And returning to previous tab gives me the recycler view items, on long press of the items shows the action mode too but the items are not selectable. But if again I click on the another tab & return back to previous tab, the selection is working properly. The selection of items is working in alternate fashion when I click in another tab.
Can you figure out something???
Pasquale
Friday, July 21st, 2017The windowActionModeOverlay isn’t wotking
Pasquale
Friday, July 21st, 2017Sorry, it works 🙂
coriane
Friday, July 28th, 2017actionmode.finish() fait planter mon appli. Besoin d’aide!!!
Dr. Droid
Friday, August 11th, 2017Hi Coriane,
Can you please tell me what exception you are getting?
Thanks
harshil
Tuesday, August 22nd, 2017new MainActivity().getFragment(position) return null in Toolbar_ActionMode_Callback
Dr. Droid
Tuesday, August 22nd, 2017Hi Harshil,
Please check the position which you are passing may be the position is not valid.
Thanks
harshil
Tuesday, August 22nd, 2017i have use position 0 that is valid for my viewPagerAdapter
like this: Fragment listFragment = new NewActivity().getFragment(0);
and i have tried this also in my NewActivity().
public Fragment getFragment(int pos) {
/*List fragments = getSupportFragmentManager().getFragments();*/
Fragment frag = viewPagerAdapter.getItem(pos);
// Fragment frag = getSupportFragmentManager().getFragments().get(pos);
// Fragment frag = (Fragment) viewPagerAdapter.instantiateItem(viewPager, viewPager.getCurrentItem());
return frag;
}
all gives me NullPointerException why?
everything work file except this case.
Dr. Droid
Tuesday, August 22nd, 2017Hi Harshil,
Can you send me your over my mail id or if possible can connect via TeamViewer so that i can fix it.
Thanks
harshil
Tuesday, August 22nd, 2017finally, i have solved my issue.
Fragment listFragment = new NewActivity().getFragment(0);
In the above case NewActivity every time gives me new object. so all values are null. that’s why NullPointerException occur in my case.
so, i change my code and i remove function getFragment() from NewActivity and directly pass fragment object to getter setter class and pass this object to my each fragments when activity initializing fragments and now i can access the current fragment object.
Thank you.
john
Thursday, February 22nd, 2018thanks for your effort to explain.
serhii
Sunday, January 13th, 2019holder.itemView
.setBackgroundColor(mSelectedItemsIds.get(position) ? 0x9934B5E4
: Color.TRANSPARENT);
didn’t work for me . What means itemView? And how can I fix and set background color for selected items?
Dr. Droid
Monday, January 14th, 2019Hi Serhii,
Here itemView is the root layout view of the item row. You have to get id of your root layout and set the background color to it.
Thanks
saman
Saturday, April 20th, 2019thank you bro. your example was so useful for me. nice one.
harshil: i had same problem , fixed bye your approach.
Dilip Pashi
Monday, April 29th, 2019how to implement select all list in this code
Dr. Droid
Tuesday, April 30th, 2019Hi Dilip,
To do select all you have to make a for loop for your array list and add the positions to mSelectedItemsIds.
Thanks
mydroid
Friday, October 18th, 2019Thx! u really saved my day! You are the first one who mentions the importance of setting “windowActionModeOverlay” = true. That was the missing part in my code.
Ali Khan
Thursday, March 26th, 2020Thx! u really saved my day!…… I want to ask you something. If I select some rows from listview fragment and move to recycler view fragment action bar is still visible…..how I can hide this.
Dr. Droid
Friday, March 27th, 2020Hi Ali,
For this when you switch between tabs you have to call this method to finish action mode: mActionMode.finish(); and also clear the selected list.
Thanks