当前位置: 代码迷 >> Android >> Android — 长按ListView 利用上下文菜单(ActionMode) 进展批量事件处理
  详细解决方案

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

热度:10   发布时间:2016-04-28 00:07:13.0
Android — 长按ListView 利用上下文菜单(ActionMode) 进行批量事件处理

好久没写博客拉```````

最近终于稍微闲一点了```````

无聊拿手机清理短信,发现批量事件的处理还是挺管用的``````

那么自己也来山寨一记看看效果吧`````


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



然后  是我们自己简单山寨的效果:

     


模拟的操作过程很简单,但也很有代表性。

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


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

接着,当网络数据成功下载到移动设备上后,将数据绑定显示到对应的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="@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装载和显示工作完成之后,我们要做的就很简单了,只需要在onItemCheckedStateChanged中进行监听,当用户选中某个列表项时,对用于显示提示信息的TextView的显示内容进行更新,则OK了。


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


版权声明:本文为博主原创文章,未经博主允许不得转载。

1楼yangguohai1昨天 14:15
151565
  相关解决方案