Android : Updating row layouts based on User Input with a CursorAdapter - android

I am currently working on an Application wich displays data from a SQLiteDatabase in a listview. This listview is using custom layouts. My intent was to have three different layouts, depending on what the user is doing. The first layout only displays one TextView and a Button (called row_nameonly). If the user presses this button, the layout would switch to a more detailed view (called row_viewentry). Finnaly, if the user presses the button again, the first layout is displayed once more (row_nameonly). I have tried to accomplish this, but have not found a working solution. My current version seems to change the View of the row, but the new layout is not visible. I wish to do this without having to add extra data to the database.
This is the code of the Custom CursorAdapter :
public class EntryListCursorAdapter extends CursorAdapter{
public int viewRequestPosition = -100;
public EntryListCursorAdapter(Context context, Cursor c) {
super(context, c, 0);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.row_nameonly, parent, false);
}
#Override
public void bindView(View view, final Context context, final Cursor cursor) {
ViewGroup parent = (ViewGroup) view.getParent();
if(cursor.getPosition() == viewRequestPosition){
view = LayoutInflater.from(context).inflate(R.layout.row_viewentry, parent, false);
}
if(!(cursor.getPosition() == viewRequestPosition)) {
TextView nameView = (TextView) view.findViewById(R.id.txt_name);
ImageButton expandMoreButton = (ImageButton) view.findViewById(R.id.btn_expandmore);
String name = cursor.getString(cursor.getColumnIndexOrThrow("prename")) + " " +
cursor.getString(cursor.getColumnIndexOrThrow("surname"));
nameView.setText(name);
expandMoreButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
viewRequestPosition = cursor.getPosition();
notifyDataSetChanged();
}
});
}
}
TLDR: Is there a way to have the CursorAdapter change the layout of the view in wich the button was clicked, and have the new layout displayed, without adding extra data to the SQLiteDatabase ?
Thank you
(If you need any more information please ask)

In case someone is curious,
I had the Adapter take the Fragment calling it in its constructor. Then i saved the position of the ListItems i wanted to change the layout of in the fragment wich called the adapter. Then i created a new instance of the adapter, but this time the layout would changed based on the positions i saved in the fragment.

Related

onClickListener for custom ListView item with multiple Buttons

In my program, I have a Fragment which dynamically adds custom items to a ListView. These items each have 2 Buttons, which are supposed to have a certain functionality (like removing the specific item from the list). I would like to set an OnClickListener for them, calling a function within my Fragment Class.
Simple xml onClick attributes cannot find the function and when I try to add the onClickListener within the onCreateView() method (like with Listeners for static Buttons). But when the fragment is created, a NullPointerException is called, because the referenced Buttons are not part of the Fragment (?).
How can I call a function from my Fragment after pressing a dynamically added Button (or Checkbox)?
this is easier if you post some code
In my code I have something similar, I have one listview with a Array Adapter, and in some line I have one imageButton with a funtion.
I solve my problem with a class to work on my adapter and in that that I apply the functionaly to the Button.
Main_Class code :
ListViewResources listViewResources = new ListViewResources(this, resourcesName);
listView = (ListView) popupView.findViewById(R.id.listView1);
listView.setAdapter(listViewResources);
ListViewResouces_Class:
public class ListViewResources extends ArrayAdapter<String>{
Activity context;
String[] resourcesName;
public ListViewResources(Activity context, String[] resourcesName) {
super(context, R.layout.popup_listitem_resources, resourcesName);
this.context = context;
this.resourcesName = resourcesName;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View rowView = inflater.inflate(R.layout.popup_listitem_resources, null, true);
TextView resourceName = (TextView)rowView.findViewById(R.id.textViewResourceName);
ImageView openResource = (ImageView)rowView.findViewById(R.id.buttonOpenResource);
openResource.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//code for happen on Button Click
}
});
resourceName.setText(resourcesName[position]);
return rowView;
}

Set visibility for an image within a ListView row

Overview:
I have a ListView with a custom adapter/layout, every time a user adds a new row (which contains a number), I check if that number is the smallest in the list. If so, an image within that row must be set as visible while setting all other row's images as invisible.
Problem:
My ListView does not set any row's image as visible, even though I have the index of the smallest element.
How I'm doing it:
//In MainActivity
private void addProduct(float price) { //User adds product
priceList.add(price); //Add to Float list
adapter.notifyDataSetChanged();
updateView(findMinIndex(priceList)); //Find smallest val indx
}
private void updateView(int index){
View v = listView.getChildAt(index -
listView.getFirstVisiblePosition());
if(v == null)
return;
ImageView checkMark = (ImageView) v.findViewById(R.id.check_mark);
checkMark.setVisibility(View.VISIBLE); //Initially set Invisible
}
Edit, CustomAdapter:
public CustomList(Activity context,
ArrayList<Float> priceList) {
super(context, R.layout.list_single, priceList);
this.context = context;
priceList = priceList;
}
#Override
public View getView(int position, View view, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View rowView = inflater.inflate(R.layout.list_single, null, true);
TextView price = (TextView) rowView.findViewById(R.id.new_price);
ImageView cheapest = (ImageView) rowView.findViewById(R.id.check_mark);
price.setText(priceList.get(position) + "");
return rowView;
}
Thank you
It is your priceList binded with the adapter?
First of all i would put a breakpoint to see if you are getting the right view in the updateView method.
try this way;
Create a Pojo class with imageview and it's state(Visibility) initially set all to invisible
Add your items to the ArrayList of Pojo Class type.
when user enters a new row based on your requirement set visibility state to true or false(visible or invisible) and call notifyDataSetChanged() to the adapter.
Doing this way you can have a easy track of the items.
I got it working :).
Problem is that adapter.notifyDataSetChanged(); is async, so while it's doing that, updateView(findMinIndex(priceList)); runs but doesn't find the new row as it should. Therefore, I add a runnable to the ListView object as so:
adapter.notifyDataSetChanged();
listView.post( new Runnable() {
#Override
public void run() {
updateView(findMinIdx(priceList));
}
});
Now it works perfectly!

CursorAdapter - List beheivour

I use a CursorAdapter with one single customized layout for my list, depending on the value of a field (true or false) of my table, the color of one of the textviews in the list item will be different.
public class CustomAdapter extends CursorAdapter {
DatabaseHelper myDB;
SQLiteDatabase db;
public CustomAdapter(Context context, Cursor cursor, int flags) {
super(context, cursor, 0);
myDB = new DatabaseHelper(context, DatabaseHelper.DB_NAME, null, DatabaseHelper.DB_VERSION_SCHEME);
db = myDB.getReadableDatabase();
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.customize_cell_list, parent, false);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
TextView tv = (TextView) view.findViewById(R.id.tv);
TextView tv_date = (TextView) view.findViewById(R.id.tvDateCursor);
TextView tv_time = (TextView) view.findViewById(R.id.tvTime);
ImageView img = (ImageView)view.findViewById(R.id.img);
if(cursor != null) {
String tv = cursor.getString(2);
String date = cursor.getString(3);
String time = cursor.getString(8);
boolean active = cursor.getInt(12) > 0;
boolean go = cursor.getInt(13) > 0;
tv_date.setText(date);
tv.setText(tv);
if(go) { // OPTION 1
img.setImageResource(R.drawable.calle);
tv_time.setText(time);
} else { // OPTION 2
img.setImageResource(R.drawable.casa);
if(active){ // OPTION 2.1
tv_time.setText("Activado");
}else{ // OPTION 2.2
tv_time.setText("No activado");
tv_time.setTextColor(Color.RED);
}
}
}
}
}
The result: images display the correct one the same than the textviews, the problem is the color, when I have one item with the OPTION 2.2 and start scrolling up and down some of my tv_time (even from the option 1) begin to change the color to Red, then I scroll again and they become white and others change to red, it is something random.
Why is this happening? How can I keep the red color only when "go" and "active" are false?
Thanks
You need to remember the following things
1. The views in the ListView are limited.
2. Whenever you scroll the View whose visibility is gone will be updated with the later items and then it will come back to the view
So now you are setting the color based on some criteria. and whenever you are binding the view u are setting the color.
Make sure when unbinding the view you will make the view go back to the original color. if u don't do this the View when looses its visibility it still contains the color and when other data item is fetched into it that looks in red color.
So when binding first check whether it is colored red. If it is then check do you want to keep it red, and yes keep it and no then make it go back to original color

Android onlistclick toggle listview row height causing muliple changes

Situation:
My application contains a ToDo-list styled listview (each row has a checkbox). The listview rows are structured with 2 textviews laid out vertically. The topmost text is the title and bottommost is the description, however the description is hidden (View.GONE). Using the ListActivities onListItemClick method I can set both the height and visibility of the pressed row.
#Override
protected void onListItemClick(ListView list, View view, int position, long id) {
super.onListItemClick(list, view, position, id);
view.getLayoutParams().height = 200;
view.requestLayout();
TextView desc = (TextView) view.findViewById(R.id.description);
desc.setVisibility(View.VISIBLE);
}
Note: Code is stripped to the most basic
The above code works fine, except that it expands both the pressed row as well as the 10th row above or below (next unloaded view?). The row expansion will also change place when list is flinged.
Background:
The listview data is retrieved from a SQLite Database through a managed cursor and set by a custom CursorAdapter. The managed cursor is sorted by checkbox value.
private void updateList() {
todoCursor = managedQuery(TaskContentProvider.CONTENT_URI, projection, null, null, TaskSQLDatabase.COL_DONE + " ASC");
startManagingCursor(todoCursor);
adapter = new TaskListAdapter(this, todoCursor);
taskList.setAdapter(adapter);
}
The CursorAdapter consists of the basic newView() and bindView().
Question:
I require some system which keeps track of which rows are expanded. I have tried storing the cursor id's in arrays and then checking in the adapter if row should be expanded, but I can't seem to get it working. Any suggestions would be much appreciated!
The ListView will recycle views as you scroll it up and down, when it needs a new child View to show, it will first see if it doesn't already have one(if it finds one it will use it). If you modify the children of a ListView like you did(in the onListItemClick() method) and then scroll the list, the ListView will eventually end up reusing that child View that you modified and you'll end up with certain views in position that you don't want(if you continue to scroll the ListView you'll see random position changing because of the View recycling).
One way to prevent this is to remember those positions that the user clicked and in the adapter change the layout of that particular row but only for the position that you want. You can store those ids in a HashMap(a field in your class):
private HashMap<Long, Boolean> status = new HashMap<Long, Boolean>();
I used a HashMap but you can use other containers(in the code bellow will see why I choose a HashMap). Next in the onListItemClick() method you'll change the clicked row, but also store that row id so the adapter will know(and take measures so you don't end up with wrong recycled views):
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// check and see if this row id isn't already in the status container,
// if it is then the row is already set, if it isn't we setup the row and put the id
// in the status container so the adapter will know what to do
// with this particular row
if (status.get(id) == null) {
status.put(id, true);
v.getLayoutParams().height = 200;
v.requestLayout();
TextView d = (TextView) v.findViewById(R.id.description);
d.setVisibility(View.VISIBLE);
}
}
Then in the adapter use the status container with all the ids to setup the rows correctly and prevent the recycling to mess with our rows:
private class CustomAdapter extends CursorAdapter {
private LayoutInflater mInflater;
public CustomAdapter(Context context, Cursor c) {
super(context, c);
mInflater = LayoutInflater.from(context);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
TextView title = (TextView) view.findViewById(R.id.title);
title.setText(cursor.getString(cursor.getColumnIndex("name")));
TextView description = (TextView) view
.findViewById(R.id.description);
description.setText(cursor.getString(cursor
.getColumnIndex("description")));
// get the id for this row from the cursor
long rowId = cursor.getLong(cursor.getColumnIndex("_id"));
// if we don't have this rowId in the status container then we explicitly
// hide the TextView and setup the row to the default
if (status.get(rowId) == null) {
description.setVisibility(View.GONE);
// this is required because you could have a recycled row that has its
// height set to 200 and the description TextView visible
view.setLayoutParams(new AbsListView.LayoutParams(
AbsListView.LayoutParams.MATCH_PARENT,
AbsListView.LayoutParams.MATCH_PARENT));
} else {
// if we have the id in the status container then the row was clicked and
// we setup the row with the TextView visible and the height we want
description.setVisibility(View.VISIBLE);
view.getLayoutParams().height = 200;
view.requestLayout();
// this part is required because you did show the row in the onListItemClick
// but it will be lost if you scroll the list and then come back
}
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View v = mInflater.inflate(R.layout.adapters_showingviews, null);
return v;
}
}
If you want to toggle the row clicked(and something tells me that you want this), show the TextView on a click/hide the TextView on another row clicked then simple add a else clause to the onListItemClick() method and remove the clicked row id from the status container and revert the row:
//...
else {
status.remove(id);
v.setLayoutParams(new AbsListView.LayoutParams(
AbsListView.LayoutParams.MATCH_PARENT,
AbsListView.LayoutParams.MATCH_PARENT));
TextView d = (TextView) v.findViewById(R.id.description);
d.setVisibility(View.GONE);
}

Different layout for the items in the listview

I have some extended cursor adapter, in witch I call super with the context and the resource layout for the item in the list, something like this.
call to super in my adapter:
super(activity, viewResource, c, false);
creation of my adapter:
new MyCursorAdapter(this, null, R.layout.my_list_item, null);
What I want to achieve is something like my stupid mock up made in paint.
Put into words I want to have different kinds of layout for the items, for example I want all even items to have layout1 and all odd to have the layout2. So far I am able to give only one layout in this case R.layout.my_list_item. Is it possible to dynamically change the layout ?
Is it possible to construct the adapter to have items with different layout ? My goal is to dynamically chose the layout of the item. I do not want to have just one layout for all of the items I want to have foe example two...
Thanks
Yes, you're going to have to do two things though. First, override the getItemViewType() method in your adapter so that you can be sure your bindView() only get's views that appropriate for a particular position in the list, like so:
public int getItemViewType(int position){
if(correspondsToViewType1(position)){
return VIEW_TYPE_1;
}
else(correspondsToViewType2(position)){
return VIEW_TYPE_2;
}
//and so on and so forth.
}
Once you do that, just have a simple test in your bindView() that checks to see what type of view it should have recieved and setup things accordingly like so:
public void bindView(View view, Context context, Cursor cursor){
if(correspondsToViewType1(cursor)){
//Now we know view is of a particular type and we can do the
//setup for it
}
else if(correspondsToViewType2(cursor){
//Now we know view is of a different type and we can do the
//setup for it
}
}
Note that you're going to have to have to different methods for correpondsToViewType, one that takes a cursor and one that takes an int (for a position). The implementation for these will vary depending on what you want to do.
Note that doing things this way will allow you to reuse potentially recycled views. If you don't do this, your app is going to take a huge performance hit. Scrolling will be super choppy.
I'm guessing your extending SimpleCursorAdapter from the name of your custom adapter. You will want to override the function getView in your adapter and depending on the object in the list inflate a different layout and return that view.
EX:
#Override
public View getView (int position, View convertView, ViewGroup parent)
{
Object myObject = myList.get(position);
if(convertView == null)
{
if( something to determine layout )
convertView = inflater.inflate(Layout File);
else
convertView = inflater.inflate(Some Other Layout File);
}
//Set up the view here, such as setting textview text and such
return convertView;
}
This is just an example and is somewhat sudo code so it will need some adjustments for your specific situation.
Just override the newView Method:
public class MyCursorAdapter extends CursorAdapter {
private final LayoutInflater inflater;
private ContentType type;
public MyCursorAdapter (Context context, Cursor c) {
super(context, c);
inflater = LayoutInflater.from(context);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
if( cursor.getString(cursor.getColumnIndex("type")).equals("type1") ) {
// get elements for type1
} else {
// get elements for type1
}
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
if( cursor.getString(cursor.getColumnIndex("type")).equals("type1") ) {
final View view = inflater.inflate(R.layout.item_type1, parent, false);
} else {
final View view = inflater.inflate(R.layout.item_type2, parent, false);
}
return view;
}

Categories

Resources