Android Listview.getChildAt() points to two items - android

I have an activity that has a single listview in it with enough items to extend of the page.
I want to set a certain listView item at position i to a different drawable.
To go this I use the line of code..
listView.getChildAt(selector).setBackgroundResource(R.drawable.main_button_shape_pressed);
There is a very confusing problem going in. This line of code is setting two listView items to the specified drawable.
When i = 0 item 0 and item 11 are set to that drawable. It turns out that when I call this line of code with i both item i and item i+11 are set to that drawable. This is rather baffling. Then to mix EVERYTHING when I start the activity in landscape, it is a different second listview that gets set to that drawable. And in certain scenarios when I change from portrait to landscape, the current highlight listview item on screen will change to a different one.
WTF is going on with the listview class? Are the indexes to it children constantly pointing to different things?
Here is my entire activity.
public class SelectorActivity extends Activity {
private ListView listView;
private int selector;
private boolean set;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.selector_layout);
set=false;
Bundle extras = getIntent().getExtras();
if(extras!=null)
{
selector=extras.getInt("selector");
}
listView=(ListView)findViewById(R.id.selector_layout);
//set the string array for the listview
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.sounds_array, android.R.layout.simple_list_item_1);
adapter.setDropDownViewResource(android.R.layout.simple_list_item_1);
listView.setBackgroundResource(R.drawable.listview_background);
listView.setAdapter(adapter);
highlightSelected();
}
//this method will highlight a selected listview once that listview is drawn
private void highlightSelected()
{
if(!set)
{
new Thread(
new Runnable()
{
#Override
public void run() {
// TODO Auto-generated method stub
boolean trigger=true;
while(trigger)
{
if(listView.getChildAt(selector)!=null)
{
set=true;
trigger=false;
listView.getChildAt(selector).setBackgroundResource(R.drawable.main_button_shape_pressed);
}
}
}
}
).start();
}
}
}

ListViews recycle their children. While drawing itself, the ListView will create a new view for every visible child. When you scroll, it will then re-use the last view that became non-visible (scrolled off the screen) as the next view in the list. That is why it's a different view index in landscape and that is why it would probably be a different view index on a device with a different screen size.
The solution should be to reset the view background in the Adapter's getView() method.
Additionally, touching views on anything other than the UI (main) thread is a bad practice. Check the selected item index in the getView() method and set the background right there. You'll also need to handle the case where the selected index changes (unless it never changes after this activity is created) by iterating over visible views in the listview and setting their backgrounds to the appropriate values.
// Must be final to use inside the ArrayAdapter
final int selector = extras == null ? -1 : extras.getInt("selector");
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(
this,
R.array.sounds_array,
android.R.layout.simple_list_item_1) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View newView = super.getView(position, convertView, parent);
// set the background according to whether this is the selected item
if (position == selector) {
// this is the selected item
newView.setBackgroundResource(R.drawable.main_button_shape_pressed);
} else {
// default background for simple_list_item_1 is nothing
newView.setBackground(null);
}
return newView;
}
};

Related

how to hide a row in the listview, when in the adapter, from a touch listener in the activity

I have an activity which has a TextView button (btnChangeMode) that toggles the mode from "admin" to "guest". Depending on the mode chosen, I need to hide/show a button (btnAddListItems) within my listview row. The code i have, currently doesn't seem to be cutting it.
Code speaks easier, so here's the gist of my code:
My activity layout:
<FrameLayout>
<ListView> ... </ListView> <!-- which has its items populated from myCustomAdapter -->
<TextView> ... </TextView> <!-- this is my btnChangeMode -->
<FrameLayout>
nothing fancy in my activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
List listItems = ... // some method that gets objects from Database
ListView listView = (ListView) findViewById(R.id.list_view_id);
listView.setAdapter(new MyCustomAdapter(this, listItems));
}
I have a custom adapter that basically has two types of rows header & item. In the "header" row, I have button "btnAddListItems" that allows me to add items into the list view. I want this button to be visible only in admin mode.
I've overridden the necessary methods in myCustomAdapter (getviewTypeCount, getItemViewType, getCount and getView). here's the getView method:
#Override
public View getView(int position, View row, ViewGroup parent) {
if (row == null) {
if (getItemViewType(position) == ITEM_VIEW_TYPE_HEADER) {
return getHeaderRow();
} else {
return getItemRow();
}
}
if (getItemViewType(position) == ITEM_VIEW_TYPE_ROW) {
MyHolder holder = (MyHolder) row.getTag();
holder.populateNewContent();
}
return row;
}
....
private View getHeaderRow() {
View lRow = LayoutInflater.from(getContext()).inflate(R.layout.my_header, null);
mViewMode = new DetailsViewMode((Activity) getContext(), getChangeViewModeListener(lRow));
return lRow;
}
So mViewMode here is a convenience POJO class i wrote that contains btnChangeMode, a boolean variable that indicates current mode (isAdmin) and the caller activity. I don't believe there's anything specific to my problem, so i'm not including that code here. Will be glad to if someone thinks that'll help.
private ChangeViewModeFragment.ChangeViewModeListener getChangeViewModeListener(final View headerRow) {
return new ChangeViewModeFragment.ChangeViewModeListener() {
#Override
public void onViewModeChanged(boolean isViewModeAdmin) {
mViewMode.changeViewModeButtonText(isViewModeAdmin);
toggleAdminFeatures(isViewModeAdmin, headerRow);
}
};
}
private void toggleAdminFeatures(boolean isViewModeAdmin, View headerRow) {
TextView btnAddListItems = (TextView) headerRow.findViewById(R.id.add_button_id);
if (isViewModeAdmin) {
btnAddListItems.setVisibility(View.VISIBLE);
} else {
btnAddListItems.setVisibility(View.GONE);
}
}
This is the part that's not working as it should. btnAddListItems is always visible within my listview.
btnChangeMode is within my activity so to speak, while the btnAddListItems is within my Adapter (ListView). But my requirement necessitates this behavior of having the listner of an activity, change the row state of my listview.
I suspect that when i change the visibility of my header row's button, I don't have hold of the correct header row instance, if that makes sense :P.
NitroNbg's suggestion of having a private button didn't work, which leads me to believe that maybe the ListView just needs a kickstart to get refreshed?
But I've tried calling notifyDataSetChanged() at the end of my toggleAdminFeatures method but that doesn't seem to be doing the trick.
I'd try the following - create a private Button within your adapter class and within your getView() method, put a reference to btnAddListItems to it.
private Button buttonToHide;
//...
public View getView(...) {
//...
buttonToHide = (Button) row.findViewById(R.id.add_button_id);
//...
}
Then, inside your ChangeViewModeListener() simply refer to a method of your adapter class (of course you'll have to write it) that sets the buttonToHide.setVisibility(View.INVISIBLE)
Hopefully, since it's within the adapter it would be accessible.
EDIT: Just to point out if it isn't obvious - only refer to the button that's in the header row.
You don't need to notifyDataSetChanged(), coz dataset hasn't changed, try setting the visiblity to INVISIBLE

Different content for each Spinner item in the same Activity

How can I show different content after selecting an item from the Spinner?
I want to create a Spinner with locations of chain stores.
I want the spinner to be always there on top. The only thing that
changes to be the content under the spinner
Create a simple method in your activity to refresh the layout below the Spinner(which will remain untouched). That method will be called from a OnItemSelectedListener set on the Spinner. It would be something like this:
private void changeAdress(int newSelectedAdress) {
// The ImageView and the TextView will be already in the layout
ImageView map = (ImageView) findViewById(R.id.theIdOfTheImage);
// Set the image. You know the current address selected by the user
// (the newSelectedAddress int) so get it from the array/list/database
// where you stored it
// also set the image
}
Called the method above from the onItemSelected callback:
yourSpinnerRefference.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
changeAddress(position);
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
If this is not what you want, please explain.
Edit: Make two arrays to hold your data:
int[] images = {R.drawable.imag1, R.drawable.imag2 ..etc...};
//also for the text
String[] text = {"text1", "text2 ...etc...};
Then use those two arrays in the method I recommended above:
private void changeAdress(int newSelectedAddress) {
((ImageView)findViewById(R.id.mapView1)).setImageResource(images[newSelectedAddress]);
// assing an id to the TextView in your layout and do the same as above.
}
There is no need for multiple ImageView and TextViews.

What is the best way to change drawable of all item in ListView

Let say I have my view that I use it as a toggle button. When user clicks it, I change the background via setBackgroundResource(). The number of list is around 15 items and ListView can show only around 7 items on screen.
At first, I try to use ListView.getChildAt(position) but when position is more than 7 it returns NullPointer. eventhough ListView.getCount() returns 15. But that's make sense because it show only visible child.
Then I solve it by loop through all Data that binds to this Adapter, change the boolean value, and call notifyDataSetChange()
So the number of loop will be 15 for update data + 7 show visible view.
The best way should be 15 and that's done.
Is there anyway to achieve this?
Thank
Forget your child index. You should just toggle some type of flag in your adapter.
Then when your getView method is called again it will redraw your list.
i.e.:
public class YourAdapter extends BaseAdapter {
private boolean useBackgroundTwo = false;
.. constructor ..
#Override
public View getView (int position, View convertView, ViewGroup parent) {
...
...
View background = findViewById(...);
int backgroundResource = R.drawable.one;
if(useBackgroundTwo){
backgroundResource = R.drawable.two;
}
background.setBackgroundResource(backgroundResource);
....
}
public void useNewBackground(){
this.useBackgroundTwo = true;
notifyDataSetChanged();
}
public void useOldBackground(){
this.useBackgroundTwo = false;
notifyDataSetChanged();
}
}
Then in your activity code:
((YourAdapter) listview.getAdapter()).useNewBackground();
Taking it further, you could use an enum instead of a boolean and have multiple methods setBackgroundGreen(), setBackgroundRed() or you could pass in the drawable you want to use setItemBackground(R.drawable.one); The choice is yours.
Api: Adapter

How to Set Background Color or Drawable to a Specific Item and when the listview is scrolled to have those colors stay

I have a ListView that is populated with 50 items.
This is the xml that I use for my ArrayAdapter.
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:paddingLeft="6dip"
android:minHeight="?android:attr/listPreferredItemHeight"
android:background="#drawable/listview_background"
android:textColor="#ff000000">
</TextView>
I type
ArrayAdapter<String> list = new ArrayAdapter<String>(this, R.layout.my_simple_list);
to create a new ArrayAdapter. Then I add a String[] items to the list. Then I call
setListAdapter(list);
To add Strings from the String[] items to list, I use a for loop to loop through the String array and add each String to list using command: list.add(items.get(i)); where i is my for loop counter.
for(int i=0;i<items.size();i++){
list.add(items.get(i));
}
This gets my ListView created and populated. I also have an onListItemClick function.
public void onListItemClick(ListView parent, View v, int position, long id) {
String Select = this.getListAdapter().getItem(position).toString();
if(copyitems.contains(Select)){
v.setBackgroundResource(R.drawable.listview_background);
copyitems.remove(Select);
}else{
copyitems.add(Select);
v.setBackgroundColor(Color.GRAY);
}
}
In the onListItemClick function copyitems is an ArrayAdapter variable that I declare as a global variable. So what this function does is store the text from the selected items of the listview so later I can copy that selected text. So if an item is selected it's text is added to copyitems and if the same item is selected it will be removed form copyitems. Every time an item is added to copyitems the background of that item is changed to Gray and if an item is removed from copyitems the background of that item is set back to original. All this work fine. But when I have lots of items(like 50) they don't all fit to the screen so if I want to select an item that is not on screen then I have to scroll down or up to get to the item. The wierd this is that when I scroll away from a selected item and comeback to it the Background changes to original and a different item has the Gray background. So basically if my screen fits 10 items and I select the 1st one and scroll to select others that are passed 10(for example 11). When the 1st item leaves the screen and the 11 comes in, it is already colored Gray but I didn't selected. When I scroll back up to see the 1st item, the background may or may not be Gray. If its not Gray so other item's Background is set to Gray like the 2nd one. Is this happening because the items are recycled when they go off the screen and because of it the positions change of the items? If it is how can I disable that so I only have items background being gray if I have selected that item. If there is a better approach to what I'm trying to do please tell me. I'm new to android.
You should write your own Adapter. I would recommend you to extend from BaseAdapter. Code would look like this:
// These are members of your activity
private List<String> mList = null;
private int mNewlyAddedItem;
private class Adapter extends BaseAdapter {
#Override
public int getCount() {
return mList.size();
}
#Override
public Object getItem(int position) {
return mList.get(position);
}
#Override
public long getItemId(int position) {
return (long)position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(position == mNewlyAddedItem) {
convertView.setBackgroundColor(Color.GRAY);
} else {
convertView.setBackgroundResource(R.drawable.listview_background);
}
// Set text to our TextView
TextView nameText = (TextView)convertView.findViewById(R.id.text1);
nameText.setText(mList.get(position));
return convertView;
}
}
Instead of background changing inside your onListItemClick handler you should just assign mNewlyAddedItem:
public void onListItemClick(ListView parent, View v, int position, long id) {
String Select = this.getListAdapter().getItem(position).toString();
if(copyitems.contains(Select)){
copyitems.remove(Select);
mNewlyAddedItem = -1;
}else{
copyitems.add(Select);
mNewlyAddedItem = position;
}
}
mNewlyAddedItem = position;
I hope I understood your task right. Anyway, you got the point: you should change adapter code to draw your views based on some state (which you can set using Activity member variables), change the state and your views will update (you can also call getListView().invalidateViews() to force redrawal of all views if it is not happening at some point).

ListView stay selected?

I have a list view full of items, after the users selects an item it lights up, and then it goes back to normal. Is there a way to make it so that when the user selects an item in my ListView it stays selected, and highlighted?
Apparently the "disappearing selection" is by design; it's something called "touch mode". I read through that document and still I have no idea why they thought it was a good idea. My guess is that, since Android was originally designed for small-screen devices, they expected that you would fill the screen with a list and then, when the user clicks an item, move to a new list on a different screen. Thus, the user wouldn't be aware that Android lost track of the selected item.
But this behavior is quite annoying if, for example, you want the user to select an item and then show information about that item on the same screen. If the selection disappears, how is the user supposed to know what they clicked (assuming of course that users have the attention span of a goldfish)?
One possible solution is to change all the list items into radio buttons. I don't really like that solution because it wastes screen real estate. I'd rather just use the background color to show which item is selected. I have seen one solution so far but it is not quite complete or general. So here's my solution:
1. In your XML layout file
Go to your ListView element and the following attribute: android:choiceMode="singleChoice". I'm not entirely sure what this does (by itself, it doesn't allow the user to select anything) but without this attribute, the code below doesn't work.
2. Define the following class
It is used to keep track of the selected item, and also allows you to simulate pass-by-reference in Java:
public class IntHolder {
public int value;
public IntHolder() {}
public IntHolder(int v) { value = v; }
}
3. Put the following code somewhere
I'll assume you put it in your Activity, but it could go in any class really:
static void setListItems(Context context, AdapterView listView, List listItems, final IntHolder selectedPosition)
{
setListItems(context, listView, listItems, selectedPosition,
android.R.layout.simple_list_item_1,
android.R.layout.simple_spinner_dropdown_item);
}
static void setListItems(Context context, AdapterView listView, List listItems, final IntHolder selectedPosition,
int list_item_id, int dropdown_id)
{
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> list, View lv, int position, long id) {
selectedPosition.value = position;
}
});
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(context, list_item_id, listItems) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View itemView = super.getView(position, convertView, parent);
if (selectedPosition.value == position)
itemView.setBackgroundColor(0xA0FF8000); // orange
else
itemView.setBackgroundColor(Color.TRANSPARENT);
return itemView;
}
};
adapter.setDropDownViewResource(dropdown_id);
listView.setAdapter(adapter);
}
This code does two things: it attaches your list items (e.g. List<String>) to your ListView, and it overrides ArrayAdapter.getView() with some code that changes the background of the selected item.
4. Use that code to set up your list
For example:
ListView _list;
IntHolder _selectedItem = new IntHolder(-1); // nothing selected at first
#Override
protected void onCreate(Bundle savedInstanceState) {
...
_list = (ListView)findViewById(R.id.list);
List<String> items = Arrays.asList("Item 1", "Item 2", "Item 3");
setListItems(this, _list, items, _selectedItem);
}
That's all! The above assumes you want single selection. With some small modifications to getView(), you could support multi-selection too, I guess, but you should probably use checkboxes instead.
Warning: this solution needs further development. If the user uses arrow keys or buttons to select an item, that item will not be selected from the IntHolder's perspective. If the user presses the unlabeled button (what's the name of that button? "Enter"?) then the item will become "officially" selected but then you have another problem because if the user uses the arrow keys again, it will sort of look like two items are selected. Leave a comment if you figure out how to keep the "internal selection" in the IntHolder synchronized with the "keyboard selection" or whatever it's called. What is it called, anyway?
There is an attribute in ListView called listSelector:
Drawable used to indicate the currently selected item in the list.
http://developer.android.com/reference/android/widget/AbsListView.html#attr_android:listSelector
EDIT after Stan comment
To ensure that a ListView stays selected, you should
① Set the view's attribute choiceMode via xml or programmatically.
② Use an adapter that uses views which implement Checkable interface, like CheckedTextView (inside simple_list_item_single_choice layout).
File TestActivity.java
public class TestActivity extends Activity {
private static final int SINGLE_CHOICE = android.R.layout.simple_list_item_single_choice;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
String[] items = {"test 1", "test 2", "test 3"};
ListAdapter adapter = new ArrayAdapter<String>(this, SINGLE_CHOICE, items);
ListView list = (ListView) findViewById(R.id.testList);
list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
list.setAdapter(adapter);
}
}
Here a simpler solution than Qwertie's:
Do not rely on given selection mechanism. Do it yourself.
View mSelectedItemView = null; //class member variable
View mTouchedItemView = null; //class member variable
ListView v = (ListView) getActivity().findViewById(R.id.listView);
// select on click
v.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapter,
View clickedViewItem, int position, long id) {
if (mSelectedItemView != null)
selectedItemView.setBackgroundColor(Color.WHITE);
clickedViewItem.setBackgroundColor(Color.YELLOW);
mSelectedItemView = clickedViewItem;
}
});
// highlight on touch
v.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (v instanceof ListView) {
ListView listView = (ListView) v;
// Find the child view that was touched (perform a
// hit test)
Rect rect = new Rect();
int childCount = listView.getChildCount();
int[] listViewCoords = new int[2];
v.getLocationOnScreen(listViewCoords);
int x = (int) event.getRawX() - listViewCoords[0];
int y = (int) event.getRawY() - listViewCoords[1];
View child;
for (int i = 0; i < childCount; i++) {
child = listView.getChildAt(i);
child.getHitRect(rect);
if (rect.contains(x, y)) {
View touchedView = child;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
touchedView
.setBackgroundColor(Color.RED);
mTouchedItemView = touchedView;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
mTouchedItemView
.setBackgroundColor(Color.WHITE);
}
}
}
}
return false;
}
});
Also this method only deals with clicks and will not work if the user uses the arrow keys.
Disclaimer: De-highlighting after touch does not work reliably.
Credits for the touching part go to ozik.dev:
Get Item from ListView only with OnTouchListener
just add this to your listview layout
android:listSelector="#drawable/selector_expandable_listview"
android:drawSelectorOnTop="true"
Use a Selector.XML File and this code:
//SetOnClickListner to catch Events
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
view.setSelected(true);
}
});
Just add this to your ListView:
android:listSelector="#color/my_color"
This answer is working try this one
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long arg3)
{
for(int a = 0; a < parent.getChildCount(); a++)
{
parent.getChildAt(a).setBackgroundColor(Color.TRANSPARENT);
}
view.setBackgroundColor(Color.GREEN);
}

Categories

Resources