本来不想写这篇博客,原因是不想被几个月以后的自己鄙视,其实很容易的,可是又有几个细节要注意,还遇上了几个不那么难的bug,但是不好描述,也解释不清楚,也不好咨询别人的问题。所以我决心还是记录一下。(注意一下所有出现Qrom,qrom词的都可以把qrom去掉,因为qrom是我引入的第三方控件库,对原生的控件进行了重新封装,效果更好而已,大家也不要问我要,这个是公司产品,我也不能给你们,但这并不影响你们理解博客)
1. 2.
CheckedTextView基本版如图1,实现步骤如下:
item的布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<CheckedTextView
android:id="@+id/checktv_title"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:gravity="center_vertical"
android:paddingLeft="6dip"
android:paddingRight="6dip"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
代码:
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<Boolean> checkedMap = new SparseArray<Boolean>();
@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<String> {
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<String> datas;
ListView listView;
ActionMode mActionMode;
MyAdapter myAdapter;
@SuppressLint("UseSparseArrays")
private SparseArray<Boolean> 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<Boolean> mapsFromDatas(ArrayList<String> strings) {
SparseArray<Boolean> 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<Boolean> 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<String> listFromStrings(String[] strings) {
ArrayList<String> arrayList = new ArrayList<String>();
for (int i = 0; i < strings.length; i++) {
arrayList.add(strings[i]);
}
return arrayList;
}
private class MyAdapter extends ArrayAdapter<String> {
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模式
先看效果图吧,有图更比说很多话就管用
在ActionMode下切换到
先分析下思路(这里换成了CheckBox,跟CheckedTextView使用基本一样,请忽略细节)。
1,item.xml中同时设置Button和CheckBox为alignRight,在ActionMode出现和释放阶段通过刷新adapter设置切换Button和CheckBox一个visibility,另一个gone。这么一分析就很简单的。
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:descendantFocusability="blocksDescendants"
android:orientation="horizontal" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/app_name"
android:src="@drawable/ic_launcher" />
<TextView
android:layout_marginLeft="5dp"
android:id="@+id/textView1"
android:layout_width="200dp"
android:layout_height="48dp"
android:gravity="center" />
<Button
android:id="@+id/button1"
android:layout_width="96dp"
android:layout_height="42dp"
android:layout_alignParentRight="true"
android:visibility="gone" />
<com.tencent.qrom.widget.CheckBox
android:id="@+id/select_check"
android:layout_width="wrap_content"
android:layout_height="42dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="42dp"
android:clickable="false"
android:focusable="false"
android:visibility="visible" >
</com.tencent.qrom.widget.CheckBox>
</RelativeLayout>
代码:
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<PackageInfo> list = new ArrayList<>();;
onItemSelectedListener mListener;
PackageManager packageManager;
private SparseArray<Boolean> 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<PackageInfo> 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<Boolean> 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<Boolean> mapsFromDatas(List<PackageInfo> list2) {
SparseArray<Boolean> maps = new SparseArray<>();
for (int i = 0; i < list2.size(); i++) {
maps.put(i, false);
}
return maps;
}
private List<PackageInfo> getApplications(List<PackageInfo> mlist, int flag) {
List<PackageInfo> l = new ArrayList<PackageInfo>();
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获取指定类别应用程序