How to do put a CheckBox in a ListView? - android

I have a listview with a custon adapter. I the row's layout, I have a text and a checkbox.
When I load the listview, I get the data from a database and it has one colunm that determine if the row is cheched or not. When I load the list, its ok, the rows that has to stay checked, stays checkd, and the others no. The problem is: when I unckheck a row ans roll the list down and up, when I return to the start, the row that I had unchecked, returns checked again, how can I resold this problem:
The getView() code below:
public View getView(int index, View view, ViewGroup parent) {
if (view == null) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
view = inflater.inflate(R.layout.linha_acessorios, parent, false);
}
final AcessoriosItensLista acessorios = (AcessoriosItensLista)getItem(index);
final ImageView imgAcessorio = (ImageView)view.findViewById(R.id.imgAcessorioLista);
final CheckBox cb = (CheckBox)view.findViewById(R.id.cbListaAcessorios);
TextView tvNome = (TextView) view.findViewById(R.id.tvNomeAcessoriosLinha);
tvNome.setText(acessorios.getNomeAcessorio());
final Integer iditem = Integer.valueOf(acessorios.getId());
boolean ch = acessorios.isChecked();
final Integer position = Integer.valueOf(index);
if(ch){
if(!checked.contains(iditem)){
checkedPositions.add(position);
checked.add(iditem);
}
}
cb.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if(checked.contains(iditem)){
checked.remove(iditem);
checkedPositions.remove(position);
}
if (((CheckBox) v).isChecked()) {
checkedPositions.add(position);
checked.add(iditem);
int id = context.getResources().getIdentifier("acc_gold_"+acessorios.getId(), "drawable", context.getPackageName());
imgAcessorio.setBackgroundResource(id);
}
else if(checkedPositions.contains(position)) {
checkedPositions.remove(position);
checked.remove(iditem);
int id = context.getResources().getIdentifier("acc_"+acessorios.getId(), "drawable", context.getPackageName());
imgAcessorio.setBackgroundResource(id);
}
}
});
if(checkedPositions.contains(position)){
cb.setChecked(true);
int id = context.getResources().getIdentifier("acc_gold_"+acessorios.getId(), "drawable", context.getPackageName());
imgAcessorio.setBackgroundResource(id);
} else {
cb.setChecked(false);
int id = context.getResources().getIdentifier("acc_"+acessorios.getId(), "drawable", context.getPackageName());
imgAcessorio.setBackgroundResource(id);
}
return view;
}

My guess is that probably you're unchecking that CheckBox but you're not saving its status anywhere, so when that row disappears from the screen by scrolling and you scroll down again, it loads the data again from the database and it's checked in it. I don't know how you're handling your ArrayAdapter extension, but I recommend saving the constructor's ArrayList as an instance inside the class, updating that value inside of it on uncheck, and call notifyDataSetChanged().
---- EDIT ----
To store the ArrayList inside your class, you'll have to create a separate class (with the two fields you're working on), for example:
class MyRow {
CheckBox cb;
TextView tv;
}
So when you declare your custom adapter in your Activity, you'll have to declare previously an ArrayList with some initial elements (or even empty):
ArrayList<MyRow> myList = new ArrayList<MyRow>();
MyRow row1 = new MyRow();
row1.cb.isChecked(...);
row1.tv.setText(...);
myList.add(row1);
Then you call the constructor of your adapter class, something like this:
MyArrayAdapter adapter = new MyArrayAdapter(context, R.layout.your_layout, myList);
So when you pass it to the constructor of your adapter class, you save a copy of it in that class:
public class MyArrayAdapter extends ArrayAdapter {
final private ArrayList<MyRow> myContent;
...
MyArrayAdapter(Context context, int my_layout, ArrayList<MyRow> myContent_) {
...
myContent = myContent_
}
}
So now, any content you change (like for example checking/unchecking a checkbox) you have to save its state in the myContent array. You would find that item by getItem(position) in your getView() method and make the changes you need. After it, you just have to call the notifyDataSetChanged(); method and it will automatically display the changes in your ListView.

It's almost as it your list items are being re-redered or recreated when they go off screen, now the easiest and obvious solution here is to trigger an event when your checkbox is clicked so make an onclick event in your adapter that is triggered when the checkbox is checked or unchecked and updates the data source.

Related

Custom ListView not getting updated

I'm developing a currency exchange app and I'm having some problems with updating my ListView after extracting the rates from an API.
Ignore the flags, I just put whatever files I had to test the solution
On the start of my activity, I am defining:
final ArrayList<ItemData> list = new ArrayList<ItemData>();
final String[] web = {
"EUR", "JPY", "USD", "GBP"
};
final Integer[] imageId = {R.drawable.austria, R.drawable.bangladesh, R.drawable.benin, R.drawable.uk};
private static String _spinnerData;
public static String test;
public static synchronized String getCurrentSpinner(){
if (_spinnerData == null) {
String _spinnerData;
}
return _spinnerData;
}
And onCreate() is defined as:
... not important ...
Spinner spin = (Spinner) findViewById(R.id.spinner_complist);
final SpinnerAdapter adapter1 = new SpinnerAdapter(this,
R.layout.spinner_layout, R.id.txt, list);
spin.setAdapter(adapter1);
spin.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
ItemData item = (ItemData) (parentView.getItemAtPosition(position));
Log.i("item_1", item.getText());
String spinnerData = getCurrentSpinner();
spinnerData = item.getText();
}
I then have a custom Adapter to put the flags+name, where the name is a textview.
Afterwards, I get my conversion rate from an API, through a function getRate() that is working.
On the custom adapter, I have overriden the getView method:
#Override
public View getView(int position, View view, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View rowView = inflater.inflate(R.layout.mylist, null, true);
TextView txtTitle = (TextView) rowView.findViewById(R.id.itemName);
ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);
imageView.setImageResource(imageId[position]);
String currency;
txtTitle.setText(web[position]);
String spinnerData= getCurrentSpinner();
if (spinnerData!=null) {
currency=spinnerData;
getRate(currency, web[position], txtTitle);
}
return rowView;
}
So, in getRate I obtain a String from each row of the ListView and replace it by the value in another coin.
My problem is: If I write getRate("EUR",web[position],txtTitle), everything works as intended.
However, if I put the code as it is, it just doesn't update my ListView. I put a breakpoint and currency is "EUR", so it should be equivalent to what I had by hardcoding the string.
I think that probably the ListView isn't getting properly updated, or the function is making some callback that is replacing my TextViews with the original values.
Any ideas?
Thanks in advance,
Two things stand out to me.
First, this block:
public static synchronized String getCurrentSpinner(){
if (_spinnerData == null) {
String _spinnerData;
}
return _spinnerData;
}
This doesn't really make sense. It reads "if the static scoped _spinnerData is null, create another locally scoped String also called _spinnerData and do nothing with it, then return the statically scoped instance."
And second, this logic:
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
ItemData item = (ItemData) (parentView.getItemAtPosition(position));
Log.i("item_1", item.getText());
String spinnerData = getCurrentSpinner();
spinnerData = item.getText();
}
This reads "create a temporary string called spinnerData that is initialized with the return value of getCurrentSpinner() then immediately replace it with the value of item.getText() then throw the whole thing away (because it's never used thereafter)".
It appears to me you have a misunderstanding in how references work. I think what you are trying to do is save the current spinner selection and use that in your getView().
If that's the case, you would do something like this:
private String _spinnerData; // Does not need to be static
// In onItemSelected
ItemData item = (ItemData) (parentView.getItemAtPosition(position));
Log.i("item_1", item.getText());
_spinnerData = item.getText(); // Save the last item selected
// In getView()
// Call this method with the last known spinner selection
getRate(_spinnerData, web[position], txtTitle);
I would have to assume you are new to Java and don't fully understand how references and scope work. Is that a fair assumption? If so, I strongly suggest you take a step back from Android and work on getting more familiar and comfortable with Java before proceeding. Android is complicated enough without having to figure out the language as you go as well.
Hope that helps!
I think you have some problems with the flow of the data in your Activity.
The getView in the listview should return a view with the populated data from a data source that has the data.
I would advice changing to have something like:
Snippet position selected -> call web API to get any data that you need -> when is returned add it to a list of data that is being displayed on the listview -> then call in the adapter of the listview notifyDataSetChanged()
I would recommend this article for more information about using listviews: http://www.vogella.com/tutorials/AndroidListView/article.html

Android programming: I cant get data to reload into arrayadapter

I am still stuck with this issue, can anyone help. It seems that my problem is that I cant update the data list. I have tried every solution that I've searched for on google etc.. but half the time i'm not even sure that I'm doing the correct thing.
I've used the onResume() to call notifyDataSetChanged, it didn't work. I've tried putting a refresh method into the adapter which i then called in OnResume(). Again it didn't work. Some people suggest clearing the adpater (adapter.clear();) in onResume and then using the addAll() function to relist the data but nothing works.
There has to be a simple solution to this. I have literally been stuck on this for 2 days now. very frustrated.
Here's my Fragment code again...
enter code here
public class SavedAppFragment extends ListFragment {
private static final String TAG = "AppClicked"; //DEBUGGER
private ArrayList<App> mSavedApps;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Populate the ArrayList
mSavedApps = SavedAppData.get(getActivity()).getApps();
AppAdapter adapter = new AppAdapter(mSavedApps);
setListAdapter(adapter);
}
//LIST ITEM CLICKED: /*Control what happens when list item is clicked: I.E. Load up a quiz while putting an EXTRA key containg the package name of the App to be launhced should the user get the question correct */ #Override public void onListItemClick(ListView l, View v, int position,long id) { //Return the crime for the list item that was clicked App c = ((AppAdapter) getListAdapter()).getItem(position); Log.d(TAG, "was clicked");
//Start the Activity that will list the detail of the app
Intent i = new Intent(getActivity(), Quiz_Activity.class);
String name = c.getPackage();
i.putExtra("packagename", name);
startActivity(i);
}
private class AppAdapter extends ArrayAdapter {
private ArrayList<App> mSavedApps;
public AppAdapter(ArrayList<App> apps) {
super(getActivity(), 0, apps);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//If we weren't given a view, inflate one
if (null == convertView) {
convertView = getActivity().getLayoutInflater().inflate(R.layout.list_item_app, null);
//((AppAdapter) getListAdapter()).notifyDataSetChanged();
}
((AppAdapter) getListAdapter()).notifyDataSetChanged();
//Configure the view for this crime
App c = getItem(position);
TextView nameTextView = (TextView) convertView.findViewById(R.id.app_name);
nameTextView.setText(c.getName());
// nameTextView.setText(applicationInfo.loadLabel(packageManager));
TextView packageTextView = (TextView) convertView.findViewById(R.id.app_package);
packageTextView.setText(c.getPackage());
CheckBox appCheckBox = (CheckBox) convertView.findViewById(R.id.app_checked);
appCheckBox.setChecked(c.isChecked());
//Return the view object to the ListView
return convertView;
}
}
}
THANKS!!!
When you return to Activity B, the previous Activity B hasn't been destroyed. Thus, it skips the onCreate. Move all of the stuff you want to make sure happens every time into the onResume. I think you want to make your Adapter a class variable (I'll call it mAdapter) in onCreate, and add code that will get data from the list directly. If you need to do something, put a "refresh" function in the adapter. I'm assuming you have a custom Adapter, because I've never heard of AppAdapter. If you don't, then extend AppAdapter and add that functionality. Thus, your onCreate should look like this:
mAdapter = new AppAdapter(mSavedApps);
setListAdapter(mAdapter);
Your onRefresh could update the data contained in the adapter by some new update function, like so:
mAdapter.update(SavedAppData.get(getActivity()).getApps());

Getting the clicked view in a multi-view listItem and the correct data - Using ListView, CursorAdapter

So I have a CursorAdapter that I am using for my listview. I want each list item to have 4 clickable views, because I will have different things happen when each thing is clicked... not important here.
My problem... is that I am not able to get both the data in a selected item (or row) and the view (1 through 4) that was clicked inside that list item.
What I've tried so far:
When I override onClick in my CursorAdapter I can get the view
successfully, but then returning the data for that list item is not
correct (it returns the data from only the first entry in the list
no matter what has been selected).
So I tried implementing onItemClick in the activity that hosts the listview. When I do this
I am able to get the data from which ever item was selected but then
I can not get one of the four clickable areas from the list item.
When I try to use both of these ideas the onClick override in the
CursorAdapter seems to override the onItemClick method in the
activity and then all I can do is get the correct view (1 of 4) that
was selected in the list item.
Perhaps one of these strategies is the right one and I am just not either getting the data the correct way or getting the view correctly. Regardless, any help would be appreciated!
When implementing the onClick from my CursorAdapter:
#Override
public void bindView(View v, Context context, Cursor c) {
ImageView imPlay = (ImageView) v.findViewById(R.id.row_play_button);
LinearLayout llFirstHalf = (LinearLayout) v.findViewById(R.id.row_first_half);
LinearLayout llSecondHalf = (LinearLayout) v.findViewById(R.id.row_second_half);
CheckBox cbCheck = (CheckBox) v.findViewById(R.id.row_checkbox);
}
public void myOnClick(View v) {
switch(v.getId()){
case R.id.row_checkbox:
//do something
break;
case R.id.row_first_half:
//do something
break;
case R.id.row_second_half:
//do something
break;
case R.id.row_play_button:
//do something
break;
When implementing the onItemClick in MainActivity:
#Override
public void onItemClick(AdapterView<?> parent, View view, int index, long pos) {
Log.e("onITEMCLICK", "index: "+String.valueOf(index));
Log.e("onITEMCLICK", "position: "+String.valueOf(pos));
LinearLayout listItem = (LinearLayout) view;
String title;
String date_created;
String comments;
String sample_rate;
String duration;
String file_size;
String id;
String file_url;
Cursor c = app.rec_adapter.getCursor();
c.moveToPosition(index);
title = c.getString(c.getColumnIndexOrThrow(DBAdapter.rKEY_TITLE));
date_created = c.getString(c.getColumnIndexOrThrow(DBAdapter.rKEY_DATE_CREATED));
comments = c.getString(c.getColumnIndexOrThrow(DBAdapter.rKEY_COMMENTS));
sample_rate = c.getString(c.getColumnIndexOrThrow(DBAdapter.rKEY_SAMPLE_RATE));
sample_rate = convertToHumanReadable(sample_rate);
duration = c.getString(c.getColumnIndexOrThrow(DBAdapter.rKEY_DURATION));
file_size = c.getString(c.getColumnIndexOrThrow(DBAdapter.rKEY_FILE_SIZE));
file_url = c.getString(c.getColumnIndexOrThrow(DBAdapter.rKEY_FILE_URL));
id = c.getString(c.getColumnIndexOrThrow(DBAdapter.rKEY_ROWID));
// find out what section was clicked on the item -- NO SOLUTION
ImageView imPlay = (ImageView) listItem.findViewById(R.id.row_play_button);
LinearLayout llFirstHalf = (LinearLayout) listItem.findViewById(R.id.row_first_half);
LinearLayout llSecondHalf = (LinearLayout) listItem.findViewById(R.id.row_second_half);
CheckBox cbCheck = (CheckBox) listItem.findViewById(R.id.row_checkbox);
}

Implementing custom checkable list view + dynamic adapter

I have implemented custom list view with two rows (name and number) and it is checkable.
The list view has multiple select option.
Whenever user searches for a name, cursor will returned the new list of items. I can't keep track for items which has been selected earlier once adapter gets changed with the new cursor items.
For example user searches for name "Jo" it returns 10 items, in which i have selected 2 rows. Once i remove the search, the cursor and adapter gets changed. I am not able to mark the items checked.
I want to override default checkable items based on position( have to write own which has to make items checkable based on _id(contact id))
( I tried overriding onFinishInflate method. But it didn't help).
Any help appreciated.
Thanks in advance.
what you need is an object to have your check-box data persist your adapter and listview. A hashmap of boolean arrays should suffice.
private HashMap<String, boolean[]> contactMap;
I'd imagine that you could load this in some database method or something and you could have the person's name, like "Jo", as an identifier if need be. the array indexes would correspond to the checkboxes in each listview row as they appear. Then in your adapter which i'd imagine is a CursorAdapter, you could have the following:
private boolean[] contactObj;
public void setContactObj(boolean[] contactObj) {
this.contactObj = contactObj;
notifyDataSetChanged();
}
public boolean[] getContactObj() {
return contactObj;
}
#Override
public void bindView(View view, Context context, Cursor c) {
final int position = c.getPosition();
final CheckBox cb = (CheckBox) view.findViewById(R.id.checkbox);
cb.setChecked(contactObj[position]);
cb.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (cb.isChecked()) {
contactObj[position] = true;
} else {
contactObj[position] = false ;
}
}
});
}
Basically, you have an adapter that only has capacity for one boolean[] which is able to adjust the checkboxes in your listview, modify as the boolean[] as the checkbox is being clicked and then return it in the event that you still need it.

checkbox in List being checked unexpectedly

I have list of checkboxes in list binded by Custom simpleCursorAdapter.
In my custom simpleCursorAdapter, I've overridden newView and bindView with my modifications.
I've managed somehow to do multichoice.
The wierd thing is, after I delete any item from my list, the first item's checkbox is being checked all of a sudden. How does that happen? How can I solve it?
My SimpleCursorAdapter class:
public class MyListCursorAdapter extends SimpleCursorAdapter
{
private Context context;
private int layout;
public MyCursorAdapter(Context context, int layout, Cursor c,
String[] from, int[] to)
{
super(context, layout, c, from, to);
this.context = context;
this.layout = layout;
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent)
{
Cursor c = getCursor();
final LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(layout, parent, false);
CheckBox chkBoxBtn = (CheckBox) v.findViewById (R.id.deleteTwittChkBox);
if (chkBoxBtn != null)
{
chkBoxBtn.setChecked(false);
}
return v;
}
#Override
public void bindView(View v, Context context, Cursor c)
{
--binding view to my textsview in my items
//now it's the importat part:
CheckBox chkBoxBtn = (CheckBox) v.findViewById(R.id.deleteTwittChkBox);
if (chkBoxBtn != null)
{
chkBoxBtn.setId(Integer.valueOf(c.getString(c
.getColumnIndex(MyUsers.User._ID))));
chkBoxBtn.setOnClickListener(new OnItemClickListener(chkBoxBtn, v));
chkBoxBtn.setChecked(false);
}
}
//i couldnt find another way of doing this, but this is how i set listeners to my checkboxses
static ArrayList<String> checkedItemsList = new ArrayList<String>();
private class OnItemClickListener implements OnClickListener
{
private int mPosition;
private CheckBox chkBox;
OnItemClickListener(CheckBox mChkBox, View v)
{
chkBox = mChkBox;
chkBox.setChecked(false);
}
#Override
public void onClick(View v)
{
if (chkBox.isChecked())
{
checkedItemsList.add(String.valueOf(chkBox.getId()));
}
else
{
checkedItemsList.remove(String.valueOf(chkBox.getId()));
}
}
}
}
Here is the code part from the ListActivity class which describes the button that deletes the checked box items:
OnClickListener btListener = new OnClickListener()
{
public void onClick(View view)
{
// long[] items = listView.getCheckItemIds();
int x = 0;
Uri myUri = Uri
.parse("content://com.idan.datastorageprovider/users");
String where = "_id" + "=?";
//here i am tatking all checkboxes which ive added from the adapter class
ArrayList<String> checkedItemsList = MySimpleCursorAdapter.checkedItemsList;
for (String itemID : checkedItemsList)
{
getContentResolver()
.delete(myUri, where, new String[] { itemID});
checkedItemsList.remove(itemID);
}
}
};
I doubt that SimpleCursorAdapter is the right class to extend here.
Is the "checked" state connected to the data XML in any way? No? So you need your own custom adapter!
Basically all adapters have to implement a way to generate a view from a given element (more precisely an element position!). This will be called at any time where the list wants to display an element. Now, the trick it uses is to re-use formerly created list view elements that cannot be seen on screen any more! Thus: when you scroll your list down and an element disappears at the top, EXACTLY this view object will be re-used for the next appearing item.
So, when this method is called with a given "old" view that should be re-used, all contained elements will have to be set according the elements data. If a checkbox is part of this game, you will have to have a storage for the checked state! It is not sufficient to have a checkbox as there will be less checkbox objects as there are list elements!
SimpleCursorAdapters are there to - yeah - represent SIMPLE things. An XML describing data (images and text, as the documentation states). Because of this simplicity all you have to do here is provide a method to create NEW element view objects - you are not intercepting the re-use process AT ALL! It basically only knows how to put the data into an existing view object - but it is lacking the knowledge of how to handle checked/unchecked boxes!
Your solution: write your own BaseAdapter extension and do what has to be done: implement "getView" (and some other methods like getItem, getItemId and getCount). It's not hard at all!
This API Demo uses a BaseAdapter and the mExpanded state here is basically identical to your checkbox states!
Good luck!
You might need to call notifyDataSetChanged when you modify the data.
The problem is probably that you're calling setChecked from within the onItemClickListener. One hacky way around this is to do the following before and after you call setChecked from within your listener:
chkBox.setClickable(false);
chkBox.setChecked(false);
checkBox.setClickable(true);
This will prevent your onItemClickListener from getting called when you manually call setChecked.

Categories

Resources