ListFragment+CheckedTextView和ListFragment+CheckBox两种多选效果

本来不想写这篇博客,原因是不想被几个月以后的自己鄙视,其实很容易的,可是又有几个细节要注意,还遇上了几个不那么难的bug,但是不好描述,也解释不清楚,也不好咨询别人的问题。所以我决心还是记录一下。(注意一下所有出现Qrom,qrom词的都可以把qrom去掉,因为qrom是我引入的第三方控件库,对原生的控件进行了重新封装,效果更好而已,大家也不要问我要,这个是公司产品,我也不能给你们,但这并不影响你们理解博客)


先看效果(请忽略我是在ViewPager中添加ListFragment事情):


 1.  ListFragment+CheckedTextView和ListFragment+CheckBox两种多选效果_第1张图片             2.  ListFragment+CheckedTextView和ListFragment+CheckBox两种多选效果_第2张图片


CheckedTextView基本版如图1,实现步骤如下:

item的布局:




    

    


代码:

package com.marttinli.qromstudy1_1;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckedTextView;

import com.tencent.qrom.support.v4.app.ListFragment;
import com.tencent.qrom.widget.AdapterView;
import com.tencent.qrom.widget.AdapterView.OnItemClickListener;
import com.tencent.qrom.widget.ListView;

public class ListFragmentCheckMode extends ListFragment {

	String[] datas;
	ListView listView;
	@SuppressLint("UseSparseArrays")
	private SparseArray checkedMap = new SparseArray();

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onActivityCreated(savedInstanceState);
		datas = getResources().getStringArray(R.array.date);
		setListAdapter(new MyAdapter());
		listView = getListView();
		listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
		listView.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView parent, View view,
					int position, long id) {
				// TODO Auto-generated method stub
				CheckedTextView cheView = (CheckedTextView) view
						.findViewById(R.id.checktv_title);
				cheView.setChecked(!cheView.isChecked());
				checkedMap.put(position, checView.isChecked());
			}
		});
	}

	private class MyAdapter extends ArrayAdapter {
		public MyAdapter() {
			// TODO Auto-generated constructor stub
			super(getActivity(), 0, datas);
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			// TODO Auto-generated method stub
			ViewHolder holder = null;
			if (convertView == null) {
				convertView = getActivity().getLayoutInflater().inflate(
						R.layout.item1_listview, null);
				holder = new ViewHolder();
				holder.tView = (CheckedTextView) convertView
						.findViewById(R.id.checktv_title);
				convertView.setTag(holder);
			} else {
				holder = (ViewHolder) convertView.getTag();
			}
			holder.tView.setText(datas[position]);
			holder.tView.setChecked(checkedMap.get(position) == null ? false
					: checkedMap.get(position));
			return convertView;
		}

		class ViewHolder {
			CheckedTextView tView;
		}
	}
}


这里必须注意:

1,getListView()放在onActivityCreated()中获取,反正不能在onCreated()获取,因为在onCreated的时候listview还未初始化完成,其他地方暂时没有尝试过。

2,item.xml中CheckedTextView设置

 android:checkMark="?android:attr/listChoiceIndicatorMultiple"
不过字面翻译应该很好理解,如果没有的话,复选框就不会出现

3,在onItemClick()中获取CheckedTextView使用view.findViewById(R.id.checktv_title);,网上有很多种其他方法,但是终究没有让我找到一种正确的。他们的问题主要是在于,获取到的CheckedTextView只是对应屏幕上的item,一旦listview有位置滑动,那么则将不能准确对焦。

还是举个例子吧:

				CheckedTextView cheView = (CheckedTextView) parent.getChildAt(position).findViewById(R.id.checktv_title);
这种获取方法就是错误的,理由如上,不信的可以自己尝试下。

4,

		listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

这里也可以设置成单选框,至于是不是只要修改了这里就可以了呢,我也不知道,但是我感觉第2步的CheckedTextView的checkMark也要设置成单选,你可以试一下。

5,在获取选中状态时,建议使用CheckedTextView.isChecked()

因为之前我还使用过另外一个方法listview.isItemChecked(position)也能获取到选中状态,但是她不太稳定,有时候选中了也会返回false,原因暂不明确,总之不建议使用。


本代码中其他装逼地方:

1,SparseArray的使用

使用操作基本同HashMap,功能同HashMap,那为什么不使用大家熟悉的HashMap呢?

原因一,SparseArray采用二分法查找,在效率上优于HashMap(注明:我也不知道HashMap用什么方法查找,也没有比较过他们的效率,google官方推荐使用SparseArray并说她效率高,不管你信不信,我是信了)。

有人想了解细节的,我推荐一篇文章:Android应用性能优化之使用SparseArray替代HashMap

原因二,SparseArray相对HashMap更加不熟悉,程序员应该要有坚持学习新东西的心态,另外,相对而言不熟悉的东西才能提高逼格。

2,有两句精简的代码

cheView.setChecked(!cheView.isChecked());
holder.tView.setChecked(checkedMap.get(position) == null ? false
					: checkedMap.get(position));
这两段之所以这么写:

原因一:代码更加简洁

原因二:逼格



CheckedTextView Plus版如图2

这里集成了在ActionMode下的全选,取消全选,删除操作。

代码如下:

package com.marttinli.qromstudy1_1;

import java.util.ArrayList;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.util.SparseArray;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckedTextView;

import com.tencent.qrom.app.ActionBar;
import com.tencent.qrom.support.v4.app.ListFragment;
import com.tencent.qrom.widget.AdapterView;
import com.tencent.qrom.widget.AdapterView.OnItemClickListener;
import com.tencent.qrom.widget.AdapterView.OnItemLongClickListener;
import com.tencent.qrom.widget.ListView;
import com.tencent.qrom.widget.ToggleButton;

public class ListFragmentCheckMode extends ListFragment {

	ArrayList datas;
	ListView listView;
	ActionMode mActionMode;
	MyAdapter myAdapter;
	@SuppressLint("UseSparseArrays")
	private SparseArray checkedMap;

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onActivityCreated(savedInstanceState);
		datas = listFromStrings(getResources().getStringArray(R.array.date));
		checkedMap = mapsFromDatas(datas);
		setListAdapter(myAdapter = new MyAdapter());
		listView = getListView();
		listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
		listView.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView parent, View view,
					int position, long id) {
				// TODO Auto-generated method stub
				CheckedTextView cheView = (CheckedTextView) view
						.findViewById(R.id.checktv_title);
				cheView.setChecked(!cheView.isChecked());
				// 这里证明了cheView.isChecked()比listView.isItemChecked(position)更稳定,原因尚不明确
				System.out.println("listView.isItemChecked(position):"
						+ listView.isItemChecked(position)
						+ " cheView.isChecked():" + cheView.isChecked());
				checkedMap.put(position, cheView.isChecked());

			}
		});

		listView.setOnItemLongClickListener(new OnItemLongClickListener() {

			@Override
			public boolean onItemLongClick(AdapterView parent, View view,
					int position, long id) {
				// TODO Auto-generated method stub

				// Start the CAB using the ActionMode.Callback defined above
				mActionMode = getActivity().startActionMode(mCallback);
				initActionBarButton();
				return true;
			}

		});

	}

	private void initActionBarButton() {
		final ActionBar qromActionBar = getActivity().getQromActionBar();
		final ToggleButton selectAll = (ToggleButton) qromActionBar
				.getMultiChoiceView(true);
		if (selectAll != null) {
			selectAll.setText(getString(R.string.selectedAll));
			selectAll.setTextOff(getString(R.string.selectedAll));
			selectAll.setTextOn(getString(R.string.unSelectedAll));
			selectAll.setOnClickListener(new OnClickListener() {

				@Override
				public void onClick(View v) {
					// TODO Auto-generated method stub
					if (selectAll.isChecked()) {
						selectedAll();
					} else {
						unSelectedAll();
					}
				}
			});
		}

		final Button cancelButton = (Button) qromActionBar.getCloseView(true);
		if (cancelButton != null) {
			cancelButton.setText(R.string.cancel);
			cancelButton.setOnClickListener(new View.OnClickListener() {

				@Override
				public void onClick(View arg0) {
					if (mActionMode != null) {
						mActionMode.finish();
					}
				}
			});
		}

	}

	private SparseArray mapsFromDatas(ArrayList strings) {
		SparseArray maps = new SparseArray<>();
		for (int i = 0; i < strings.size(); i++) {
			maps.put(i, false);
		}
		return maps;
	}

	private void selectedAll() {
		for (int i = 0; i < datas.size(); i++) {
			checkedMap.put(i, true);
		}
		myAdapter.notifyDataSetChanged();
	}

	private void unSelectedAll() {
		for (int i = 0; i < datas.size(); i++) {
			checkedMap.put(i, false);
		}
		myAdapter.notifyDataSetChanged();
	}

	/**
	 * 这里for循环从大往小,是很有必要的。保证remove后面的数据不会对面前数据的位置有影响
	 * 
	 * @param map
	 */
	private void removeItems(SparseArray map) {

		for (int i = datas.size() - 1; i >= 0; i--) {
			System.out.println("i:" + i);
			if (map.get(i)) {

				datas.remove(i);
				map.remove(i);
				System.out.println("remove i:" + i);

			}
		}
		myAdapter.notifyDataSetChanged();
		listView.refreshDrawableState();
	}

	private ArrayList listFromStrings(String[] strings) {
		ArrayList arrayList = new ArrayList();
		for (int i = 0; i < strings.length; i++) {
			arrayList.add(strings[i]);
		}
		return arrayList;
	}

	private class MyAdapter extends ArrayAdapter {
		public MyAdapter() {
			// TODO Auto-generated constructor stub
			super(getActivity(), 0, datas);
		}

		/**
		 * getView表示只显示在屏幕的items会初始化。
		 */
		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			// TODO Auto-generated method stub
			ViewHolder holder = null;
			if (convertView == null) {
				convertView = getActivity().getLayoutInflater().inflate(
						R.layout.item1_listview, null);
				holder = new ViewHolder();
				holder.tView = (CheckedTextView) convertView
						.findViewById(R.id.checktv_title);
				convertView.setTag(holder);
			} else {
				holder = (ViewHolder) convertView.getTag();
			}
			holder.tView.setText(datas.get(position));
			holder.tView.setChecked(checkedMap.get(position) == null ? false
					: checkedMap.get(position));
			return convertView;
		}

		class ViewHolder {
			CheckedTextView tView;
		}
	}

	private ActionMode.Callback mCallback = new ActionMode.Callback() {

		@Override
		public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
			return false;
		}

		@Override
		public void onDestroyActionMode(ActionMode mode) {
			// TODO Auto-generated method stub
		}

		@Override
		public boolean onCreateActionMode(ActionMode mode, Menu menu) {
			MenuInflater inflater = mode.getMenuInflater();
			inflater.inflate(R.menu.main2, menu);
			return true;
		}

		@Override
		public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
			boolean ret = false;
			if (item.getItemId() == R.id.delete) {
				removeItems(checkedMap);
				checkedMap = mapsFromDatas(datas);
				mode.finish();
				ret = true;
			}
			return ret;
		}
	};

}

注意:

1,我在removeItems()方法里对datas循环删除是从大往小遍历,个人觉得使用得恰到好处。如此可以保证remove之后,在removed对象位置之前的对象的位置都不会有变化。

2,在removeItems()之后要对checkMap的数据重新初始化,保证对应的position和listview同步刷新。

3,ActionMode虽然会上下弹出一个Bar,但是上面那个Bar其实还是ActionBar。我猜有可能原来的ActionBar会暂时缓存起来,并且在Activity中释放掉了,等到ActionMode模式结束,然后重新绘制原来那个ActionBar。


CheckedTextView Plus++版也能图2一样,不过是由正常模式在ActionMode下切换到CheckBox模式

先看效果图吧,有图更比说很多话就管用

ListFragment+CheckedTextView和ListFragment+CheckBox两种多选效果_第3张图片  在ActionMode下切换到    ListFragment+CheckedTextView和ListFragment+CheckBox两种多选效果_第4张图片


先分析下思路(这里换成了CheckBox,跟CheckedTextView使用基本一样,请忽略细节)。

1,item.xml中同时设置Button和CheckBox为alignRight,在ActionMode出现和释放阶段通过刷新adapter设置切换Button和CheckBox一个visibility,另一个gone。这么一分析就很简单的。

item.xml




    

    

    

代码:

package com.marttinli.qromstudy1_1;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.SparseArray;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.tencent.qrom.app.ActionBar;
import com.tencent.qrom.support.v4.app.Fragment;
import com.tencent.qrom.widget.AdapterView;
import com.tencent.qrom.widget.AdapterView.OnItemClickListener;
import com.tencent.qrom.widget.AdapterView.OnItemLongClickListener;
import com.tencent.qrom.widget.CheckBox;
import com.tencent.qrom.widget.ListView;
import com.tencent.qrom.widget.ToggleButton;

enum CheckStatus {
	NoCheckStatus, CheckStaus
}

public class MyListFragment extends Fragment {

	List list = new ArrayList<>();;
	onItemSelectedListener mListener;
	PackageManager packageManager;
	private SparseArray checkedMap;
	ActionMode mActionMode;
	ListView listView;
	MyAdapter myAdapter;
	public static final int FILTER_ALL_APP = 0; // 所有应用程序
	public static final int FILTER_SYSTEM_APP = 1; // 系统程序
	public static final int FILTER_THIRD_APP = 2; // 第三方应用程序
	public static final int FILTER_SDCARD_APP = 3; // 安装在SDCard的应用程序
	CheckStatus checkStaus = CheckStatus.NoCheckStatus;

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		try {
			mListener = (onItemSelectedListener) activity;
		} catch (ClassCastException e) {
			throw new ClassCastException(activity.toString()
					+ " must implement onItemSelectedListener");
		}
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		View rootView = inflater.inflate(R.layout.fragment_section_dummy,
				container, false);
		packageManager = getActivity().getPackageManager();
		List mlist = packageManager.getInstalledPackages(0);
		list.addAll(getApplications(mlist, FILTER_THIRD_APP));
		checkedMap = mapsFromDatas(list);
		listView = (ListView) rootView.findViewById(R.id.listview);
		listView.setAdapter(myAdapter = new MyAdapter());
		listView.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView parent, View view,
					int position, long id) {
				// TODO Auto-generated method stub
				CheckBox cheView = (CheckBox) view
						.findViewById(R.id.select_check);
				cheView.setChecked(!cheView.isChecked());
				// 这里证明了cheView.isChecked()比listView.isItemChecked(position)更稳定,原因尚不明确
				System.out.println("listView.isItemChecked(position):"
						+ listView.isItemChecked(position)
						+ " cheView.isChecked():" + cheView.isChecked());
				checkedMap.put(position, cheView.isChecked());

			}
		});
		listView.setOnItemLongClickListener(new OnItemLongClickListener() {

			@Override
			public boolean onItemLongClick(AdapterView parent, View view,
					int position, long id) {
				// TODO Auto-generated method stub

				// Start the CAB using the ActionMode.Callback defined above
				mActionMode = getActivity().startActionMode(mCallback);
				initActionBarButton();
				return true;
			}

		});
		return rootView;
	}

	private ActionMode.Callback mCallback = new ActionMode.Callback() {

		@Override
		public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
			return false;
		}

		@Override
		public void onDestroyActionMode(ActionMode mode) {
			// TODO Auto-generated method stub
			checkStaus = CheckStatus.NoCheckStatus;
			myAdapter.notifyDataSetChanged();
		}

		@Override
		public boolean onCreateActionMode(ActionMode mode, Menu menu) {
			MenuInflater inflater = mode.getMenuInflater();
			inflater.inflate(R.menu.main2, menu);
			checkStaus = CheckStatus.CheckStaus;
			myAdapter.notifyDataSetChanged();
			return true;
		}

		@Override
		public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
			boolean ret = false;
			if (item.getItemId() == R.id.delete) {
				removeItems(checkedMap);
				checkedMap = mapsFromDatas(list);
				ret = true;
			}
			return ret;
		}
	};

	private void initActionBarButton() {
		final ActionBar qromActionBar = getActivity().getQromActionBar();
		final ToggleButton selectAll = (ToggleButton) qromActionBar
				.getMultiChoiceView(true);
		if (selectAll != null) {
			selectAll.setText(getString(R.string.selectedAll));
			selectAll.setTextOff(getString(R.string.selectedAll));
			selectAll.setTextOn(getString(R.string.unSelectedAll));
			selectAll.setOnClickListener(new OnClickListener() {

				@Override
				public void onClick(View v) {
					// TODO Auto-generated method stub
					if (selectAll.isChecked()) {
						selectedAll();
					} else {
						unSelectedAll();
					}
				}
			});
		}

		final Button cancelButton = (Button) qromActionBar.getCloseView(true);
		if (cancelButton != null) {
			cancelButton.setText(R.string.cancel);
			cancelButton.setOnClickListener(new View.OnClickListener() {

				@Override
				public void onClick(View arg0) {
					if (mActionMode != null) {
						mActionMode.finish();
					}
				}
			});
		}

	}

	private void selectedAll() {
		for (int i = 0; i < list.size(); i++) {
			checkedMap.put(i, true);
		}
		myAdapter.notifyDataSetChanged();
	}

	private void unSelectedAll() {
		for (int i = 0; i < list.size(); i++) {
			checkedMap.put(i, false);
		}
		myAdapter.notifyDataSetChanged();
	}

	/**
	 * 这里for循环从大往小,是很有必要的。保证remove后面的数据不会对面前数据的位置有影响
	 * 
	 * @param map
	 */
	private void removeItems(SparseArray map) {

		for (int i = list.size() - 1; i >= 0; i--) {
			System.out.println("i:" + i);
			if (map.get(i)) {

				list.remove(i);
				map.remove(i);
				System.out.println("remove i:" + i);

			}
		}
	}

	private SparseArray mapsFromDatas(List list2) {
		SparseArray maps = new SparseArray<>();
		for (int i = 0; i < list2.size(); i++) {
			maps.put(i, false);
		}
		return maps;
	}

	private List getApplications(List mlist, int flag) {
		List l = new ArrayList();
		switch (flag) {
		case FILTER_ALL_APP:
			l.addAll(mlist);
			break;
		case FILTER_SYSTEM_APP:
			for (PackageInfo packageInfo : mlist) {
				if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
					l.add(packageInfo);
				}
			}
			break;
		case FILTER_THIRD_APP:
			for (PackageInfo packageInfo : mlist) {
				if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) <= 0) {
					l.add(packageInfo);
				}// 本来是系统程序,被用户手动更新后,该系统程序也成为第三方应用程序了
				else if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
					l.add(packageInfo);
				}
			}
			break;
		case FILTER_SDCARD_APP:
			for (PackageInfo packageInfo : mlist) {
				if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
					l.add(packageInfo);
				}
			}
			break;
		default:
			break;
		}
		return l;
	}

	public interface onItemSelectedListener {
		public void onItemSelected(int position);
	}

	public void onListItemClick(ListView l, View v, int position, long id) {
		// TODO Auto-generated method stub
		mListener.onItemSelected(position);

	}

	class MyAdapter extends BaseAdapter {

		@Override
		public int getCount() {
			return list.size();
		}

		@Override
		public Object getItem(int arg0) {
			// TODO Auto-generated method stub
			return list.get(arg0);
		}

		@Override
		public long getItemId(int arg0) {
			// TODO Auto-generated method stub
			return arg0;
		}

		@Override
		public View getView(int arg0, View convertView, ViewGroup arg2) {

			Holder holder;
			if (null == convertView) {
				holder = new Holder();
				convertView = (RelativeLayout) LayoutInflater.from(
						getActivity()).inflate(R.layout.item2_listview, null);
				holder.iconView = (ImageView) convertView
						.findViewById(R.id.imageView1);
				holder.textView = (TextView) convertView
						.findViewById(R.id.textView1);
				holder.button = (Button) convertView.findViewById(R.id.button1);
				holder.box = (CheckBox) convertView
						.findViewById(R.id.select_check);
				convertView.setTag(holder);
			} else {
				holder = (Holder) convertView.getTag();
			}
			final PackageInfo app = list.get(arg0);
			holder.iconView.setImageDrawable(packageManager
					.getApplicationIcon(app.applicationInfo));
			holder.textView.setText(packageManager.getApplicationLabel(
					app.applicationInfo).toString());
			holder.button.setText("打开");
			holder.button.setOnClickListener(new OnClickListener() {

				@Override
				public void onClick(View view) {

					Intent intent = packageManager
							.getLaunchIntentForPackage(app.packageName);
					getActivity().startActivity(intent);
				}
			});
			holder.box.setChecked(checkedMap.get(arg0) == null ? false
					: checkedMap.get(arg0));

			if (checkStaus == CheckStatus.NoCheckStatus) {
				holder.button.setVisibility(View.VISIBLE);
				holder.box.setVisibility(View.GONE);
			} else {
				holder.button.setVisibility(View.GONE);
				holder.box.setVisibility(View.VISIBLE);
			}
			return convertView;
		}

		class Holder {
			public ImageView iconView;
			public TextView textView;
			public Button button;
			public CheckBox box;

		}

	}

}

这里一切都很简单,并没有什么要注意的地方。只是我把之前做的一个功能:关于获取本机app的知识点整合进来了。如果对这个知识点有兴趣点击: PackageManager获取指定类别应用程序






你可能感兴趣的:(Android)