zoukankan      html  css  js  c++  java
  • 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="@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编译到模拟器或者手机上,看看效果了~


  • 相关阅读:
    【.Net--资料】
    【系统Configmachine.config与自己的应用程序的App.config/Web.Config配置节点重复】解决方法
    【AutoMapper】实体类间自动实现映射关系,及其转换。
    【EntityFramwork--处理数据并发问题】
    【IOC--Common Service Locator】不依赖于某个具体的IoC
    Android学习——碎片Fragment的使用
    Android学习——利用RecyclerView编写聊天界面
    Android学习——控件ListView的使用
    Android学习——LinearLayout布局实现居中、左对齐、右对齐
    Android学习——Button填充颜色及实现圆角
  • 原文地址:https://www.cnblogs.com/blfbuaa/p/7218869.html
Copyright © 2011-2022 走看看