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編譯到模擬器或者手機上,看看效果了~
Android — 長按ListView 利用上下文菜單(ActionMode) 進行批量事件處理