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);
}
Related
I have an Activity where I need to create 1 or more spinners dynamically according to an external DB.
SOme of this spinner items have to show a dialog according what value does the spinner has. For example the spinner has this options:
-Own
-Rental
-Family House
If the user selects Rental I have to show a dialog (or anything) asking him how much does he pays per month. If he selects own, or family nothing should happen.
After I create the layout with the spinners, edittexts, etc. Im using something like this:
for(int q=0;q<=parent.getChildCount();q++){
View v = parent.getChildAt(q);
if (v instanceof Spinner) {
Spinner res = (Spinner) v;
res.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
//Here its supposed to show dialog if the option is "RENT"
}
public void onNothingSelected(AdapterView<?> adapterView) {
return;
}
});
}
}
The problem is that when I do this the "setOnItemSelectedListener" only sets for the last spinner on the layout.
How can I do what Im trying? I dont know what else to do.
The easiest solution would probably be to make one Listener as a variable and use that for all of your spinners. To do this, you would not set it as you are currently (using the anonymous inner-class style) and instead would do this:
//This goes outside of the method
private AdapterView.OnItemSelectedListener listener =
new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
System.out.println("Spinner Selected ID = " + parent.getId());
/*
Put a check here for which one is being selected.
While you could use the parent to check, in your case, it will be easier
to use something from your DB table as a unique identifier (maybe a column
name would be ideal? Your pick)
*/
//Show your dialogs here
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
return;
}
};
//This is the method you have where you are iterating the parent object
private void doStuff(){
for(int q=0;q<=parent.getChildCount();q++){
View v = parent.getChildAt(q);
if (v instanceof Spinner) {
Spinner res = (Spinner) v;
res.setOnItemSelectedListener(listener);
}
}
}
Good luck to ya!
I'm trying to make a ListView in Android where some items have details and some items do not. For those items that have details, I want them to be clickable and for a click on them to present a new fragment. For those items that have no details, I want to hide an ImageView and set the list item to be "unclickable".
I'm trying to accomplish this in the following way (this is in my #Override getView method in my ArrayAdapter class):
ParseGeoPoint location = itemScan.getParseGeoPoint("location");
ImageView chevron = (ImageView)convertView.findViewById(R.id.chevron_image_view);
if (location == null) {
subtitleLabel.setText("{Location Data Unavailable}");
chevron.setVisibility(View.GONE);
convertView.setClickable(false);
} else {
subtitleLabel.setText(""); <-- Not important
chevron.setVisibility(View.VISIBLE);
convertView.setClickable(true);
}
Here's my list view's on item click listener:
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// This may be unnecessary when I get this
// issue solved, but I put this in because
// of the odd behavior described below
if (view.isClickable() == true) {
// Push to a new fragment
}
}
});
Part of this code works as expected and part does not. The list view items have their labels populated correctly. The list view items have the "disclosure chevron" shown or hidden as expected.
Weirdly, though, the click-ability of the views seems to be inverted from my expectations. The "good" items (the ones with details) don't respond to clicks/taps in any way. The "bad" items (the ones without details) have the audible "click" sound played, but the if statement in my OnItemClickListener() prevents the code from firing. So, my list view is rendered nonfunctional.
Have I misunderstood somehow what is the intended functionality of setClickable(boolean)? I got this idea from this answer.
Im guessing that your using a ArrayList to store your data you could do this
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ArrayList Object Type itemScan = arrayList.get(position);
ParseGeoPoint location = itemScan.getParseGeoPoint("location");
if (location == null) {
return;
} else {
// Push to a new fragment
}
}
});
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;
}
};
I have a ListView that contains items with checkboxes that should behave sometimes like a CHOICE_MODE_MULTIPLE and sometimes like a CHOICE_MODE_SINGLE. What I mean is for certain items in the list, when selected certain other items needs to be deselected whilst other can remain selected.
So when item A is checked I can find in my data the item B that needs to be unchecked but how do I get the UI to refresh to show this as I (I believe) cannot find the actual View that represents B but just it's data?
It sounds like you're off to a good start. You're right that you should be manipulating the underlying data source for item B when A is clicked.
Two tips that may help you:
Your getView() method in the Adapter should be looking at your data source and changing convertView based on what it finds. You cannot find the actual View that represents B because in a ListView, the Views are recycled and get reused as different data needs to be displayed. Basically, when an item is scrolled off the list, the View that was used gets passed to the getView() function as convertView, ready to handle the next element's data. For this reason, you should probably never directly change a View in a ListView based on user input, but rather the underlying data.
You can call notifyDataSetChanged() from within your adapter to signal that somewhere the underlying data has been changed and getView() should be called again for the elements currently displayed in your list.
If you're still having trouble, feel free to post some code that illustrates the specific problem that you're having. It's much easier to provide concrete advice when the problem is better defined. Hope this helps!
you can use singleChoice alartDialog, i have used like:
private int i = 0; // this is global
private final CharSequence[] items = {"Breakfast", "Lunch", "Dinner"}; // this is global
Button settings = (Button)view.findViewById(R.id.settings);
settings.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext());
//Title of Popup
builder.setTitle("Settings");
builder.setSingleChoiceItems(items, i,
new DialogInterface.OnClickListener() {
// When you click the radio button
public void onClick(DialogInterface dialog, int item){
i=item;
}
});
builder.setPositiveButton("Confirm",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
if (i == 0) {
//it means 1st item is checked, so do your code
}
if (i == 1) {
//it means 2nd item is checked, so do your code
} /// for more item do if statement
}
});
//When you click Cancel, Leaves PopUp.
builder.setNegativeButton("Cancel", null);
builder.create().show();
}
});
i have initialized i=0, so that for the very first time when user click on settings button, the first item is selected. and after then when user select other item, i have saved the i value so that next time when user click settings button, i can show user his/her previously selected item is selected.
I come across and solve this question today.
public class ItemChooceActivity extends Activity implements OnItemClickListener {
private int chosenOne = -1;
class Madapter extends BaseAdapter {
.....
.....
#Override
public View getView(final int position, View convertView,
ViewGroup parent) {
// TODO Auto-generated method stub
if (chosenOne != position) {
set the view in A style
} else {
set the view in B style
}
return convertView;
}
}
#Override
public void onItemClick(AdapterView<?> arg0, View view, int position,
long arg3) {
,,,,
chosenOne = position;
adapter.notifyDataSetChanged();
,,,
}
}
I am developing an app in which I need a ListView whose rows have a TextView, 2 CheckBox and a Spinner.
However, I am experiencing issues with onItemSelected() of the Spinner, as it gets called each time it is displayed for each row. In this method I am updating database records with the selected option, but as Android calls it automatically, every time the items get reset because Android calls it with position 0 and this is the value updated in the database.
I have read a lot of links about the issue with onItemSelected() and some hacks, but all of them are to use without a ListView. Any points here?
I have tried to track in a List which positions are actually displayed to make it work but it does not. I think it is because of the recycling in Android that causes the troubleshooting method get called for Spinners already shown!
So the point is: How can I differenciate a real call to onItemSelected() because of a user selection from the Android call when displaying the Spinner?
Here is the code of my adapter that extends SimpleCursorAdapter.
Thank you so much in advance.
public ParticipationAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c, from, to);
mActivity = (Activity)context;
ParticipationComment.ParticipationCommentManager commentManager = new ParticipationComment.ParticipationCommentManager(mActivity);
mParticipationCommentsCursor = commentManager.get();
mActivity.startManagingCursor(mParticipationCommentsCursor);
commentManager.detach();
mPositionsOfCursorIds = getPositionsOfCursorIds(mParticipationCommentsCursor);
mSpinnerPositionsDisplayed = new ArrayList<Integer>();
}
#Override
public View getView(final int participationPosition, View convertView, ViewGroup parent) {
final Cursor participationsCursor = getCursor();
mActivity.startManagingCursor(participationsCursor);
participationsCursor.moveToPosition(participationPosition);
View participationRow;
if (convertView == null) {
participationRow = LayoutInflater.from(mActivity).inflate(R.layout.participation_row_student, null);
} else {
mSpinnerPositionsDisplayed.remove((Integer)convertView.getTag());
participationRow = convertView;
}
participationRow.setTag(participationPosition);
Spinner commentSpinner = (Spinner)participationRow.findViewById(R.id.participation_comment_id_spinner);
SimpleCursorAdapter commentSpinnerAdapter = new SimpleCursorAdapter(
mActivity,
android.R.layout.simple_spinner_item,
mParticipationCommentsCursor,
new String[] {DatabaseManager.NAME},
new int[] {android.R.id.text1}
);
commentSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
commentSpinner.setAdapter(commentSpinnerAdapter);
long participationCommentId = participationsCursor.getLong(participationsCursor.getColumnIndex(DatabaseManager.PARTICIPATION_COMMENT_ID));
if (participationCommentId != 0) {
commentSpinner.setSelection(mPositionsOfCursorIds.get(participationCommentId));
}
commentSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
participationsCursor.moveToPosition(participationPosition);
if (!mSpinnerPositionsDisplayed.contains(participationPosition)) {
// Android calls this method the first time a Spinner is displayed,
// to differentiate from a real user click we check if the current Spinner's position
// in the ListView is being shown
mSpinnerPositionsDisplayed.add(participationPosition);
} else {
ParticipationComment participationComment = new ParticipationComment((Cursor)parent.getItemAtPosition(position));
Participation.ParticipationManager participationManager = new Participation.ParticipationManager(mActivity);
Participation participation = new Participation(participationsCursor);
participation.setConnectionProfileParticipationCommentId(participationComment.getConnectionProfileId());
participation.setParticipationCommentId(participationComment.getIdOpenErp());
participation.setChanged(true);
participationManager.update(participation);
participationManager.detach();
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
// Not used
}
});
TextView studentName = (TextView)participationRow.findViewById(R.id.participation_student_name);
studentName.setText(participationsCursor.getString(participationsCursor.getColumnIndex(DatabaseManager.NAME)));
CheckBox expectedPresent = (CheckBox)participationRow.findViewById(R.id.participation_expected_present_value);
expectedPresent.setChecked(participationsCursor.getInt(participationsCursor.getColumnIndex(DatabaseManager.EXPECTED_PRESENT)) == 1);
CheckBox present = (CheckBox)participationRow.findViewById(R.id.participation_present_value);
present.setChecked(participationsCursor.getInt(participationsCursor.getColumnIndex(DatabaseManager.PRESENT)) == 1);
return participationRow;
}
A better way is to use a AlertDialog Variant.. like this.. and create a button which initially has the first selection as its Text and its changed based on the AlertDialog choice..
What about using a small flag to discard first call of ItemSelected ?