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.
Related
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"
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;
}
}
Since PreferenceFragment is not available in the support library I created a ListFragment to show a list of settings to a user since I don't have a lot of settings to show. I also created a custom ArrayAdapter to customize the list items. I need to handle when a user checks one of the CheckBoxs so I can save weather or not it was checked. So if its checked then it will stay checked until the user uncheckes it. This would be much easier if there was only one setting in the list but there are 2 for now and I might need to add more. So I need to be able to determine which one was checked. I can handle the check and uncheck fine I just cant find a way to determine which one was checked.
THE CODE
Here is my list item:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="horizontal">
<TextView android:id="#+id/pref_edit_text" android:layout_width="0dp" android:layout_height="30dp"
android:layout_weight="5"/>
<CheckBox android:id="#+id/pref_check_box" android:layout_width="0dp" android:layout_height="wrap_content"
android:layout_weight="1" android:onClick="onCheckBoxClick"/>
</LinearLayout>
<TextView android:id="#+id/pref_edit_text2" android:layout_width="match_parent"
android:layout_height="50dp"/>
Here is getView() in my adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//mIntCheckBoxPosition = position;
Typeface tf = Typeface.createFromAsset(mMainActivity.getAssets(), "fonts/ArchitectsDaughter.ttf");
LayoutInflater inflater = mMainActivity.getLayoutInflater();
View view = inflater.inflate(mIntLayoutId, parent, false);
TextView text = (TextView) view.findViewById(R.id.pref_edit_text);
text.setText(mStringArrayTitle[position]);
text.setTypeface(tf, Typeface.BOLD);
text = (TextView) view.findViewById(R.id.pref_edit_text2);
text.setText(mStringArraySubTitle[position]);
text.setTypeface(tf);
mMainActivity.setTitle("Settings");
return view;
}
And here is where I handle when a CheckBox is clicked which is in the ListFragment:
public void onCheckBoxClick(View view) {
boolean isChecked = ((CheckBox) view).isChecked();
Editor editor = mMainActivity.getSharedPreferences(PREF_KEY_CHECK_BOX, Activity.MODE_PRIVATE).edit();
switch (view.getId()) {
case R.id.check_box :
if (isChecked) {
editor.putBoolean(PREF_KEY_ROUNDING, true).commit();
}
else {
editor.putBoolean(PREF_KEY_ROUNDING, false).commit();
}
break;
}
}
}
Here is what my settings look like:
WHAT I HAVE TRIED
1. I have tried setting a variable in the adapter to the item position and using a getter to get the position, but that only returns the position of the last item shown.
2. I have tried using some of the methods in ListFragment to get the position of the CheckBox but they always return -1.
3. I have done a lot of Googling and searching on SO but I have not been able to find a solution to get this to work.
So if anyone knows of a way I can get the position of the CheckBox or any other ways I might be able to tell which one was clicked I will be eternally grateful.
You can use setTag to add an int to the view indicating its position, and then retreive that later using getTag. Ex:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
... // your other code here
CheckBox checkbox = (CheckBox) view.findViewById(R.id.pref_check_box);
checkbox.setTag(new Integer(position));
}
Then, in your onCheckBoxClick:
public void onCheckBoxClick(View view) {
Integer pos;
pos = (Integer) view.getTag();
... // do what you want with `pos` here
}
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;
}
}
I've seen example com.example.android.apis.view.List11 from ApiDemos. In that example, each row takes the view android.R.simple_list_item_multiple_choice. Each such view has a TextView and a CheckBox.
Now I want each view to have 2 TextViews and 1 CheckBox, somewhat similar to the List3 example. I tried creating a custom layout file row.xml like this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<CheckBox
android:id="#+id/checkbox"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="fill_parent" />
<TextView
android:id="#+id/text_name"
android:textSize="13px"
android:textStyle="bold"
android:layout_toLeftOf="#id/checkbox"
android:layout_alignParentLeft="true"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/text_phone"
android:textSize="9px"
android:layout_toLeftOf="#id/checkbox"
android:layout_below="#id/text_name"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
Then in Activity's onCreate(), I do like this:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Query the contacts
mCursor = getContentResolver().query(Phones.CONTENT_URI, null, null, null, null);
startManagingCursor(mCursor);
ListAdapter adapter = new SimpleCursorAdapter(this,
R.layout.row,
mCursor,
new String[] { Phones.NAME, Phones.NUMBER},
new int[] { R.id.text_name, R.id.text_phone });
setListAdapter(adapter);
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
}
The result kind of looks like what I want, but it looks like the list doesn't know which item of it is selected. Also, I need to click exactly on the CheckBox. In the List11 example, I only need to click on the item row.
So what do I need to do to make a multiple choice list with my custom view for each row? Many thanks.
You have to make your own RelativeLayout that implements the Checkable interface and have a reference to the CheckBox or to the CheckedTextView (or a list if it's multiple choice mode).
Look at this post:
http://www.marvinlabs.com/2010/10/29/custom-listview-ability-check-items/
The answer of Rahul Garg is good for the first time the list is loaded, if you want some rows to be checked depending on the model data, but after that you have to handle the check/uncheck events by yourself.
You can override the onListItemCLick() of the ListActivity to check/uncheck the rows
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
ViewGroup row = (ViewGroup)v;
CheckBox check = (CheckBox) row.findViewById(R.id.checkbox);
check.toggle();
}
If you do so, do not set the ListView to CHOICE_MODE_MULTIPLE, because it makes strange things when calling the function.
To retrieve the list of checked rows, you have to implement a method yourself, calling getCheckItemIds() on the ListView does not work:
ListView l = getListView();
int count = l.getCount();
for(int i=0; i<count; ++i) {
ViewGroup row = (ViewGroup)l.getChildAt(i);
CheckBox check = (Checked) row.findViewById(R.id.ck1);
if( check.isChecked() ) {
// do something
}
}
Each such view has a TextView and a
CheckBox.
No, it doesn't. It has a CheckedTextView.
So what do I need to do to make a
multiple choice list with my custom
view for each row?
Try making the CheckBox android:id value be "#android:id/text1" and see if that helps. That is the ID used by Android for the CheckedTextView in simple_list_item_multiple_choice.
The solution is to create a custom View that implements the Clickable interface.
public class OneLineCheckableListItem extends LinearLayout implements Checkable {
public OneLineCheckableListItem(Context context, AttributeSet attrs) {
super(context, attrs);
}
private boolean checked;
#Override
public boolean isChecked() {
return checked;
}
#Override
public void setChecked(boolean checked) {
this.checked = checked;
ImageView iv = (ImageView) findViewById(R.id.SelectImageView);
iv.setImageResource(checked ? R.drawable.button_up : R.drawable.button_down);
}
#Override
public void toggle() {
this.checked = !this.checked;
}
}
And create a custom layout for the list items using the new widget.
<?xml version="1.0" encoding="utf-8"?>
<ax.wordster.OneLineCheckableListItem xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:background="#drawable/selector_listitem"
android:orientation="horizontal" >
<ImageView
android:id="#+id/SelectImageView"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="#drawable/button_friends_down" />
<TextView
android:id="#+id/ItemTextView"
android:layout_width="fill_parent"
android:layout_height="60dp"
android:gravity="center"
android:text="#string/___"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="#color/text_item" />
</ax.wordster.OneLineCheckableListItem>
Then create a new custom Adapter using the layout above.
It is possible by some trick
in your ListActivtyClass in method
protected void onListItemClick(ListView l, View v, int position, long id) {
//just set
<your_model>.setSelected(true);
}
now in you custom Adapter
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(textViewResourceId, parent, false);
}
if (<your_model>.isSelected()) {
convertView.setBackgroundColor(Color.BLUE);
} else {
convertView.setBackgroundColor(Color.BLACK);
}
return convertView;
}
this way you can customize the view in adapter when the item is selected in the list.
Simple example how to get a custom layout to work as custom checkbox:
private class FriendsAdapter extends ArrayAdapter<WordsterUser> {
private Context context;
public FriendsAdapter(Context context) {
super(context, R.layout.listitem_oneline);
this.context = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final int pos = position;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rv = inflater.inflate(R.layout.listitem_oneline, parent, false);
rv.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
boolean checked = friendsListView.isItemChecked(pos);
friendsListView.setItemChecked(pos, !checked);
}
});
WordsterUser u = getItem(position);
TextView itw = (TextView) rv.findViewById(R.id.ItemTextView);
itw.setText(u.userName + " (" + u.loginName + ")");
ImageView iv = (ImageView) rv.findViewById(R.id.SelectButton);
if (friendsListView.isItemChecked(position)) {
iv.setImageResource(R.drawable.downbutton);
} else {
iv.setImageResource(R.drawable.upbutton);
}
return rv;
}
}
I found it very useful this little code: http://alvinalexander.com/java/jwarehouse/apps-for-android/RingsExtended/src/com/example/android/rings_extended/CheckableRelativeLayout.java.shtml
It is a great addition to #ferdy182 's http://www.marvinlabs.com/2010/10/29/custom-listview-ability-check-items/ content.
Got the solution ... You can get the clicks on the views (like checkboxes in custom layouts of row) by adding listener to each of them in the adapter itself while you return the converted view in getView(). You may possibly have to pass a reference of list object if you intent to get any list specific info. like row id.
I want to confirm that the Pritam's answer is correct. You need an onClickListener on each list's item (define it in the adapter's getView()).
You can create a new onClickListener() for each item, or have the adapter implement onClickListener() - in this case the items must be tagged for the listener to know, which item it is operating on.
Relying on the list onItemClickListener() - as someone advised in another thread - will not work as the CheckBox will intercept the click event so the list will not get it.
And finally #Rahul and JVitella:
The situation is that the CheckBox on a list item must be clickable and checkable independently from the list item itself. Therefore the solution is as I just described above.