ArrayAdapter custom view state gets repeated when scrolling - android

I have a ListView that gets populated with custom view items. The custom view consists of an icon, a label and a checkbox. When I first create the list, everything displays as it should. If I scroll down the list, the icons and labels continue to be correct further down the list but the checkbox states start to get mixed up, displaying other items as checked besides the ones I chose.
Example: My list starts with no checkboxes set as checked for any items. I see 10 items on screen. I toggle the checkbox on item 10. It updates appropriately. If I scroll down the list, I find that the checkbox for item 20, item 30, etc. start with the checkbox already toggled even though they were never visible to interact with. If I scroll back and forth repeatedly, more and more items in a non-identifiable pattern appear checked.
List setup in my activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.default_list);
profile = (Profile) i.getParcelableExtra("profile");
ArrayList<Application> apps = new ApplicationListRetriever(this).getApplications(true);
adapter = new ApplicationsAdapter(this, R.layout.application_list_item, apps, getPackageManager(), profile);
setListAdapter(adapter);
}
ApplicationsAdapter:
public class ApplicationsAdapter extends ArrayAdapter<Application> {
private ArrayList<Application> items;
private PackageManager pm;
private Profile profile;
private ArrayList<ApplicationListener> listeners = new ArrayList<ApplicationListener>();
public ApplicationsAdapter(Context context, int textViewResourceId,
ArrayList<Application> objects, PackageManager pm, Profile profile) {
super(context, textViewResourceId, objects);
this.pm = pm;
items = objects;
this.profile = profile;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater li = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = li.inflate(R.layout.application_list_item, null);
}
final Application info = items.get(position);
if (info != null) {
TextView text = (TextView) v.findViewById(R.id.label);
CheckBox check = (CheckBox) v.findViewById(R.id.check);
ImageView img = (ImageView) v.findViewById(R.id.application_icon);
//see if the app already is associated and mark checkbox accordingly
for (Application app : profile.getApps()) {
if (info.getPackageName().equals(app.getPackageName())) {
check.setChecked(true);
break;
}
}
check.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
for (ApplicationListener listener : listeners) {
listener.applicationReceived(info, isChecked);
}
}
});
try {
text.setText(info.getName());
}
catch (Exception ex) {
Log.e("ApplicationsAdapter", "Label could not be set on adapter item", ex);
}
if (img != null) {
try {
img.setImageDrawable(pm.getApplicationIcon(info.getPackageName()));
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
}
return v;
}
}
List item layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="wrap_content">
<ImageView android:id="#+id/application_icon"
android:layout_centerVertical="true" android:layout_alignParentLeft="true"
android:layout_width="36dp" android:layout_height="36dp" android:paddingRight="3dp" android:adjustViewBounds="true" />
<TextView android:layout_width="wrap_content"
android:layout_centerVertical="true" android:layout_toRightOf="#+id/application_icon"
android:layout_height="wrap_content" android:id="#+id/label"
android:text="Application Name" />
<CheckBox android:id="#+id/check"
android:layout_centerVertical="true" android:layout_alignParentRight="true"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="right" />
</RelativeLayout>
Also worth noting is if I set a breakpoint on the line where I call check.setChecked(true); it only hits that point if the original item I checked is on screen, never for any of the other items that display as checked.
Any ideas why the later items would display as checked or what I can try to fix it?

It is caused by View recycling. You should completely refresh the state of your View when getView is called. Although you are refreshing almost everything, you've forgotten one little thing.
Suppose the list shows 5 items on screen and the second item is checked. The user then scrolls down a further 5 items - however due to view recycling they're really just the same 5 views as before the user scrolled, so the one of the items on-screen will still be checked even though it shouldn't be, because in your code above if no package is matched you are not setting the checkbox to unchecked (so it will stay checked), you are assuming it is already unchecked (which due to View recycling it may not be).
The fix is simple: simply set the checkbox to unchecked before your logic to check if it need be checked:
// Do not assume the checkbox is unchecked to begin with, it might not be
// if this view was recycled, so force it to be unchecked by default and only
// check it if needed.
check.setChecked(false);
for (Application app : profile.getApps()) {
if (info.getPackageName().equals(app.getPackageName())) {
check.setChecked(true);
break;
}
}

Related

android listview's click event is not applied after changing visibility of component in list's item

In my listview's item, there is a two components.
<TextView
android:id="#+id/owner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="방장"
android:layout_centerInParent="true"
android:textColor="#color/colorBlack"
android:textSize="10dp"/>
<ImageButton
android:id="#+id/kickOutBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:visibility="invisible"
android:scaleType="fitCenter"
android:background="#color/colorTransparent"
android:tint="#color/colorBlack"
android:src="#drawable/ic_baseline_close_24"/>
These are in a same location in a view.
I mean, the Textview is located in the center of the ImageButton.
And if the ListView item's target is a manager, I want to make its list item's ImageButton invisible and open the Textview that says manager, and if not, I want to make its list item's ImageButton show the opposite, and I want to make the Textview that says manager invisible. (default is that imagebutton is invisible and textview is visible)
To do that, I tried to this code.
// getView in ListAdapter
#Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
View view;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.navi_list_menu_item, null);
}
else {
view = convertView;
}
TextView supervisor_tv = (TextView) view.findViewById(R.id.owner);
ImageButton kick_btn = (ImageButton) view.findViewById(R.id.kickOutBtn);
if(!mNavItems.get(i).fb_uid.equals(roomSupervisor)) {
kick_btn.setVisibility(View.VISIBLE);
kick_btn.setOnClickListener(v -> {
Log.d("ChatRoomActivity", "click imagebutton");
});
supervisor_tv.setVisibility(View.INVISIBLE);
}
return view;
}
// Activity
mDrawerList = (ListView) findViewById(R.id.nav_list);
mDrawerList.addHeaderView(listHeaderView);
NaviDrawerListAdapter adapter = new NaviDrawerListAdapter(this, mNavItems, roomSupervisor);
mDrawerList.setAdapter(adapter);
mDrawerList.setOnItemClickListener((adapterView, view, position, id) -> {
Log.d(TAG, String.valueOf(position));
});
It is ok to click manager's list item and ImageButton of others.
But the problem is that I cannot click non-manager user's list item.
Could you tell me what is the problem of my code?
By googling, I found the answer.
It is setFocusable and setFocusableInTouchMode
I revised my Adapter code like this.
if(!mNavItems.get(i).fb_uid.equals(roomSupervisor)) {
kick_btn.setVisibility(View.VISIBLE);
kick_btn.setOnClickListener(v -> {
Log.d("ChatRoomActivity", "click a kick btn");
});
kick_btn.setFocusable(false);
kick_btn.setFocusableInTouchMode(false);
supervisor_tv.setVisibility(View.INVISIBLE);
}
Then it works well!

OnItemClickListener - changing the click area [duplicate]

So I have a custom ListView object. The list items have two textviews stacked on top of each other, plus a horizontal progress bar that I want to remain hidden until I actually do something. To the far right is a checkbox that I only want to display when the user needs to download updates to their database(s). When I disable the checkbox by setting the visibility to Visibility.GONE, I am able to click on the list items. When the checkbox is visible, I am unable to click on anything in the list except the checkboxes. I've done some searching but haven't found anything relevant to my current situation. I found this question but I'm using an overridden ArrayAdapter since I'm using ArrayLists to contain the list of databases internally. Do I just need to get the LinearLayout view and add an onClickListener like Tom did? I'm not sure.
Here's the listview row layout XML:
<?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="?android:attr/listPreferredItemHeight"
android:padding="6dip">
<LinearLayout
android:orientation="vertical"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="fill_parent">
<TextView
android:id="#+id/UpdateNameText"
android:layout_width="wrap_content"
android:layout_height="0dip"
android:layout_weight="1"
android:textSize="18sp"
android:gravity="center_vertical"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:id="#+id/UpdateStatusText"
android:singleLine="true"
android:ellipsize="marquee"
/>
<ProgressBar android:id="#+id/UpdateProgress"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:indeterminateOnly="false"
android:progressDrawable="#android:drawable/progress_horizontal"
android:indeterminateDrawable="#android:drawable/progress_indeterminate_horizontal"
android:minHeight="10dip"
android:maxHeight="10dip"
/>
</LinearLayout>
<CheckBox android:text=""
android:id="#+id/UpdateCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
And here's the class that extends the ListActivity. Obviously it's still in development so forgive the things that are missing or might be left laying around:
public class UpdateActivity extends ListActivity {
AccountManager lookupDb;
boolean allSelected;
UpdateListAdapter list;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lookupDb = new AccountManager(this);
lookupDb.loadUpdates();
setContentView(R.layout.update);
allSelected = false;
list = new UpdateListAdapter(this, R.layout.update_row, lookupDb.getUpdateItems());
setListAdapter(list);
Button btnEnterRegCode = (Button)findViewById(R.id.btnUpdateRegister);
btnEnterRegCode.setVisibility(View.GONE);
Button btnSelectAll = (Button)findViewById(R.id.btnSelectAll);
btnSelectAll.setOnClickListener(new Button.OnClickListener() {
#Override
public void onClick(View v) {
allSelected = !allSelected;
for(int i=0; i < lookupDb.getUpdateItems().size(); i++) {
lookupDb.getUpdateItem(i).setSelected(!lookupDb.getUpdateItem(i).isSelected());
}
list.notifyDataSetChanged();
// loop through each UpdateItem and set the selected attribute to the inverse
} // end onClick
}); // end setOnClickListener
Button btnUpdate = (Button)findViewById(R.id.btnUpdate);
btnUpdate.setOnClickListener(new Button.OnClickListener() {
#Override
public void onClick(View v) {
} // end onClick
}); // end setOnClickListener
lookupDb.close();
} // end onCreate
#Override
protected void onDestroy() {
super.onDestroy();
for (UpdateItem item : lookupDb.getUpdateItems()) {
item.getDatabase().close();
}
}
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
UpdateItem item = lookupDb.getUpdateItem(position);
if (item != null) {
item.setSelected(!item.isSelected());
list.notifyDataSetChanged();
}
}
private class UpdateListAdapter extends ArrayAdapter<UpdateItem> {
private List<UpdateItem> items;
public UpdateListAdapter(Context context, int textViewResourceId, List<UpdateItem> items) {
super(context, textViewResourceId, items);
this.items = items;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = null;
if (convertView == null) {
LayoutInflater li = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = li.inflate(R.layout.update_row, null);
} else {
row = convertView;
}
UpdateItem item = items.get(position);
if (item != null) {
TextView upper = (TextView)row.findViewById(R.id.UpdateNameText);
TextView lower = (TextView)row.findViewById(R.id.UpdateStatusText);
CheckBox cb = (CheckBox)row.findViewById(R.id.UpdateCheckBox);
upper.setText(item.getName());
lower.setText(item.getStatusText());
if (item.getStatusCode() == UpdateItem.UP_TO_DATE) {
cb.setVisibility(View.GONE);
} else {
cb.setVisibility(View.VISIBLE);
cb.setChecked(item.isSelected());
}
ProgressBar pb = (ProgressBar)row.findViewById(R.id.UpdateProgress);
pb.setVisibility(View.GONE);
}
return row;
}
} // end inner class UpdateListAdapter
}
edit: I'm still having this problem. I'm cheating and adding onClick handlers to the textviews but it seems extremely stupid that my onListItemClick() function is not being called at all when I am not clicking on my checkbox.
The issue is that Android doesn't allow you to select list items that have elements on them that are focusable. I modified the checkbox on the list item to have an attribute like so:
android:focusable="false"
Now my list items that contain checkboxes (works for buttons too) are "selectable" in the traditional sense (they light up, you can click anywhere in the list item and the "onListItemClick" handler will fire, etc).
EDIT: As an update, a commenter mentioned "Just a note, after changing the visibility of the button I had to programmatically disable the focus again."
In case you have ImageButton inside the list item you should set the descendantFocusability value to 'blocksDescendants' in the root list item element.
android:descendantFocusability="blocksDescendants"
And the focusableInTouchMode flag to true in the ImageButton view.
android:focusableInTouchMode="true"
I've had a similar issue occur and found that the CheckBox is rather finicky in a ListView. What happens is it imposes it's will on the entire ListItem, and sort of overrides the onListItemClick. You may want to implement a click handler for that, and set the text property for the CheckBox as well, instead of using the TextViews.
I'd say look into this View object as well, it may work better than the CheckBox
Checked Text View
use this line in the root view of the list item
android:descendantFocusability="blocksDescendants"

Spinner Title shows its Zero position element

I am using Spinner in one of my activity. Problem is that it shows its zero index element as title. because of this it appears twice, first in title and second is as first element of spinner. I don't want to give the selected option Which is in title in spinner drop down because its already selected So whats the use of it to give it as spinner first option. I want the selected option in title and rest of the option in Spinner list. Have a look at my code -
<?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:gravity="center_horizontal|center_vertical"
android:orientation="horizontal" >
<Spinner
android:id="#+id/spinner"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="center_horizontal|center_vertical" />
</LinearLayout>
and adapter class is -
public class SpinnerAdapter extends BaseAdapter {
Context mContext;
List<SpinnerContent> list;
public SpinnerAdapter(Context context, List<SpinnerContent> list) {
mContext = context;
this.list = list;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
SpinnerContent item = list.get(position);
if (convertView == null) {
LayoutInflater inflator = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflator.inflate(R.layout.spinner_item_row_image,
null);
}
TextView tvTitle = (TextView) convertView
.findViewById(R.id.tvSpinnerItem);
ImageView imgSpinnerContent = (ImageView) convertView
.findViewById(R.id.imgSpinnerItem);
if (item.getCollectionName().equalsIgnoreCase("Home")) {
imgSpinnerContent.setVisibility(View.VISIBLE);
imgSpinnerContent.setImageResource(R.drawable.icon_home);
tvTitle.setText("Home");
tvTitle.setTextColor(mContext.getResources().getColor(
R.color.white_text));
convertView.setBackgroundColor(mContext.getResources().getColor(
R.color.text_color_light_gray));
} else if (item.getCollectionName().equalsIgnoreCase("One Level Up")) {
imgSpinnerContent.setVisibility(View.VISIBLE);
imgSpinnerContent.setImageResource(R.drawable.icon_spinner_up);
tvTitle.setText("One Level Up");
tvTitle.setTextColor(mContext.getResources().getColor(
R.color.white_text));
convertView.setBackgroundColor(mContext.getResources().getColor(
R.color.text_color_light_gray));
} else if (item.getCollectionName().equalsIgnoreCase("One Level Down")) {
imgSpinnerContent.setVisibility(View.VISIBLE);
imgSpinnerContent.setImageResource(R.drawable.icon_spinner_down);
tvTitle.setText("One Level Down");
tvTitle.setTextColor(mContext.getResources().getColor(
R.color.white_text));
convertView.setBackgroundColor(mContext.getResources().getColor(
R.color.text_color_light_gray));
} else {
if (position == 0) {
convertView.setBackgroundColor(mContext.getResources()
.getColor(android.R.color.transparent));
imgSpinnerContent.setVisibility(View.GONE);
tvTitle.setText(item.getCollectionName());
tvTitle.setTextColor(mContext.getResources().getColor(
R.color.white_text));
} else {
tvTitle.setText(item.getCollectionName());
applyTheme(convertView, mContext);
}
}
return convertView;
}
How can I achieve it. I want first element of list (Which is passed to spinner) as title but don't want to show it as first element of spinner.
Thanks in advance.
Its not possible with spinner. Spinner always shows the first element as title. If you want to add title then you better display your title at 0th position. In validation part if user selects 0th position set validation message to user.
I think you should use Kevin Chris solution.
Still if you want to achieve then try following trick.I never used this but it should work.
spinner.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
View convertView = spinner.getSelectedView();
View selectedView= adapter.getDropDownView(selectedPosition, convertView, spinner);
selectedView.setVisibility(View.GONE);
}
});

CheckBox in ListView being reset when it leaves the screen

I have followed the tutorial here to create a custom ListView that shows items with category headers. I have modified the list_item_entry.xml to put a CheckBox in the item:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize" >
<CheckBox
android:id="#+id/option_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="6dp"
android:focusable="false"
android:clickable="false" />
<TextView
android:id="#+id/list_item_entry_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
</LinearLayout>
My problem is that if I check some of the CheckBoxes then scroll them off the screen, when they come back they are unchecked. However listView.getCheckedItemPositions() still shows that the item is checked.
I'm pretty sure that my problem is with the getView() method in my custom ArrayAdapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final Item i = items.get(position);
if (i != null) {
if(i.isSection()){
SectionItem si = (SectionItem)i;
convertView = vi.inflate(R.layout.list_item_section, parent, false);
convertView.setOnClickListener(null);
convertView.setOnLongClickListener(null);
convertView.setLongClickable(false);
final TextView sectionView =
(TextView) convertView.findViewById(R.id.list_item_section_text);
sectionView.setText(si.getTitle());
}else{
EntryItem ei = (EntryItem)i;
convertView = vi.inflate(R.layout. list_item_entry, parent, false);
final TextView title =
(TextView) convertView.findViewById(R.id.list_item_entry_title);
if (title != null)
title.setText(ei.getTitle());
}
}
return convertView;
}
I think that I have two issues here, though I have no idea how to solve either:
Using vi.inflate every time is causing android to constantly create views which is bad (not sure about this). I tried to only inflate it if convertView == null but then sometimes convertView would be in the wrong format, ie. List_item_section when it should be List_item_entry. Is it fine to inflate it everytime?
I think that inflating the view each time is causing the CheckBoxes to be reset, although I may be wrong about this.
So how do I make it so the CheckBoxes will stay checked when the leave and return to the screen? And will this method fill Android's memory with Views if the the list is sufficiently long?
Update:
I liked #user3815165's answer because I didn't need to store the checked value for a sectionItem which doesn't have a checkbox. But as I mentioned in a comment, since the items list is not in the context of the Activity then the values of whether each EntryItem is checked or not persists when the view is destroyed and creates bugs.
So I decided to go with #Palash's answer, even though it stored data not needed (only a single boolean value for each SectionItem in the list). It works perfectly.
you need to maintain a status array of type boolean in your activity, pass that array into your list adapter and while setting the checkbox check status of that position, also you need to update that status array likewise on click event of checkbox.
try this you will get the desired output.
//While Setting the checkbox in adapter
if(bStatus[position]==false)
{
itemSet.chSelectItem.setChecked(false);
}else if(bStatus[position]==true)
{
itemSet.chSelectItem.setChecked(true);
}
In your main Activity
//initilize Arraylist in main Activity
boolean[] bStatus;
bStatus = new boolean[BeanArray.size()];
Arrays.fill(bStatus, false);
MyAdapter adapter = new MyAdapter(this, BeanArray, bStatus);
listView.setAdapter(adapter);
class Item{
boolean isSection;
String title;
boolean isOptionChecbox;
//your getter/setter
#Override
public String toString() {
return title;
}
}
you Adapter:
public class listAdapter extends ArrayAdapter<Item> {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final Item i = items.get(position);
if(i.isSection()){
convertView = vi.inflate(R.layout.list_item_section, parent, false);
convertView.setOnClickListener(null);
convertView.setOnLongClickListener(null);
convertView.setLongClickable(false);
final TextView sectionView = (TextView) convertView.findViewById(R.id.list_item_section_text);
sectionView.setText(si.getTitle());
} else{
convertView = vi.inflate(R.layout. list_item_entry, parent, false);
final TextView title = (TextView) convertView.findViewById(R.id.list_item_entry_title);
if (title != null) title.setText(ei.getTitle());
CheckBox optionCheckbox = (CheckBox) convertView.findViewById(R.id.option_checkbox);
optionCheckbox.setChecked(ei.isOptionCheckbox());
optionCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
item.setOptionCheckbox(b);
}
});
}
return convertView;
}
}

ListView CheckedTextView

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.

Categories

Resources