I'm having a list View with each item composed of a collection of Textviews and a CheckBox.
I'm storing the state of the checkbox in the DB and updating it from a on clickListener .It works fine for the controls that are visible.By default all the checkbox's are in the checked state.
If there are 10 items and screen can accommodate 7, then when I Uncheck the first one and scroll to the 10th item and again scroll back to the first one. The first one looses its state( it gets checked again).I checked the DB for the rows state, which is reflected correctly. But the fetch in the BindView always get me the wrong state. I'm not able to pin down where the issues is. I have attached the list adaptor along with this for review...
// List Adaptor code
public class ListAdaptor extends CursorAdapter {
private LayoutInflater mInflater;
Cursor dataCursor;
Context context;
ListView mLv;
private static final String TAG = "Delete";
public ListAdaptor(Context context, Cursor cursor, ListView lv)
{
super(context, cursor);
this.context = context;
mLv = lv;
mInflater = LayoutInflater.from(context);
}
#Override
public void bindView(View view, Context context, final Cursor cursor) {
// Get the stored tag for the view
CheckBox tmp_Chk = (CheckBox)view.findViewById(R.id.chkbox);
String selText = cursor.getString(11);
// Debug Message
int val = cursor.getPosition();
tmp_Chk.setChecked(false);
SparseBooleanArray sba = mLv.getCheckedItemPositions();
if(sba != null)
if(sba.get(cursor.getPosition()))
tmp_Chk.setChecked(true);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent)
{
View newView = mInflater.inflate(R.layout.listviewlyt, null);
return newView;
}
}
// Item layout
<?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="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="#+id/chkbox"
android:focusable="false"
android:clickable="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</CheckBox>
<TextView
android:id="#+id/label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#+id/label"
android:textSize="18sp"
android:textColor="#000000" >
</TextView>
</LinearLayout>
// List control code in the Main Activity
lv.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int lv_Pos = ListView.INVALID_POSITION;
CheckBox tmp_Chk = (CheckBox)view.findViewById(R.id.chkbox);
if (lv_Pos != ListView.INVALID_POSITION) {
if(tmp_Chk.isChecked()){
Check_Uncheck(Integer.toString(lv_Pos + 1), 1);
}
else if(!tmp_Chk.isChecked()){
Check_Uncheck(Integer.toString(lv_Pos + 1), 0);
}
}
public void Check_Uncheck(String deleteItem , int select)
{
// Initialize database
DB dbAdapters = DB.getDBAdapterInstance(TabActivity.this);
dbAdapters.openDataBase();
ContentValues cv_InitialValues = new ContentValues();
cv_InitialValues.put("Selection", select);
dbAdapters.b_UpdateRecordInDB("Items", cv_InitialValues, "_id=?", new String[] {deleteItem});
dbAdapters.close();
}
});
// List view XML Properties in the Main activity
<ListView
android:id="#+id/LV_Instore_CartTab"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textSize="2px"
android:layout_weight="1"
android:choiceMode="multipleChoice"/>
You will want to override getView in your adapter. This function is called as views come into view when the list is scrolled. The convertView variable is the view that has just gone out of view as you scroll and is being reused. The convertView may be null so you should check if it is null and inflate a new row layout if it is null, otherwise you can use the convertView just like you would if you had inflated it. Now what is happening to you is that the view is being reused but you are not setting the state back to how you want it in the getView function. In this function you should use the position variable passed in to determine which item in your list the view is being connected with. If you store the state of the check in the list of objects you can then use the position to get the correct item out of your list. Use the object you retrieve from the list to either check or uncheck the checkbox in your row.
I am not really aware of CompundButton, but can you try the below code snippet,
tmp_Chk.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if ( !tmp_Chk.isChecked()) {
tmp_Chk.setChecked(false);
Check_Uncheck();
} else {
Check_Uncheck();
tmp_Chk.setChecked(true);
}
}
});
if (cursor.getInt(11) == 0) {
tmp_Chk.setChecked(false);
} else {
tmp_Chk.setChecked(true);
}
Related
I am creating a ListView inside of a popupWindow. Currently, i have my list view showing and parts will update as of now, but if click an index to change the background color, scroll to put it off the page, then scroll back to that index, it doesn't retain my change (which was a text change). Also, as of now, another problem is that the "parent.getChildAt(position).setBackgroundColor(Color.BLUE);" causes an error if i click on specific indexes.
Here is the part of the code that contains my popupwindow and the list view
public void bringUpEventsPopover() {
final LayoutInflater layoutInflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//inflate the custom popup layout
final View inflatedView = layoutInflater.inflate(R.layout.popup_layout,null,false);
//find the listeview in the popup layout (we will need to construct this still)
final ListView listView = (ListView)inflatedView.findViewById(R.id.commentsListView);
//get device size
/*Display display = getActivity().getWindowManager().getDefaultDisplay();
final Point size = new Point();
display.getSize(size);*/
int width = view.getWidth();
int height = view.getHeight();
//fill the data to the list items
setSimpleList(listView);
//set the height depends on the device size
//popWindow= new PopupWindow(inflatedView, size.x, size.y, true);
popWindow= new PopupWindow(inflatedView, width, height-50, true);
//make it focusable to show the keyboard to enter in 'edittext'
popWindow.setFocusable(true);
popWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
//show the popup at bottom of the screen and set some margin at bottom
popWindow.showAtLocation(view, Gravity.CENTER, 0, 0);
//make it outside touchable to dismiss the popup window when touched outside the popup area
popWindow.setOutsideTouchable(true);
popWindow.update();
final FrameLayout mainFrame = (FrameLayout) getActivity().findViewById(R.id.frametop);
mainFrame.getForeground().setAlpha(160);
popWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
#Override
public void onDismiss() {
mainFrame.getForeground().setAlpha(0);
}
});
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id)
{
// selected item
//Event item = (Event)listView.getItemAtPosition(position);
TextView tv = (TextView)view.findViewById(R.id.eventCreator);
parent.getChildAt(position).setBackgroundColor(Color.BLUE);
}
});
}
void setSimpleList(ListView listView){
eventList = new ArrayList<>();
for (int index = 0; index < 6; index++) {
eventList.add(new Event("BT","We gon play sports and stuff","Title","Time is "+index+"pm today","3 Hours","15/30"));
}
for(Event item: eventList)
{
System.out.println(item.getTitle()+"\n"+item.getTime()+"\n"+item.getNumPeople()+"\n");
}
adapter = new ItemAdapter(getActivity(),R.layout.view_event,eventList);
listView.setAdapter(adapter);
}
This is my ItemAdapter class for the ListView
public class ItemAdapter extends ArrayAdapter<Event> {
// declaring our ArrayList of items
private ArrayList<Event> objects;
/* here we must override the constructor for ArrayAdapter
* the only variable we care about now is ArrayList<Item> objects,
* because it is the list of objects we want to display.
*/
public ItemAdapter(Context context, int textViewResourceId, ArrayList<Event> objects) {
super(context, textViewResourceId, objects);
this.objects = objects;
}
/*
* we are overriding the getView method here - this is what defines how each
* list item will look.
*/
public View getView(int position, View convertView, ViewGroup parent){
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// assign the view we are converting to a local variable
View v = convertView;
// first check to see if the view is null. if so, we have to inflate it.
// to inflate it basically means to render, or show, the view.
if (v == null) {
v = inflater.inflate(R.layout.view_event, null);
v.setTag(position);
}
else
{
v.getTag();
}
/*
* Recall that the variable position is sent in as an argument to this method.
* The variable simply refers to the position of the current object in the list. (The ArrayAdapter
* iterates through the list we sent it)
*
* Therefore, i refers to the current Item object.
*/
Event i = objects.get(position);
if (i != null) {
// This is how you obtain a reference to the TextViews.
// These TextViews are created in the XML files we defined.
TextView creator = (TextView) v.findViewById(R.id.eventCreator);
TextView title = (TextView) v.findViewById(R.id.eventTitle);
TextView description = (TextView) v.findViewById(R.id.eventDescription);
TextView time = (TextView) v.findViewById(R.id.eventTime);
TextView duration = (TextView) v.findViewById(R.id.eventDuration);
TextView numPeople = (TextView) v.findViewById(R.id.eventAttendance);
// check to see if each individual textview is null.
// if not, assign some text!
if(creator!=null)
{
creator.setText("Creator: "+i.getCreator());
}
if (title != null){
title.setText("Title: "+i.getTitle());
}
if(description!=null)
{
description.setText("Description: "+i.getDescription());
}
if( time != null) {
time.setText("Time: "+i.getTime());
}
if(duration!=null)
{
duration.setText("Duration: "+i.getDuration());
}
if (numPeople != null){
numPeople.setText(i.getNumPeople());
}
}
// the view must be returned to our activity
return v;
}
}
and this is the layout for each list item
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/eventCreator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="false"
android:text="Creator: "
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_margin="5dp" />
<TextView
android:id="#+id/eventTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/eventCreator"
android:text="Title: "
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_margin="5dp" />
<TextView
android:id="#+id/eventDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/eventTitle"
android:text="Description: "
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_margin="5dp" />
<TextView
android:id="#+id/eventTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/eventDescription"
android:text="Time: "
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_margin="5dp" />
<TextView
android:id="#+id/eventDuration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/eventTime"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Duration: "
android:layout_margin="5dp" />
<TextView
android:id="#+id/eventAttendance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/eventDuration"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="15/30 Going"
android:layout_margin="5dp" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/eventAttendance"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_margin="20dp"
android:text="I'm Going!"
android:layout_centerHorizontal="true"
android:focusable="false"
android:clickable="false"/>
</RelativeLayout>
Two thoughts. 1) Maybe consider a container for your events/items so that you can give them dynamic ids and then use those to set the background color. You might have more luck saving the state when it roll on/off the screen. 2) I'm not 100% sure on this, so don't take it as gospel, but I think when you roll a list view out of view, the adaptor may mess with the index variable in the child structure. Is there another way you can access the child nodes of that list view? Sorry I don't have a good answer for that one, just an idea for you to chase down maybe. Good luck!
Jon
I figured it out on my own. So the Array Adapter has several parts to it when setting it up.
// first check to see if the view is null. if so, we have to inflate it.
// to inflate it basically means to render, or show, the view.
if (v == null) {
/* This is where you initialize new rows, by:
* - Inflating the layout,
* - Instantiating the ViewHolder,
* - And defining any characteristics that are consistent for every row */
v = inflater.inflate(R.layout.view_event, null);
v.setTag(position);
}
else
{
/* Fetch data already in the row layout,
* primarily you only use this to get a copy of the ViewHolder */
v.getTag();
}
/* Set the data that changes in each row, like `title` and `size`
* This is where you give rows there unique values. */
so i setup a new variable in my Event Class to hold an "isSelected" field, which my "onItemClickListener" sets, and then notifies the ArrayAdapter to a data change.
In the Array Adapter, i just did a check for the Event.isSelected() var and loaded background accordingly.
myArrayAdapter that alters background:
if(objects.get(position).isSelected())
{
v.setBackgroundColor(Color.CYAN);
}
else
{
v.setBackgroundColor(Color.WHITE);
}
myItemClickListener:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id)
{
// selected item
adapter.objects.get(position).setSelected(true);
adapter.notifyDataSetChanged();
}
});
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 am developing an extremely simple list app to save items to an SQLite DB, and populate a ListView with it's contents.
This can be done using a SimpleCursorAdapter, which I do like this:
mySQLiteAdapter.openToRead();
cursor = mySQLiteAdapter.queueAll();
String[] from = new String[]{mySQLiteAdapter.getKeyContent()};
int[] to = new int[]{R.id.normal};
cursorAdapter = new SimpleCursorAdapter(this, R.layout.row, cursor, from, to, 0);
listView.setAdapter(cursorAdapter);
mySQLiteAdapter.close();
Now however I would like to implement the ability to either strike out text on a row by click or long click, or change the row to a different layout (which could also strike the text)
At first I simply set the onClickListener for the listview and changed the paint flags to strike the text. This works fine until there are enough items in the list to scroll the view, or until the activity is reloaded. In the latter the strike is gone (since nothing was persistent), and in the former other rows are striked, and the intended ones are not. Then is changes as you scroll around. See Custom ListView adapter, strange ImageView behavior for a similar situation to my own.
From this I have found that I will need use a custom adapter to do what I want. So I have created a CustomCursorAdapter which extends SimpleCursorAdapter and overriden some methods to attempt to inflate a seperate layout with a background colour. I am not having much luck.
Here is what I have so far:
// Creating a new instance of the custom adapter and assigning it to the listview
mySQLiteAdapter.openToRead();
cursor = mySQLiteAdapter.queueAll();
String[] from = new String[]{mySQLiteAdapter.getKeyContent()};
int[] to = new int[]{R.id.normal};
cursorAdapter = new CustomCursorAdapter(this, R.layout.row, cursor, from, to, 0);
listView.setAdapter(cursorAdapter);
mySQLiteAdapter.close();
and
// CustomCursorAdapter class
private class MyCursorAdapter extends SimpleCursorAdapter {
private LayoutInflater inflater;
public MyCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {
super(context, layout, c, from, to, flags);
inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get reference to the row
View view = super.getView(position, convertView, parent);
//View view;
if (getItemViewType(position) == 0) {
view = inflater.inflate(R.layout.row, null);
}
else {
view = inflater.inflate(R.layout.rowstrike, null);
}
return view;
}
#Override
public int getItemViewType(int position) {
int row;
if (cursor.getInt(cursor.getColumnIndex(mySQLiteAdapter.getKeyStrike())) == 1) {
Log.d("DEBUG", "Row " + position + " is STRIKED");
row = 1;
}
else {
Log.d("DEBUG", "Row " + position + " is normal");
row = 0;
}
return row;
}
}
The implementation of the CustomCursorAdapter works and shows the correct amount of rows. The logic to determine if the row should contain striked text by querying the db is correct, however the returned inflated views are completely blank. I think it may be to do with the way my XML files are arranged and which ones I pass to the adapter but all my testing and tinkering to try to get this working have failed spectacularly so far.
Here are my XML files for the layouts
// activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
tools:context=".MainActivity">
<EditText
android:layout_width="fill_parent"
android:layout_height="35dp"
android:id="#+id/editText"
android:hint="Press here to add an item"
android:maxLines="1"
android:imeOptions="actionDone"
android:inputType="textAutoCorrect"/>
<ListView
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:id="#+id/listView"
android:layout_below="#id/editText"/>
</RelativeLayout>
.
// row.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp" >
<TextView
android:id="#+id/normal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"/>
</RelativeLayout>
.
//rowstrike.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp" >
<TextView
android:id="#+id/striked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#F05"/>
</RelativeLayout>
I have been searching on this for days and days, each time getting closer but nothing seems to work, or the explanations are not beginner friendly for someone like myself.
The closest post I have found to what I am after is
ListView view recycling with CustomAdapter
However I think I need more code snippets as I must be doing something wrong elsewhere in my app?
There is a mention of overriding the getViewTypeCount method but I am unsure of how this is done...
There is also
How would I use a different row layout in custom CursorAdapter based on Cursor data?
However I am not quite sure where to go from this post...
EDIT: Solution based on Luksprogs post.
mySQLiteAdapter.openToRead();
cursor = mySQLiteAdapter.queueAll();
String[] from = new String[]{mySQLiteAdapter.getKeyContent()};
int[] to = new int[]{R.id.normal};
cursorAdapter = new SimpleCursorAdapter(this, R.layout.row, cursor, from, to, 0);
cursorAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
#Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
if (view.getId() == R.id.normal) {
TextView tv = (TextView)view.findViewById(R.id.normal);
if (cursor.getInt(cursor.getColumnIndex(mySQLiteAdapter.getKeyStrike())) == 1) {
tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}
else {
tv.setPaintFlags(tv.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
}
}
return false;
}
});
listView.setAdapter(cursorAdapter);
mySQLiteAdapter.close();
and I have an OnItemClickListener as follows to strike and unstrike rows
private ListView.OnItemClickListener listViewOnItemClickListener
=new ListView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final int rowID = cursor.getInt(cursor.getColumnIndex(mySQLiteAdapter.getKeyID()));
mySQLiteAdapter.openToWrite();
if (cursor.getInt(cursor.getColumnIndex(mySQLiteAdapter.getKeyStrike())) == 0) {
mySQLiteAdapter.setKeyStrike(rowID, 1);
}
else {
mySQLiteAdapter.setKeyStrike(rowID, 0);
}
cursor = mySQLiteAdapter.queueAll();
cursorAdapter.swapCursor(cursor);
mySQLiteAdapter.close();
};
however the returned inflated views are completely blank.
As a side note, you shouldn't override the getView() method of a Cursor based adapter because this type of adapters implemented getView() to separate building the row layout and binding the data in two separate methods, newView() and bindView(). This two methods should be overridden. Also, SimpleCursorAdapter is a class designed for basic scenarios, if you need to implement (really) different row types then extending CursorAdapter would be a better approach.
You get blank row layouts because you don't do any data binding on them. In the getView() method you do:
View view = super.getView(position, convertView, parent);
which will return you a properly built row layout(by the SimpleCursorAdapter class implementation)only to discard that view and inflate a new row layout based on the row type(the if-else piece of code). You don't bind any data to those views so you return just the inflated layout which will be blank.
If your two rows are only different by a strikethrough text then you shouldn't be using two row types(different row types should be used when the rows are different in structure). You could implement what you want through a ViewBinder:
cursorAdapter = new SimpleCursorAdapter(this, R.layout.row, cursor, from, to, 0);
cursorAdapter.setViewBinder(new ViewBinder() {
#override
public void setViewValue(View view, Cursor cursor, int columnIndex) {
if (view.getId() == R.id.normal) {
// I'm assuming that the TextView with the id R.id.normal
// is the on to strike through
// use the cursor to get the value from the column on which you
// do the strikethrough
if (cursor.getInt(cursor.getColumnIndex(mySQLiteAdapter.getKeyStrike()) == 1) {
// strike the text
} else {
// otherwise un-strike the text
}
}
return false;
}
});
listView.setAdapter(cursorAdapter);
If you want
I would like to implement the ability to either strike out text on a
row by click or long click
Then you need to remember the row status somewhere so you can use it to restore it.
This question already has answers here:
How do I check when my ListView has finished redrawing?
(3 answers)
Closed 3 years ago.
I currently have a list view that displays information of things that have to be done.
When an user clicks the item on the listview, then the app checks which item has been clicked and set as done.
If the item has been set as done, it will show an imageview with a check; this imageview is part of the item of the listview.
everything is working well. If I scroll, exit the activity and open it again, uncheck the item, everything works well and shows or hide the imageview of the corresponding listview item.
BUT my problem is that this:
I have 2 buttons to sort the list view items, alphabetically and in order of date. When I click them, they work great. BUT, the app doesn't show the imageview of the items that have been checked.
I know this is because in order to make the checks appear, I need the listview to be fully loaded. I mean, show in the activity with all the info.
My question is:
How can I know when the list view is done populating or loading, so that I can call the method that make sure which items have been check to show the image view?
I have use isfocused(), onfocuschanged(), onWindowChangeState(), and all those type of methods, but none of them works to trigger my method.
Is there any way to be able to know when the listview gets populated to make the check appear on the items that are been show, without any user interaction?
Thanks for your time and help.
PD: I already have the part where the user scroll the list, that part is taken care of.
I'm using a SimpleCursorAdapter to fill the listview.
This is where I fill the list view:
public void mostrarLista(){
Cursor c = admin.obtenerCursorGastosFijos(ordenadoPor);
// The desired columns to be bound
String[] columnas = new String[] {"_id", "Descripcion", "Costo_Estimado", "Fecha_Pago"};
// the XML defined views which the data will be bound to
int[] views = new int[] {R.id.IdGstFijo, R.id.DescGstFijo, R.id.CostoGstFijo, R.id.FechaGstFijo };
// create the adapter using the cursor pointing to the desired data
//as well as the layout information
dataAdapter = new SimpleCursorAdapter(this, R.layout.lista_gastos_fijos, c, columnas, views, 0);
listaGastos = (ListView) findViewById(R.id.listaGastosFijos1);
// Assign adapter to ListView
listaGastos.setAdapter(dataAdapter);
listaGastos.setOnItemLongClickListener(new OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> Listview, View v,
int position, long id) {
// TODO Auto-generated method stub
Cursor cursor = (Cursor) Listview.getItemAtPosition(position);
String idGst=cursor.getString(cursor.getColumnIndexOrThrow("_id"));
dialogoOpcionesGst(Integer.parseInt(idGst), v).show();
return true;
}
});
listaGastos.setOnScrollListener(new OnScrollListener(){
#Override
public void onScroll(AbsListView arg0, int arg1, int arg2,
int arg3) {
// TODO Auto-generated method stub
}
#Override
public void onScrollStateChanged(AbsListView arg0, int arg1) {
// TODO Auto-generated method stub
mostrarItemsPagados(listaGastos);
}
});
}
This is the method that I did to navigate the items that are visible and see if they were checked or not
public void mostrarItemsPagados(ListView lista){
for (int i = 0; i <= lista.getLastVisiblePosition() - lista.getFirstVisiblePosition(); i++){
View item = (View)lista.getChildAt(i);
ImageView img = (ImageView)item.findViewById(R.id.imgPagado);
if(admin.existeGstFijoReal(Integer.parseInt(((TextView)item.findViewById(R.id.IdGstFijo)).getText().toString()))){
img.setImageResource(R.drawable.img_check);
}
else
img.setImageDrawable(null);
}
}
this is the method I use to sort the list:
public void ordenarNombre(View v){
ordenadoPor=1;
mostrarLista();
}
and well, the layout of the item inside the list is:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<TextView
android:id="#+id/IdGstFijo"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="2"
android:visibility="gone"
android:gravity="center"
android:lines="4" />
<TextView
android:id="#+id/DescGstFijo"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="2"
android:gravity="center"
android:lines="4" />
<TextView
android:id="#+id/CostoGstFijo"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="2"
android:gravity="center"
android:lines="4"/>
<TextView
android:id="#+id/FechaGstFijo"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="2"
android:gravity="center"
android:lines="4" />
<ImageView
android:id="#+id/imgPagado"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1"
/>
You should not rely on the condition of "finish loading data", instead, you should set the image view visibility on the fly.
You should do something like this in your getView method of your list view adapter.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final ItemView item;
if (convertView == null) {
item = mLayoutInflator.inflate(R.layout.listlayout)
} else {
item = (ItemView) convertView;
}
ImageView image = (ImageView)item.findViewById(R.id.yourimageidinitemview);
if(mTodoTask[posistion].isChecked) {
image.setVisibility(View.Visible);
} else {
image.setVisibility(View.Invisible);
}
return item;
}
My solution:
list = (ListView) findViewById(R.id.list);
list.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
...
private final ViewTreeObserver.OnPreDrawListener mOnPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
mMessageList.getViewTreeObserver().removeOnPreDrawListener(this);
// Do what you want to do on data loading here
return true;
}
};
You can use a Handler to accomplish this task, like this:
In your activity add the Handler as any other property.
private Handler mListViewDidLoadHanlder = new Handler(new Handler.Callback() {
#Override
public boolean handleMessage(Message message) {
//Do whatever you need here the listview is loaded
return false;
}
});
And inside the getView method of your listview you do the comparison to see if the current position is the last one , so , it will finish (just put it before the return):
public View getView(int position, View convertView, ViewGroup parent) {
//Your views logic here
if (position == mObjects.size() - 1) {
mViewDidLoadHanlder.sendEmptyMessage(0);
}
return convertView;
}
It worked for me (mc.android.developer):
listview.getViewTreeObserver().addOnPreDrawListener(OnPreDrawListener);
private final ViewTreeObserver.OnPreDrawListener OnPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
//do changes
return true;
}
};
Ok, i manage to solve the problem.
All i had to do, was override the getView method as said.
But, since Im using a SimpleCursorAdapter extended Class, I had to do something like this:
public View getView(int position, View convertView, ViewGroup parent){
View item = super.getView(position, convertView, parent);
//Validation code for drawing or not the check image
return item;
}
So, that did the work, and now everytime the listview is reordered or drawn or scrolled the views are refreshed correctly.
Thanks guys for the help
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.