Pre-fill the CheckedTextViews in a ListView - android

Here is my first question on StackOverFlow, I usually always find an answer by myself but I am really stuck on a weird problem that I will explain here:
I implemented a ListView in a fragment activity, this listview contains a list of categories related to the current record that I get from the SQLLite database.
All is working fine, I created a SimpleCursorAdapter to retrieve the data from the DB and I display the categories correctly in the ListView.
The problem is related to the pre-fill of the checkboxes (it is a multiselection list), depending on how I try to pre-check the checkboxes, I get 2 cases:
First, the checkboxes are well pre-checked, but I cannot toggle the checkboxes anymore by clicking them. Second the click toggle well the checkboxes, but they are not pre-checked anymore...
Here is the part of the code where I have the problem:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//super.onCreate(savedInstanceState);
View v = inflater.inflate(R.layout.rate_fragment, container,false);
dbCategories = "";
displayCategories = resources.getText(R.string.no_categories).toString();
/** INITIALIZATION */
mViewSwitcher = (ViewSwitcher)v.findViewById(R.id.profileSwitcher);
/** Edition view */
rateGroup = (RadioGroup)v.findViewById(R.id.rate_group);
rateOne = (RadioButton)v.findViewById(R.id.one_button);
rateOne.setTag(1);
rateTwo = (RadioButton)v.findViewById(R.id.two_button);
rateTwo.setTag(2);
rateThree = (RadioButton)v.findViewById(R.id.three_button);
rateThree.setTag(3);
rateFour = (RadioButton)v.findViewById(R.id.four_button);
rateFour.setTag(4);
rateFive = (RadioButton)v.findViewById(R.id.five_button);
rateFive.setTag(5);
descET = (EditText)v.findViewById(R.id.editdescription);
descTextSize = descET.getTextSize();
descET.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
});
categoriesTV_edit = (TextView)v.findViewById(R.id.edit_categories);
categoriesBT = (Button) v.findViewById(R.id.select_categories);
categoriesBT.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
View categoriesListTitle = getActivity().getLayoutInflater().inflate(R.layout.category_list_title, null);
AlertDialog.Builder alt_bld = new AlertDialog.Builder(v.getContext()).setCustomTitle(categoriesListTitle);
categories = db.getAllCategoriesByRate(currentRate);
categoriesList = new ListView(getActivity());
categoriesList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
categoriesList.setClickable(true);
String[] fromColumns = new String[] {
DatabaseHandler.CATEGORY_NAME
};
int[] toViews = new int[]{
R.id.cat_checked
};
//mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_multiple_choice, categories, fromColumns, toViews, 0);
mAdapter = new SimpleCursorAdapter(getActivity(), R.layout.category_item, categories, fromColumns, toViews, 0);
mAdapter.setViewBinder(new ViewBinder() {
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
if (columnIndex == 1) {
CheckedTextView categRow = (CheckedTextView) view;
String catName = cursor.getString(1);
mAdapter.setViewText((TextView) view, catName);
int catChecked = cursor.getInt(2);
//boolean checkedCat = catChecked==1;
//categoriesList.setItemChecked(cursor.getPosition(),checkedCat);
categRow.setChecked(catChecked==1);
int catID = cursor.getInt(0);
categRow.setTag(catID);
return true;
}
else {
return false;
}
}
});
categoriesList.setAdapter(mAdapter);
alt_bld.setView(categoriesList);
To have one case or another, all depends on these 2 lines:
//boolean checkedCat = catChecked==1;
//categoriesList.setItemChecked(cursor.getPosition(),checkedCat);
If they are commented, the checkboxes are not pre-checked, but the toggle on the clicks is working. But if I comment these lines out, the toggle is not working anymore but the categories are prechecked.
What I also don't understand is that this line is not working:
categRow.setChecked(catChecked==1);
But this one is working well (I succeed to retrieve the tag):
categRow.setTag(catID);
So I hope someone will succeed to explain to me what I do wrong, I guess there is something I misunderstood here...
NOTE: I get 3 columns from the cursor "categories", first one is the ID of the category, second one is the name, and third one is the status: checked or not (1 or 0).
Thanks in advance for your time.

Finally I ended up creating my own custom adapter, this way I could at least understand more easily what was happening.
I had to create actually several multiselect lists, some populated with data from the database, others from the shared preferences.
For this one displaying data from the DB, I created the following adapter (I commented out the lines about the icons because I did not set them up yet):
public class CategoriesLVAdapter extends BaseAdapter {
private Context mContext;
private LayoutInflater mInflater;
private List<Category> categoriesList;
// Constructor
public CategoriesLVAdapter(Context c, List<Category> categories_list){
mContext = c;
mInflater = LayoutInflater.from(c);
categoriesList = categories_list;
}
public List<Category> getCategoriesList(){
return categoriesList;
}
#Override
public int getCount() {
return categoriesList.size();
}
#Override
public Object getItem(int position) {
return categoriesList.get(position);
}
#Override
public long getItemId(int position) {
return categoriesList.get(position).getID();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.categories_list_row, null);
//convertView.setLayoutParams(new ListView.LayoutParams(200, 90));
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.categories_list_row_tv);
//holder.icon = (ImageView) convertView.findViewById(R.id.categories_list_row_iv);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
//holder.icon.setImageResource(categoriesList.get(position).getDrawableID());
//holder.icon.setAdjustViewBounds(true);
//holder.icon.setScaleType(ImageView.ScaleType.CENTER_CROP);
holder.title.setText(categoriesList.get(position).getName());
return convertView;
}
static class ViewHolder {
TextView title;
//ImageView icon;
}
}
In my activity, I use this adapter when the AlertDialog is called to populate the ListView, then I pre-select the categories using the last ones saved in the shared preferences:
private void categoriesFilter(){
AlertDialog.Builder alt_bld = new AlertDialog.Builder(this);
alt_bld.setTitle(resources.getText(R.string.select_categories).toString());
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.categories_list,(ViewGroup) findViewById(R.id.categories_layout_root));
categoriesLV = (ListView) layout.findViewById(R.id.categories_list);
alt_bld.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String selectedCategoriesString = getSelectedValues(categoriesLV);
//Update the shared preferences
prefs.edit().putString(RateDayApplication.PREF_KEY_CATEGORIES, selectedCategoriesString).commit();
updateFilterDisplay(resources.getText(R.string.cat_title).toString(), selectedCategoriesString, searchedCategoriesTV, "Category");
}
});
alt_bld.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
String selectedCategoriesString = prefs.getString(RateDayApplication.PREF_KEY_CATEGORIES, new String());
categoriesLV.setAdapter(new CategoriesLVAdapter(this, categoriesList));
String[] selectedCategoriesArray = selectedCategoriesString.split(",");
int categoriesLVLength = categoriesLV.getCount();
for(int i = 0; i < categoriesLVLength; i++){
int categoryID = ((Category) categoriesLV.getItemAtPosition(i)).getID();
if(Arrays.asList(selectedCategoriesArray).contains(String.valueOf(categoryID))){
categoriesLV.setItemChecked(i, true);
}
}
alt_bld.setView(layout);
AlertDialog alert = alt_bld.create();
alert.show();
}
Finally here is the function I call from my database handler to get the list of catagories:
// Getting All Categories By ID desc
public List<Category> getCategoriesList() {
String selectQuery = "SELECT " + CATEGORY_ID + ", " + CATEGORY_NAME + " FROM " + CATEGORY_TABLE + " ORDER BY " + CATEGORY_ID + " ASC";
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
List<Category> categoriesList = new ArrayList<Category>();//String[] categoriesList = {};
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
Category category = new Category(cursor.getInt(0), cursor.getString(1), false);
categoriesList.add(category);
} while (cursor.moveToNext());
}
cursor.close();
db.close();
return categoriesList;
}
I think my problem before was coming from the fact that the function "setItemChecked" is a little misleading because it does not mean necessarily that anything is checked.
When you use the function "setItemChecked", the item in the list view becomes selected, with or without a checkbox (my rows only contain text views).
The rows selected in my list appear in a different color, and that's enough in my opinion for a simple multi selection list.
The layouts I used are quite simple, "categories_list" contains a ListView in a LinearLayout and "categories_list_row" contains a TextView in a LinearLayout.
Hope it may guide someone!

Related

Android: How to refresh CursorAdapter with support for older APIs?

I read some posts and found that reQuery() is deprecated and some suggested using SwapCursor() or ChangeCursor().
I have a Favorite button on whose click I update DB and change color of the Button. When I scroll and come back to particular view(and Button) color is reset.
I know it is because view is recycled. I have a condition based on a DB column value to set the color of the Button.
I want view to get updated values from DB after I press the Button. For which I have to refresh/requery Cursor/DB.
How do I do that with CursorAdapter keeping in mind that my min. API is 19?
UPDATE
CursorAdapter code:
public class ToDoCursorAdapter extends CursorAdapter {
SparseBooleanArray selectionArrayAr = new SparseBooleanArray();
SparseBooleanArray selectionArrayRef = new SparseBooleanArray();
SparseBooleanArray selectionArrayFav = new SparseBooleanArray();
//Boolean isSet = false;
private MainButtons_Interface mAdapterCallback;
public ToDoCursorAdapter(Context context, Cursor cursor) {
super(context, cursor, 0);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
ViewHolderItem viewHolder = new ViewHolderItem();
View rowView = LayoutInflater.from(context).inflate(R.layout.listview, parent, false);
viewHolder.engTextV = (TextView) rowView.findViewById(R.id.engText);
viewHolder.arTextV = (TextView) rowView.findViewById(R.id.arabText);
viewHolder.buttonIAV = (Button) rowView.findViewById(R.id.buttonIA); //For Arabic Text
viewHolder.refTextV = (TextView) rowView.findViewById(R.id.refText);
viewHolder.buttonIRV = (Button) rowView.findViewById(R.id.buttonIR); //For Ref Text
viewHolder.buttonIFV = (ImageButton) rowView.findViewById(R.id.buttonF);
rowView.setTag(viewHolder);
return rowView;
}
#Override
public void bindView(final View view, final Context context, final Cursor cursor) {
final ViewHolderItem viewHolder = (ViewHolderItem) view.getTag();
String arabic = cursor.getString(cursor.getColumnIndexOrThrow("PlainArab_Text")).trim().replaceAll("[\n]{2,}", "TWOFEEDS").replaceAll("\n", " ").replaceAll(" +", " ").replaceAll("<br/>", "\n").replaceAll("TWOFEEDS", "\n") + "\n";
String english = cursor.getString(cursor.getColumnIndexOrThrow("PlainEng_Text")).trim().replaceAll("[\n]{2,}", "TWOFEEDS").replaceAll("\n", " ").replaceAll(" +", " ").replaceAll("<br/>", "\n").replaceAll("TWOFEEDS", "\n") + "\n";
String ref = cursor.getString(cursor.getColumnIndexOrThrow("REF")).trim().replaceAll("<br/> <br/>", " ").replaceAll("<br/>", "\n");
final Integer HadithID = cursor.getInt(cursor.getColumnIndexOrThrow("ID"));
final Integer IsFav = cursor.getInt(cursor.getColumnIndexOrThrow("IsFavorite"));
viewHolder.arTextV.setText(arabic);
viewHolder.engTextV.setText(english);
viewHolder.refTextV.setText(ref);
final int position = cursor.getPosition();
boolean isSelectedA = selectionArrayAr.get(position);
boolean isSelectedR = selectionArrayRef.get(position);
boolean isSelectedF = selectionArrayFav.get(position);
if (isSelectedA) {
viewHolder.arTextV.setVisibility(view.GONE);
viewHolder.buttonIAV.setText("Show Arabic Version");
} else if (!isSelectedA){
viewHolder.arTextV.setVisibility(view.VISIBLE);
viewHolder.buttonIAV.setText("Hide Arabic Version");
}
if (isSelectedR) {
viewHolder.refTextV.setVisibility(view.GONE);
viewHolder.buttonIRV.setText("Show Refrence");
} else if (!isSelectedR){
viewHolder.refTextV.setVisibility(view.VISIBLE);
viewHolder.buttonIRV.setText("Hide Refrence");
}
//boolean isSelectedF = selectionArrayFav.get(position);
if(isSelectedF) {
viewHolder.buttonIFV.setImageResource(R.drawable.favoritebutton_afterclick);
} else if (!isSelectedF){
viewHolder.buttonIFV.setImageResource(R.drawable.favoritebutton);
}
//Arabic Button
viewHolder.buttonIAV.setOnClickListener(
new View.OnClickListener()
{ #Override
public void onClick(View v) {
boolean isSelectedAc = selectionArrayAr.get(position);
if(!isSelectedAc) {
viewHolder.arTextV.setVisibility(v.GONE);
viewHolder.buttonIAV.setText("Show Arabic Version");
setSelectedAr(position, true);
} else if (isSelectedAc){
viewHolder.arTextV.setVisibility(v.VISIBLE);
setSelectedAr(position, false);
viewHolder.buttonIAV.setText("Hide Arabic version");
}
}
}
);
//Ref Button
viewHolder.buttonIRV.setOnClickListener(
new View.OnClickListener()
{ #Override
public void onClick(View v) {
boolean isSelectedRc = selectionArrayRef.get(position);
if(!isSelectedRc) {
viewHolder.refTextV.setVisibility(v.GONE);
viewHolder.buttonIRV.setText("Show Reference");
setSelectedRef(position, true);
} else if (isSelectedRc){
viewHolder.refTextV.setVisibility(v.VISIBLE);
setSelectedRef(position, false);
viewHolder.buttonIRV.setText("Hide Reference");
}
}
}
);
//Fav Button
viewHolder.buttonIFV.setOnClickListener(
new View.OnClickListener()
{ #Override
public void onClick(View v) {
boolean isSelectedF = selectionArrayFav.get(position);
boolean IsSet = ((ListViewActivity) context).addRemFav(HadithID);
String mess ="";
if(IsSet){
mess = "Hadith add to Favorite list";
} else if(!IsSet){
mess = "Hadith removed from Favorite list";
}
if(!isSelectedF) {
viewHolder.buttonIFV.setImageResource(R.drawable.favoritebutton_afterclick);
setSelectedF(position, true);
} else if (isSelectedF){
viewHolder.buttonIFV.setImageResource(R.drawable.favoritebutton);
setSelectedF(position, false);
}
Toast.makeText(v.getContext(), mess, Toast.LENGTH_SHORT).show();
}
}
);
}
// our ViewHolder.
static class ViewHolderItem {
TextView engTextV;
TextView arTextV;
TextView refTextV;
Button buttonIAV;
Button buttonIRV;
ImageButton buttonIFV;
}
// Method to mark items in selection
public void setSelectedAr(int position, boolean isSelected) {
selectionArrayAr.put(position, isSelected);
}
public void setSelectedRef(int position, boolean isSelected) {
selectionArrayRef.put(position, isSelected);
}
public void setSelectedF(int position, boolean isSelected) {
selectionArrayFav.put(position, isSelected);
}
UPDATE
I added this logic to my function which was called on clicking the Button.
Cursor todoCursor1 = hadDB.rawQuery("SELECT ID as _id, * FROM HAD_TABLE WHERE ID < 7001 ", null);
todoAdapter.changeCursor(todoCursor1);
Basically, you just need to requery DB so that you get updated records/Data and then change your current cursor with new one, todoCursor1 is my case above.
Also, changeCursor() will close your current cursor, in case you would want to go back to old cursor you should use swapCursor() instead as it will return you old cursor.
Now my only thing I want to know is, if this will work for APIs 19 and up.
I added this logic to my function which was called on clicking the Button.
Cursor todoCursor1 = hadDB.rawQuery("SELECT ID as _id, * FROM HAD_TABLE WHERE ID < 7001 ", null);
todoAdapter.changeCursor(todoCursor1);
Basically, you just need to requery DB so that you get updated records/Data and then change your current cursor with new one, todoCursor1 is my case above.
Also, changeCursor() will close your current cursor, in case you would want to go back to old cursor you should use swapCursor() instead as it will return you old cursor.

Wrong behavior in a button located in each row of a list when I change the orientation of a tablet

I have a custom SimpleCursorAdapter and a list view. Each row of the list have a name and a button. When I press the button for each name, a dialog appears with a description.
Inside the custom SimpleCursorAdapter I set the onclick method for the button. When I have a large list, my listView gets a scroll bar. And I dont know why, when I scroll down, the last rows of my list doesnt show the correct description for each row. This is my code:
public class listServicesCursorAdapter extends SimpleCursorAdapter{
private Context context;
private int layout;
private String[] from;
private int[] to;
public listServicesCursorAdapter (Context context, int layout, Cursor c,
String[] from, int[] to, int flags) {
super(context, layout, c, from, to, flags);
this.context = context;
this.layout = layout;
this.from = from;
this.to = to;
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
final LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(layout, parent, false);
//Column of BD that we want to recover
String column = null;
//Index of the column of DB
int nameCol = 0;
//Result of obtain the index of the column of DB
String nombre = null;
//Name of the textView in the Layout where we want to show the result
TextView name_text= null;
String description = null;
String nameService = null;
//For each value of DB, we show it in the text view.
for (int i=0; i<from.length; i++){
column= from[i];
nameCol = cursor.getColumnIndex(column);
name = cursor.getString(nameCol);
//the values to[i] equals to 0 indicates values that we need but
//that we are not showing in the list directly
//0 -> description
if(to[i] == 0){
description = name;
}else{
nameService = name;
name_text = (TextView) v.findViewById(to[i]);
if (name_text != null) {
name_text.setText(name);
}
}
}
ImageButton buttonDescription = (ImageButton) v.findViewById(R.id.imageButtonDescription);
//we store in a bundle the name and description of the service, so we can use it in
// the setOnClickListener method.
final Bundle mArguments = new Bundle();
mArguments.putString("name", nameService);
mArguments.putString("description", description);
buttonDescription .setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v){
AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext());
builder.setMessage(mArguments.getString("description"))
.setTitle(mArguments.getString("name"))
.setCancelable(false)
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}});
return v;
}
}
This is where I call the adapter:
ServiceSqliteDao serviceDao = new ServiceSqliteDao();
//get the services for DB
Cursor mCursorServices = serviceDao.listServices(getActivity());
if(mCursorServices.getCount()>0){
//indicate the fields we want to show (from) and where (to)
String[] from = new String[] { "name", "description"};
int[] to = new int[] { R.id.checkBoxService,0};
ListView lvServices = (ListView) v.findViewById (R.id.listViewServices);
ListServicesCursorAdapter notes = new ListServicesCursorAdapter (getActivity(), R.layout.activity_file_service, mCursorServices, from, to, 0);
lvServices.setAdapter(notes);
Why do I get this behavior?. I get all the names in the list right but when I press the button in horizontal way (I mean a put the tablet horizontally) and get the scroll bar in my list, I dont get the right description. By the other hand, if I use the tablet vertically, I dont get the scroll bar in my list and I get the right description in each button.
This is my layout:
<ListView
android:id="#+id/listViewServices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1" >
</ListView>
SOLUTION:
newView should look like this:
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
final LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(layout, parent, false);
return v;
}
and bindView should look like this:
#Override
public View bindView(View v, Context context, Cursor cursor) {
//Column of BD that we want to recover
String column = null;
//Index of the column of DB
int nameCol = 0;
//Result of obtain the index of the column of DB
String nombre = null;
//Name of the textView in the Layout where we want to show the result
TextView name_text= null;
String description = null;
String nameService = null;
//For each value of DB, we show it in the text view.
for (int i=0; i<from.length; i++){
column= from[i];
nameCol = cursor.getColumnIndex(column);
name = cursor.getString(nameCol);
//the values to[i] equals to 0 indicates values that we need but
//that we are not showing in the list directly
//0 -> description
if(to[i] == 0){
description = name;
}else{
nameService = name;
name_text = (TextView) v.findViewById(to[i]);
if (name_text != null) {
name_text.setText(name);
}
}
}
/********************************NEW CODE ************************************/
String uniMedition = cursor.getString(cursor.getColumnIndex("unitMedition"));
if(uniMedition.equals("none")){
EditText etMedida = (EditText) v.findViewById(R.id.editTextMedida);
etMedida.setVisibility(View.INVISIBLE);
TextView tvUniMedition = (TextView) v.findViewById(R.id.textViewUniMedition);
tvUniMedition .setVisibility(View.INVISIBLE);
}else{
EditText etMedida = (EditText) v.findViewById(R.id.editTextMedida);
etMedida.setVisibility(View.VISIBLE);
TextView tvUniMedition = (TextView) v.findViewById(R.id.textViewUniMedition);
tvUniMedition .setVisibility(View.VISIBLE);
tvUniMedition .setText(uniMedition);
}
/********************************END NEW CODE ************************************/
ImageButton buttonDescription = (ImageButton) v.findViewById(R.id.imageButtonDescription);
//we store in a bundle the name and description of the service, so we can use it in
// the setOnClickListener method.
final Bundle mArguments = new Bundle();
mArguments.putString("name", nameService);
mArguments.putString("description", description);
buttonDescription .setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v){
AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext());
builder.setMessage(mArguments.getString("description"))
.setTitle(mArguments.getString("name"))
.setCancelable(false)
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}});
}
}
Now everything works fine!.
Why do I get this behavior?. I get all the names in the list right but
when I press the button in horizontal way (I mean a put the tablet
horizontally) and get the scroll bar in my list, I dont get the right
description.
When your ListView doesn't have space to show all of the rows it will recycle the row view for performance reasons. The problem is that in your SimpleCursorAdapter you override the newView() method which will be called only when the ListView doesn't have a recycled view. Override bindView() to do the work as that method is called for each row, in the newView() method just inflate/build the row layout.

Custom Adapter List View

I need some help with my project. I've created a Custom Adapter, since I want my List View to display 5 textview, instead of one that I managed to do so far. This is my CustomAdapterPn activity:
public class CustomAdapterPn extends BaseAdapter {
private static ArrayList<Poniedzialek> searchPnArrayList;
private LayoutInflater mInflater;
public CustomAdapterPn(final Context context, final ArrayList<Poniedzialek> results) {
searchPnArrayList = results;
mInflater = LayoutInflater.from(context);
}
public int getCount() {
return searchPnArrayList.size();
}
public Object getItem(int position) {
return searchPnArrayList.get(position);
}
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.entry, null);
holder = new ViewHolder();
holder.txtSession = (TextView) convertView.findViewById(R.id.textSession);
holder.txtName = (TextView) convertView.findViewById(R.id.textName);
holder.txtStart = (TextView) convertView.findViewById(R.id.textStartTime);
holder.txtEnd = (TextView) convertView.findViewById(R.id.textEndTime);
holder.txtRoom = (TextView) convertView.findViewById(R.id.textRoom);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.txtSession.setText(searchPnArrayList.get(position).getTypeOfSession());
holder.txtName.setText(searchPnArrayList.get(position).getName());
holder.txtStart.setText(searchPnArrayList.get(position).getStartTime());
holder.txtEnd.setText(searchPnArrayList.get(position).getEndTime());
holder.txtRoom.setText(searchPnArrayList.get(position).getRoom());
return convertView;
}
static class ViewHolder {
TextView txtSession;
TextView txtName;
TextView txtStart;
TextView txtEnd;
TextView txtRoom;
}
}
And this is Activity where I wish to use this CustomAdapter. Note that I was using ArrayAdapter to display list items - I didn't modify the code yet, since I am clueless what should I do to manage this custom adapter correctly ( I was trying to, but nothing worked out well ). I am a newbie, so it's quite hard for me to get this, although I was reading tons of tutorials.
public class PoniedzialekActivity extends Activity implements OnClickListener, OnItemClickListener{ // z ListActivity na Activity
private Button butPnAdd;
private Button butPnDelete;
private ListView list_Pn;
private static final int DIALOG_ALERT = 10;
// We need some kind of Adapter to made the connection between ListView UI component and SQLite data set.
private ListAdapter pn_list_adapter;
// We need this while we read the query using Cursor and pass data
private ArrayList<Poniedzialek> pn_list;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_poniedzialek);
butPnAdd = (Button) findViewById(R.id.butPnAdd);
butPnAdd.setOnClickListener(this);
butPnDelete = (Button) findViewById(R.id.butPnDel);
butPnDelete.setOnClickListener(this);
// Initialize UI components
list_Pn = (ListView) findViewById(R.id.listPn);
list_Pn.setOnItemClickListener(this);
pn_list = new ArrayList<Poniedzialek>();
// For the third argument, we need a List that contains Strings.
//We decided to display undergraduates names on the ListView.
//Therefore we need to create List that contains undergraduates names
pn_list_adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, populateList());
list_Pn.setAdapter(pn_list_adapter);
}
#Override
public void onClick(View v) {
if(v.getId()==R.id.butPnAdd){
Intent i = new Intent(PoniedzialekActivity.this,dodawaniePoniedzialek.class);
startActivity(i);
}
if(v.getId()==R.id.butPnDel){
showDialog(DIALOG_ALERT);
}
}
/**
* DIALOG
*/
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_ALERT:
// Create out AlterDialog
Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Czy na pewno chcesz usunac wszystkie wpisy ?");
builder.setCancelable(true);
builder.setPositiveButton("Tak", new OkOnClickListener());
builder.setNegativeButton("Nie", new CancelOnClickListener());
AlertDialog dialog = builder.create();
dialog.show();
}
return super.onCreateDialog(id);
}
private final class CancelOnClickListener implements
DialogInterface.OnClickListener {
public void onClick(DialogInterface dialog, int which) {
// Nic nie robi
}
}
private final class OkOnClickListener implements
DialogInterface.OnClickListener {
public void onClick(DialogInterface dialog, int which) {
DeletePn();
onResume();
}
}
public void DeletePn(){
DatabaseHelper openHelperClass = new DatabaseHelper(this);
SQLiteDatabase sqliteDatabase = openHelperClass.getWritableDatabase();
sqliteDatabase.delete(DatabaseHelper.PN_TABLE, null, null);
}
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
// TODO Auto-generated method stub
}
// To create a List that contains undergraduate names, we have to read the SQLite database
//We are going to do it in the separate method
public List<String> populateList(){
// We have to return a List which contains only String values. Lets create a List first
List<String> pn_string_list = new ArrayList<String>();
// First we need to make contact with the database we have created using the DbHelper class
DatabaseHelper openHelperClass = new DatabaseHelper(this);
// Then we need to get a readable database
SQLiteDatabase sqliteDatabase = openHelperClass.getReadableDatabase();
// We need a a guy to read the database query. Cursor interface will do it for us
//(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
Cursor cursor = sqliteDatabase.query(DatabaseHelper.PN_TABLE, null, null, null, null, null, null);
// Above given query, read all the columns and fields of the table
startManagingCursor(cursor);
// Cursor object read all the fields. So we make sure to check it will not miss any by looping through a while loop
while (cursor.moveToNext()) {
// In one loop, cursor read one undergraduate all details
// Assume, we also need to see all the details of each and every undergraduate
// What we have to do is in each loop, read all the values, pass them to the POJO class
//and create a ArrayList of undergraduates
String session = cursor.getString(cursor.getColumnIndex(DatabaseHelper.PN_KEY_TYPE_OF_SESSION));
String start = cursor.getString(cursor.getColumnIndex(DatabaseHelper.PN_KEY_START_TIME));
String end = cursor.getString(cursor.getColumnIndex(DatabaseHelper.PN_KEY_END_TIME));
String name = cursor.getString(cursor.getColumnIndex(DatabaseHelper.PN_KEY_NAME));
String room = cursor.getString(cursor.getColumnIndex(DatabaseHelper.PN_KEY_ROOM));
// Finish reading one raw, now we have to pass them to the POJO
Poniedzialek pn = new Poniedzialek();
pn.setTypeOfSession(session);
pn.setName(name);
pn.setStartTime(start);
pn.setEndTime(end);
pn.setRoom(room);
// Przekazujemy pn do arraylist
pn_list.add(pn);
// But we need a List of String to display in the ListView also.
// That is why we create "pn_string_list"
pn_string_list.add(name);
}
// Jezeli Baza Danych nie zostanie zamknieta dostaniemy error
sqliteDatabase.close();
return pn_string_list;
}
// If you don't write the following code, you wont be able to see what you have just insert to the database
#SuppressWarnings("unchecked")
#Override
protected void onResume() {
super.onResume();
pn_list_adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, populateList());
list_Pn.setAdapter(pn_list_adapter);
((ArrayAdapter<String>) pn_list_adapter).notifyDataSetChanged(); // dodano
list_Pn.refreshDrawableState(); // dodanoe
list_Pn.invalidate(); // dodanoe
}
} // end PoniedzialekActivity
Create an object for your custom adapter class CustomAdapterPn and set this custom adapter object to list view . Not the array adapter .
Look at this lines and make changes according to it,
CustomAdapterPn pn_list_adapter; //change 1
pn_list = new ArrayList<Poniedzialek>();
populateList() // Change 2
pn_list_adapter = new CustomAdapterPn(this,pn_list); // Change 3
list_Pn.setAdapter(pn_list_adapter);
Try this and let me know what happen..

EditText in ListView without it recycling input

Still new to android and even more to custom cursor adapter so I'm having trouble understanding how to prevent my listview from recycling views to prevent input from one edittext to show up in another when scrolled. I've seen on other post saying to change the name of convertview but how to do that I'm drawing a blank. I was hoping someone here would be able to give more details or example of how to do based of what code I've wrote so far.
public class editview extends ListActivity {
private dbadapter mydbhelper;
private PopupWindow pw;
public static int editCount;
public static ListView listView;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mydbhelper = new dbadapter(this);
mydbhelper.open();
View footer = getLayoutInflater().inflate(R.layout.footer_layout, null);
ListView listView = getListView();
listView.addFooterView(footer);
showResults();
}
//Populate view
private void showResults (){
Cursor cursor = mydbhelper.getUserWord();
startManagingCursor(cursor);
String[] from = new String[] {dbadapter.KEY_USERWORD};
int[] to = new int[] {R.id.textType};
ItemAdapter adapter = new ItemAdapter(this, R.layout.edit_row, cursor,
from, to);
adapter.notifyDataSetChanged();
this.setListAdapter(adapter);
editCount = adapter.getCount();
}
//footer button
public void onClick(View footer){
final MediaPlayer editClickSound = MediaPlayer.create(this, R.raw.button50);
editClickSound.start();
startActivity(new Intent("wanted.pro.madlibs.OUTPUT"));
}
//custom cursor adapter
class ItemAdapter extends SimpleCursorAdapter {
private LayoutInflater mInflater;
private Cursor cursor;
public ItemAdapter(Context context, int layout, Cursor cursor, String[] from,
int[] to) {
super(context, layout, cursor, from, to);
this.cursor = cursor;
mInflater = LayoutInflater.from(context);
}
static class ViewHolder {
protected TextView text;
protected EditText edittext;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.edit_row, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.textType);
holder.edittext = (EditText) convertView.findViewById(R.id.editText);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
cursor.moveToPosition(position);
int label_index = cursor.getColumnIndex("userword");
String label = cursor.getString(label_index);
holder.text.setText(label);
return convertView;
}
}
Changed it to
class ItemAdapter extends SimpleCursorAdapter {
private LayoutInflater mInflater;
private Cursor cursor;
Map<Integer, String> inputValues = new HashMap<Integer, String>();
public View getView(final int position, View convertView, ViewGroup parent) {
....
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.edit_row, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.textType);
holder.edittext = (EditText) convertView.findViewById(R.id.editText);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
cursor.moveToPosition(position);
int label_index = cursor.getColumnIndex("userword");
String label = cursor.getString(label_index);
holder.text.setText(label);
String oldText = inputValues.get(position);
holder.edittext.setText(oldText == null ? "" : oldText);
holder.edittext.addTextChangedListener(new TextWatcher(){
public void afterTextChanged(Editable editable) {
inputValues.put(position, editable.toString());
}
but it is recycling after all edittext have data. Tried using holder.edittext.setText(oldText) but same effect.
First of all, you really don't want to prevent a list view from recycling its views. View recycling is a huge optimization. For a lot of really good info on lists, see the google IO talk: http://www.youtube.com/watch?v=wDBM6wVEO70
That being said, you've correctly identified your problem: You have far fewer EditTexts than you do items in your list. As the you scroll through the list those EditTexts are recycled so you see the same input over and over again.
Basically what you need to do is save the input for your EditTexts in some datastructure (a HashMap if they will only edit a few values, maybe a List if they will be changing most of the values, either would work) that maps the position to the input. You can do this by adding a textChangedListener to your edit texts in getView:
#Override
public View getView(final int position, View convertView, ViewGroup parent){
...
cursor.moveToPosition(position);
int label_index = cursor.getColumnIndex("userword");
String label = cursor.getString(label_index);
holder.text.setText(label);
//clear whatever text was there from some other position
//and set it to whatever text the user edited for the current
//position if available
String oldText = yourMapOfPositionsToValues.get(position);
holder.setText(oldText == null ? "" : oldText);
//every time the user adds/removes a character from the edit text, save
//the current value of the edit text to retrieve later
holder.edittext.addTextChangedListener(new TextWatcher(){
#Override
public void afterTextChanged(Editable editable) {
yourMapOfPositionsToValues.put(position, editable.toString());
}
....
};
return convertView;
}
Whenever your user is done editing, you can run through your datastructure and do whatever with those values.
Edit:
I changed onTextChanged to afterTextChanged because I've used that before and I know it works. Keep in mind that afterTextChanged is called every time a LETTER changes, not just after the user finishes typing a word. If the user types "dog" afterTextChanged will be called three times, first with 'd', then with 'do', then with 'dog'.
A HashMap is simple: Map yourMapOfPositionsToValues = new HashMap();
to add or update an item: yourMap.put(position, someText);
to fetch an item: yourMap.get(position);
if hashmaps don't make sense, spend some time researching them. They are an incredibly important data structure.
Your TextWatcher implementation is incorrect. Your data structure should not belong to a single view, but rather the activity or your adapter. It appears to you that positions aren't stable because your List is owned by each view. The positions themselves are stable in that unless the underlying data changes the cursor will return the same data every time for the same position. However, the edit text is used for multiple different positions.
Create a hashmap as an instance variable I demonstrated above in the constructor of your adapter. Then add exactly the TextWatcher I wrote originally, no need for a named class, anonymous is simpler. Your code should work.
The solution to this is removing the added textwatcher before setting the text. Otherwise, the previous textwatcher on that view will still be called along with the new textwatcher. Store the textwatcher as a tag on the EditText to keep track of it.
Object oldWatcher = viewHolder.quantitySold.getTag();
if(oldWatcher != null){
viewHolder.quantitySold.removeTextChangedListener((CustomTextWatcher)oldWatcher);
}
String oldText = inputValues.get("key"+position);
Log.d(TAG, "oldText: "+oldText+" position: "+position);
viewHolder.quantitySold.setText(oldText == null ? "" : oldText);
CustomTextWatcher watcher = new CustomTextWatcher(
cursor.getString(SKUFragment.COL_NAME),
cursor.getInt(SKUFragment.COL_ID),
cursor.getDouble(SKUFragment.COL_UNIT_PRICE),
position
) {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
if (s != null) {
int quantity = 0;
if (!TextUtils.isEmpty(s.toString())) {
quantity = Integer.parseInt(s.toString());
inputValues.put("key"+mPosition, "" + quantity);
}else{
inputValues.put("key"+mPosition, "");
}
double value = quantity * skuPrice;
mListener.onQuantityChanged(skuName+", position: "+mPosition, skuId, quantity, value);
}
}
};
viewHolder.quantitySold.setTag(watcher);
viewHolder.quantitySold.addTextChangedListener(watcher);

Android ViewHolder in CursorAdapter causing listView to get screwed

I've been struggeling in the past few days trying to figure this out, I hope you can help me...
I have an Activity that shows a list of Players by setting a listadapter like this:
PlayerCursorAdapter playerAdapter = new PlayerCursorAdapter(this,
R.layout.players_row, c, columns, to);
setListAdapter(playerAdapter);
When clicking an item in the list, this code will be executed showing a dialog with an "Edit" and "Delete" option for editing and removing players:
private class OnPlayerItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position,
long rowId) {
Toast.makeText(view.getContext(),
"Clicked Item [" + position + "], rowId [" + rowId + "]",
Toast.LENGTH_SHORT).show();
// Prepare Dialog with "Edit" and "Delete" option
final CharSequence[] choices = {
view.getContext().getString(R.string.buttonEdit),
view.getContext().getString(R.string.buttonDelete) };
AlertDialog.Builder builder = new AlertDialog.Builder(
view.getContext());
builder.setTitle(R.string.title_edit_delete_player);
builder.setItems(choices, new EditOrDeleteDialogOnClickListener(
view, rowId));
AlertDialog alert = builder.create();
// Show Dialog
alert.show();
}
Based on your choice (Edit or delete player), the following listener will be executed:
private class EditOrDeleteDialogOnClickListener implements
DialogInterface.OnClickListener {
private View view;
private long rowId;
public EditOrDeleteDialogOnClickListener(View view, long rowId) {
this.view = view;
this.rowId = rowId;
}
public void onClick(DialogInterface dialog, int item) {
if (item == 0) {
// Edit
showDialog(PlayGameActivity.DIALOG_EDIT_PLAYER_ID);
} else if (item == 1) {
// Delete from database
DatabaseHelper databaseHelper = new DatabaseHelper(
view.getContext());
databaseHelper.deletePlayer(rowId);
// Requery to update view.
((PlayerCursorAdapter) getListAdapter()).getCursor().requery();
Toast.makeText(
view.getContext(),
view.getContext().getString(
R.string.message_player_removed)
+ " " + rowId, Toast.LENGTH_SHORT).show();
}
}
}
The code for the adapter is here:
public class PlayerCursorAdapter extends SimpleCursorAdapter {
private LayoutInflater layoutInflater;
private int layout;
public PlayerCursorAdapter(Context context,
int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c, from, to);
this.layout = layout;
layoutInflater = LayoutInflater.from(context);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
Cursor c = getCursor();
View view = layoutInflater.inflate(layout, parent, false);
// Get Data
int nameCol = c.getColumnIndex(Player.COLUMN_PLAYER_NAME);
String name = c.getString(nameCol);
int gamesPlayedCol = c.getColumnIndex(Player.COLUMN_GAMES_PLAYED);
String gamesPlayed = c.getString(gamesPlayedCol);
int gamesWonCol = c.getColumnIndex(Player.COLUMN_GAMES_WON);
String gamesWon = c.getString(gamesWonCol);
// Set data on fields
TextView topText = (TextView) view.findViewById(R.id.topText);
if (name != null)
topText.setText(name);
TextView bottomText = (TextView) view.findViewById(R.id.bottomText);
if (gamesPlayed != null && gamesWon != null)
bottomText.setText(view.getContext().getString(
R.string.info_played_won)
+ gamesPlayed + "/" + gamesWon);
CheckBox checkBox = (CheckBox) view.findViewById(R.id.checkBox);
// Set up PlayerViewHolder
PlayerViewHolder playerViewHolder = new PlayerViewHolder();
playerViewHolder.playerName = name;
playerViewHolder.gamesPlayed = gamesPlayed;
playerViewHolder.gamesWon = gamesWon;
playerViewHolder.isChecked = checkBox.isChecked();
view.setTag(playerViewHolder);
return view;
}
private class PlayerViewHolder {
String playerName;
String gamesPlayed;
String gamesWon;
boolean isChecked;
}
#Override
public void bindView(View view, Context context, Cursor c) {
PlayerViewHolder playerViewHolder = (PlayerViewHolder) view.getTag();
TextView topText = (TextView) view.findViewById(R.id.topText);
topText.setText(playerViewHolder.playerName);
TextView bottomText = (TextView) view.findViewById(R.id.bottomText);
bottomText.setText(view.getContext()
.getString(R.string.info_played_won)
+ playerViewHolder.gamesPlayed
+ "/"
+ playerViewHolder.gamesWon);
CheckBox checkBox = (CheckBox) view.findViewById(R.id.checkBox);
checkBox.setChecked(playerViewHolder.isChecked);
}
}
Now, the problem is that after removing a few of the players in the list, the list gets screwed up, eg. it shows something different than what is actually available.
I've experimented a little and if I stop using the PlayerViewHolder in bindView and instead read the text from the cursor and assign it directly to the text fields, then it works.... So question is, why is my ViewHolder screwing up things???
Any help will be greatly appreciated!
Thanks!
Zyb3r
Found a solution...
Basically I reinitialize the Cursor and ListAdapter plus assigns the ListAdapter to the ListView all over again when I change the data in the database.
I'm not entirely sure why this is nessasary, but notifyDataSetChanged(), notifyDataSetInvalidated() and all the other things I tried didn't work, so now I'm using this approach. :o)
Zyb3r

Categories

Resources