Android — 长按ListView 利用上下文菜单(ActionMode) 进行批量事件处理

关山初度尘未洗,策马扬鞭再奋蹄!这篇文章主要讲述Android — 长按ListView 利用上下文菜单(ActionMode) 进行批量事件处理相关的知识,希望能为你提供帮助。
好久没写博客拉```````
近期最终略微闲一点了```````
无聊拿手机清理短信。发现批量事件的处理还是挺管用的``````
那么自己也来山寨一记看看效果吧`````


闲话少说,首先,我们来看下手机自带的短信功能里运行批量删除时的效果:

Android — 长按ListView 利用上下文菜单(ActionMode) 进行批量事件处理

文章图片





然后  是我们自己简单山寨的效果:
Android — 长按ListView 利用上下文菜单(ActionMode) 进行批量事件处理

文章图片
 
Android — 长按ListView 利用上下文菜单(ActionMode) 进行批量事件处理

文章图片
 
Android — 长按ListView 利用上下文菜单(ActionMode) 进行批量事件处理

文章图片
 
Android — 长按ListView 利用上下文菜单(ActionMode) 进行批量事件处理

文章图片



模拟的操作过程非常easy,但也非常有代表性。

我们假定我们所处的场景为。进入一个存放联系人列表的界面。


于是,首先我们定义了一个进度框,模拟提示正在从网络上下载数据。

接着。当网络数据成功下载到移动设备上后,将数据绑定显示到相应的ListView之中。
然后,就是我们这篇博客提到的:长按该联系人列表的ListView触发事件。
弹出使用ActionMode的上下文菜单。并让该ListView中的列表项支持复现,实现批量操作。
最后。就是当用户选择了一定数量的选项后。点击菜单中的Item进行某项批量操作后,运行相应的操作,并刷新ListView。


理清了我们想要实现的大致效果,接着我们要做的
就是整理一下思路,然后逐步的去编写代码,完毕实现工作。let‘s do it !



首先,我们已经知道了自己 想要以一个联系人列表作为场景。

那么,自然我们会须要一个ListView来绑定和存放这些联系人数据。
于是,我们先将存放ListView以及定义该ListView的Item的细节的布局文件搞出来,分别为:


context_menu_action_mode.xml
< ?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=" match_parent" android:orientation=" vertical" > < ListView android:id=" @+id/context_menu_listView" android:layout_width=" match_parent" android:layout_height=" match_parent" /> < /LinearLayout>


context_menu_action_mode_item.xml
< ?
xml version=" 1.0" encoding=" utf-8" ?
> < RelativeLayout xmlns:android=" http://schemas.android.com/apk/res/android" android:layout_width=" match_parent" android:layout_height=" match_parent" android:paddingBottom=" @dimen/activity_vertical_margin" android:paddingLeft=" @dimen/activity_horizontal_margin" android:paddingRight=" @dimen/activity_horizontal_margin" android:paddingTop=" @dimen/activity_vertical_margin" > < ImageView android:id=" @+id/user_head" android:layout_width=" 55dp" android:layout_height=" 55dp" android:contentDescription=" @string/user_head_description" android:src=https://www.songbingjia.com/android/" @drawable/headimage_default" /> < TextView android:id=" @+id/user_name" android:layout_width=" wrap_content" android:layout_height=" wrap_content" android:layout_marginLeft=" 20dp" android:layout_marginStart=" 20dp" android:layout_toEndOf=" @id/user_head" android:layout_toRightOf=" @id/user_head" android:textSize=" 25sp" /> < TextView android:id=" @+id/phone_number" android:layout_width=" wrap_content" android:layout_height=" wrap_content" android:layout_alignBottom=" @id/user_head" android:layout_marginLeft=" 20dp" android:layout_marginStart=" 20dp" android:layout_toEndOf=" @id/user_head" android:layout_toRightOf=" @id/user_head" /> < CheckBox android:id=" @+id/contact_selected_checkbox" android:layout_width=" wrap_content" android:layout_height=" wrap_content" android:layout_alignParentEnd=" true" android:layout_alignParentRight=" true" android:layout_centerVertical=" true" android:clickable=" false" android:focusable=" false" /> < /RelativeLayout>


关于布局的定义。并没有什么难点。唯一须要注意的是,我们为了更加友好的交互体验,所以在用户长按ListView进入可复选的模式后,
在每一个列表的最右側加入显示了一个CheckBox,以提示用户是否成功选择到了想要操作的列表项。
CheckBox仅仅有在用户进入复选模式后,才显示,所以我们须要在后面注意在代码中动态的控制其显示情况。

而且!更须要注意的是,记得将CheckBox的clickable与focusable两个属性的值 设置为false!
这样做的原因是由于CheckBox(定义在作为ListView的Item文件其中)自身的响应焦点及点击事件的优先级高于ListView自身。
所以。假设忘记设置的话,焦点及响应事件将被拦截在CheckBox,无法到达ListView。



第二步,当我们定义好了ListView的相关程序之后,自然忘不了它的好基友:适配器Adapter
MyContactAdapter.java:
package com.example.android_menu_test_demo.adapter; import java.util.ArrayList; import com.example.android_menu_test_demo.R; import com.example.android_menu_test_demo.domain.Contact; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.TextView; public class MyContactAdapter extends BaseAdapter { private Context mContext; private ArrayList< Contact> contacts; private ViewHolder mViewHolder; private ArrayList< Contact> selected_contacts = new ArrayList< Contact> (); private boolean itemMultiCheckable; public MyContactAdapter(Context mContext, ArrayList< Contact> contacts) { this.mContext = mContext; this.contacts = contacts; } @Override public int getCount() { return contacts.size(); } @Override public Object getItem(int position) { return contacts.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.contact_listview_item, null); mViewHolder = new ViewHolder(); mViewHolder.user_head = (ImageView) convertView.findViewById(R.id.user_head); mViewHolder.user_name_text = (TextView) convertView.findViewById(R.id.user_name); mViewHolder.phone_number_text = (TextView) convertView.findViewById(R.id.phone_number); mViewHolder.item_seleted = (CheckBox) convertView.findViewById(R.id.contact_selected_checkbox); convertView.setTag(mViewHolder); } else { mViewHolder = (ViewHolder) convertView.getTag(); }// ************对于控件的详细处理****************// 设置checkbox是否可见 if (itemMultiCheckable) { mViewHolder.item_seleted.setVisibility(View.VISIBLE); // 假设checkbox可见。证明当前处于可多选操作情况下,则依据用户选择情况设置checkbox被选中状态 if (selected_contacts.contains(contacts.get(position))) { mViewHolder.item_seleted.setChecked(true); } else { mViewHolder.item_seleted.setChecked(false); }} else { mViewHolder.item_seleted.setVisibility(View.GONE); }// 控件赋值 Contact contact = contacts.get(position); mViewHolder.user_name_text.setText(contact.getUserName()); mViewHolder.phone_number_text.setText(contact.getPhoneNumber()); return convertView; } public void setItemMultiCheckable(boolean flag) { itemMultiCheckable = flag; } public void addSelectedContact(int position) { selected_contacts.add(contacts.get(position)); } public void cancelSeletedContact(int position) { selected_contacts.remove(contacts.get(position)); } public void clearSeletedContacts() { selected_contacts = new ArrayList< Contact> (); } public void deleteSeletedContacts() { for (Contact contact : selected_contacts) { contacts.remove(contact); } } static class ViewHolder { ImageView user_head; TextView user_name_text, phone_number_text; CheckBox item_seleted; } }


适配器类的定义与我们开发中最常见的定义并没有太多差别。值 得注意的的代码,无非就是前面谈到的,做好动态控制CheckBox显示状态的工作。
另外,我们在适配器的定义中,为了让listview要显示的数据,更便于装载和传递。
一般会定义封装数据的实体类,正如上面的Contact类。只是这个太简单,就没贴代码的必要了。


接下来。就是我们想要实现的功能的重点了,
我们说到希望通过ListView的长点击事件,来触发一个上下文菜单来进行事件处理。
在Android 3.0之后加入的ActionMode相对于之前的普通上下文菜单。
显然更适合对于批量事件的处理。有着更好的交互体验。



所以说。既然将要使用到上下文 菜单,那么。废话少说。

先定义一个我们须要的简单的菜单文件:
multi_acitonmode_menu.xml:
< ?xml version=" 1.0" encoding=" utf-8" ?
> < menu xmlns:android=" http://schemas.android.com/apk/res/android" > < item android:id=" @+id/menu_cancle" android:showAsAction=" always" android:title=" @string/item_cancle" /> < item android:id=" @+id/menu_delete" android:showAsAction=" always" android:title=" @string/item_delete" /> < /menu>


紧接着,一切准备 工作我们都已经基本就绪,
那么接下来要做的。自然就是Activity的代码编写工作了。
ContextMenuActionModeActivity.java
package com.example.android_menu_test_demo; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ListView; import android.widget.TextView; import android.widget.AbsListView.MultiChoiceModeListener; import java.util.ArrayList; import com.example.android_menu_test_demo.adapter.MyContactAdapter; import com.example.android_menu_test_demo.domain.Contact; import android.annotation.TargetApi; import android.app.Activity; import android.app.ProgressDialog; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class ContextMenuActionModeActivity extends Activity { private ListView contact_list_view; private ProgressDialog mDialog; private MyContactAdapter mAdpater; private MultiModeCallback mCallback; // 模拟数据 private ArrayList< Contact> contacts; private String[] userNames = new String[] { " Jack" , " Rose" , " Matt" , " Adam" , " Xtina" , " Blake" , " Tupac" , " Biggie" , " T.I" , " Eminem" }; private String[] phoneNumbers = new String[] { " 138-0000-0001" , " 138-0000-0002" , " 138-0000-0003" , " 138-0000-0004" , " 138-0000-0005" , " 138-0000-0006" , " 138-0000-0007" , " 138-0000-0008" , " 138-0000-0009" , " 138-0000-0010" }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.context_menu_action_mode); initView(); new ContactsDownloadTask().execute(); } private void initView() { contact_list_view = (ListView) this.findViewById(R.id.context_menu_listView); mDialog = new ProgressDialog(this); mDialog.setTitle(" 提示信息" ); mDialog.setMessage(" 下载联系人列表中..." ); mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mCallback = new MultiModeCallback(); contact_list_view.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); contact_list_view.setMultiChoiceModeListener(mCallback); } private void downloadContactsFromServer() { if (contacts == null) { contacts = new ArrayList< Contact> (); }for (int i = 0; i < userNames.length; i++) { contacts.add(new Contact(userNames[i], phoneNumbers[i])); } } private class ContactsDownloadTask extends AsyncTask< Void, Integer, Void> {private int currentlyProgressValue; @Override protected void onPreExecute() { mDialog.show(); super.onPreExecute(); }@Override protected Void doInBackground(Void... params) {while (currentlyProgressValue < 100) { publishProgress(++currentlyProgressValue); try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } }// download data from server downloadContactsFromServer(); return null; }@Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); mDialog.setProgress(values[0]); }@Override protected void onPostExecute(Void result) { super.onPostExecute(result); mAdpater = new MyContactAdapter(ContextMenuActionModeActivity.this, contacts); contact_list_view.setAdapter(mAdpater); mDialog.dismiss(); } } private class MultiModeCallback implements MultiChoiceModeListener { private View mMultiSelectActionBarView; private TextView mSelectedCount; @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { mode.getMenuInflater().inflate(R.menu.multi_acitonmode_menu, menu); mAdpater.setItemMultiCheckable(true); mAdpater.notifyDataSetChanged(); if (mMultiSelectActionBarView == null) { mMultiSelectActionBarView = LayoutInflater.from(ContextMenuActionModeActivity.this) .inflate(R.layout.list_multi_select_actionbar, null); mSelectedCount = (TextView) mMultiSelectActionBarView.findViewById(R.id.selected_conv_count); } mode.setCustomView(mMultiSelectActionBarView); ((TextView) mMultiSelectActionBarView.findViewById(R.id.title)).setText(R.string.select_item); return true; }@Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { // TODO Auto-generated method stub return false; }@Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.menu_cancle: mAdpater.setItemMultiCheckable(false); mAdpater.clearSeletedContacts(); mAdpater.notifyDataSetChanged(); mode.finish(); break; case R.id.menu_delete: mAdpater.deleteSeletedContacts(); mAdpater.notifyDataSetChanged(); mode.invalidate(); mode.finish(); break; default: break; } return false; }@Override public void onDestroyActionMode(ActionMode mode) { mAdpater.setItemMultiCheckable(false); mAdpater.clearSeletedContacts(); mAdpater.notifyDataSetChanged(); }@Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { if (checked) { mAdpater.addSelectedContact(position); } else { mAdpater.cancelSeletedContact(position); }mAdpater.notifyDataSetChanged(); updateSeletedCount(); mode.invalidate(); }public void updateSeletedCount() { mSelectedCount.setText(Integer.toString(contact_list_view.getCheckedItemCount()) + " 条" ); } }}


对于我们这种菜鸟来说,上面activity代码中值 得注意的可能是:1、基本上,我们首先会定义一个异步任务类。模拟从网络下载数据的过程。有助于Adapter的API的使用的掌握。
2、我们在上面代码中定义的实现了MultiChoiceModeListener接口的内部类,MultiModeCallback就是帮助我们实现长按ListView(也试用于GridView)。而且监听处理MultiChoice事件的关键。
        —  简单来说,能够看到。我们在该内部类的回调方法onCreateActionMode中,处理长按ListView后,ActionMode菜单相关的创建工作。而且在此控制ListView中的CheckBox显示,告知用户,我们已经进入到了能够进行批量操作的模式下。

        —  onActionItemClicked方法 用于监听和响应菜单上相应的选项的点击事件,你能够在此依据自己的需求,为相应的菜单选项编写响应代码。

        —  onDestroyActionMode方法 用于处理菜单销毁时,所要运行的动作。
        —  而onItemCheckedStateChanged方法 则就是用于监听处理ListView中每一个列表项的选中状态改变时的回调了。我们会在这里依据需求完毕相应的编码工作。

3、到了这里,我们对于我们想要的功能的实现,能够说已经是基本搞定了。可是,你可能已经在上面的MultiModeCallback类的某些代码中注意点到:
为了更加友善的交互感受,我们 还能够以ActionBar的形式。在菜单条上,加入一段内容。正如 短信功能里所使用的那样,用以提示用户类似 于“您当前已经选择了XX条内容”的信息。所以我们还会定义一个类似 ActionBar的布局文件。例如以下:
list_multi_select_actionbar.xml:
< LinearLayout xmlns:android=" http://schemas.android.com/apk/res/android" android:id=" @+id/custom_title_root" android:layout_width=" match_parent" android:layout_height=" match_parent" android:orientation=" horizontal" > < TextView android:id=" @+id/title" android:layout_gravity=" center_vertical" android:layout_height=" wrap_content" android:layout_width=" wrap_content" android:textColor=" #ffffff" /> < TextView android:id=" @+id/selected_conv_count" android:layout_gravity=" center_vertical" android:layout_width=" wrap_content" android:layout_height=" wrap_content" android:textColor=" #ffffff" /> < /LinearLayout>


在上面的代码中,你能够看到,我们相同是在onCreateActionMode方法中完毕ActionMode上下文菜单的装载工作的同一时候,也会进行对于该作为ActionBar使用的View的装载与显示控制工作。在该View装载和显示工作完毕之后,我们要做的就非常easy了,仅仅须要在onItemCheckedStateChanged中进行监听,当用户选中某个列表项时,对用于显示提示信息的TextView的显示内容进行更新,则OK了。


走到这一步,我们能够说是已经山寨完成了,下一步要做的则能够将Demo编译到模拟器或者手机上,看看效果了~

【Android — 长按ListView 利用上下文菜单(ActionMode) 进行批量事件处理】























    推荐阅读