In my Android application, my goal I thought would be very simple - To generate a list of installed applications and place a tick box along side each, acting as a 'tick to exclude' list.
To generate the list of installed applications, I'm using the standard Android example code, demonstrated inside a fragment here. I won't repost it all to keep this post as concise as possible.
The performance is terrible and my first question on this subject would be requesting example code that LazyLoads the application icons. The implementation of LazyLoading icons into a ListView appears to only be a concern when the images are being downloaded. Since Android does not use this method when generating a list of applications, then I'm wondering if this is therefore overkill?
The problems start when a CheckedTextView is checked and as the views are recycled in the list, further boxes become ticked down the list (out of the initial view) or they 'forget' they have been ticked.
To combat this problem, I had to keep a reference to which items were ticked and use the following code in getView()
// store CheckTextView's
private static HashMap<Integer, CheckedTextView> mCheckedList = new HashMap<Integer, CheckedTextView>();
// store state
private static HashMap<Integer, Boolean> mIsChecked = new HashMap<Integer, Boolean>();
@Override
public View getView(final int position, View convertView, final ViewGroup parent) {
View view;
if (convertView == null) {
view = mInflater.inflate(R.layout.list_item_icon_text, parent, false);
} else {
view = convertView;
}
final AppEntry item = getItem(position);
((ImageView) view.findViewById(R.id.icon)).setImageDrawable(item.getIcon());
CheckedTextView ctv = (CheckedTextView) view.findViewById(R.id.text1);
ctv.setText(item.getLabel());
// set current state
if (mIsChecked.get(position) != null) {
if (mIsChecked.get(position)) {
ctv.setChecked(true);
}
} else {
ctv.setChecked(false);
}
ctv.setTag(position);
mCheckedList.put(position, ctv);
ctv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
CheckedTextView ct = mCheckedList.get(view.getTag());
if (DE.BUG) {
MyLog.d("ct text: " + ct.getText().toString());
}
ct.toggle();
mIsChecked.put((Integer) view.getTag(), ct.isChecked());
}
});
return view;
}
}
That works, but the performance is terrible due to the work done for each view being refreshed/recycled and the OnClickListener placed on each item (more on this below) - Eclipse also tells me there's more something else to change:
Use new SparseArray(...) instead for better performance
My woes don't end there though.. to help the user get to the application they want quickly, I implemented a filter as follows:
@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Place an action bar item for searching.
MenuItem item = menu.add("Search");
item.setIcon(android.R.drawable.ic_menu_search);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
View searchView = SearchViewCompat.newSearchView(getActivity());
if (searchView != null) {
SearchViewCompat.setOnQueryTextListener(searchView,
new OnQueryTextListenerCompat() {
@Override
public boolean onQueryTextChange(String newText) {
// Called when the action bar search text has changed. Since this
// is a simple array adapter, we can just have it do the filtering.
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
mAdapter.getFilter().filter(mCurFilter);
return true;
}
});
item.setActionView(searchView);
}
}
I assume when a filter is typed in, the ListView is redrawn and the references to positions become messed up? This results in boxes becoming ticked based on their position on the display.
I had to implement an OnClickListener for each entry above, as I cannot get a reference to the CheckedTextView from onListItemClick. Here are some of my many attempts:
@Override
public void onListItemClick(final ListView listView, final View view, final int position, final long id) {
// View v = (View) listView.getChildAt(position);
// CheckedTextView ctv = (CheckedTextView)
// v.findViewById(R.id.text1);
// ctv.toggle();
// RelativeLayout r = (RelativeLayout) view;
// CheckedTextView ctv = (CheckedTextView)
// r.findViewById(R.id.text1);
// CheckedTextView ctv = (CheckedTextView) view;
// ((CheckedTextView)
// listView.getItemAtPosition(position)).setChecked(!((CheckedTextView)
// listView
// .getItemAtPosition(position)).isChecked());
// CheckedTextView ctv = (CheckedTextView) view.getTag(position);
// ctv.toggle();
As @CommonsWare replied in this topic, the CheckedTextView reference to findViewById(R.id.text1)
is not what I'm after.
EDIT - XML Layout
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingRight="6dip" >
<CheckedTextView
android:id="@+id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/icon"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:paddingLeft="4dip"
android:paddingTop="4dip"
android:textStyle="bold"
android:duplicateParentState="true" />
<ImageView
android:id="@+id/icon"
android:layout_width="48dip"
android:layout_height="48dip"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:paddingLeft="2dip" />
</RelativeLayout>
I'm about ready to give up and implement my own layout with a separate text view and check box, but I can't help thinking I'll be reinventing the wheel if I do that? Am I making this much harder than it should be!?
In an ideal world:
- A list of installed applications that LazyLoad the application icons.
- A reference to the actual CheckedTextView from the onListItemClick.
- The correct way to store and reference which items have been checked.
I hope someone can help and I thank you in advance.