I'm facing some difficults when I try to use the performItemClick funcion of the ListView.
All I want to do is to perform a click programatically in the first item of the list.
How can I do that? I looked up that function in the documentation, but I didn't really understand its parameters.
I tried something like:
myListView.performItemClick(myListView.getChildAt(0), 0, myListView.getChildAt(0).getId());
But it didn't work (myListView.getChildAt(0) returns null)
Thank you in advance!
mList.performItemClick(
mList.getAdapter().getView(mActivePosition, null, null),
mActivePosition,
mList.getAdapter().getItemId(mActivePosition));
Where mActivePosition is your click position!
All the best! :)
This worked for me.
listView.performItemClick(
listView.getAdapter().getView(position, null, null), position, position);
use the adapter to get the view for the position of the item. The other 2 parameters I didn't want so I left them null. Leaving convertView null causes the adapter to render a new view. It's a performance issue but since this is only happening once in a while it wont have much effect. I don't need to specify the parent for anything because I'm not using it.
position is just the spot where your item is located.
Additionally these 2 lines of code before your performItemClick create the illusion of having the list item selected. They also ensure the appropriate item is on the screen.
listView.requestFocusFromTouch();
listView.setSelection(position);
This works best for me. Run this on the main thread.
new Handler().post(new Runnable() {
#Override
public void run() {
mList.performItemClick(
mList.getChildAt(mActivePosition),
mActivePosition,
mList.getAdapter().getItemId(mActivePosition));
}
});
This is similar to Arun Jose's answer, but it will queue a message to the main thread to give the ListView some time to initiate.
I tried the code below and it worked.
getListView().performItemClick(null, 0, getListAdapter().getItemId(0));
The first parameter (view) can be null.
I went with
listView.getAdapter().getView(position, null, null).performClick();
When using Listview (simple array adapter or custom adapter) define listview and other finally make perform click.
For example:
//At onCreate function:
lv = (ListView) findViewById(R.id.listView);
lv.setAdapter(new CustomAdapter(List_item.this, list, images));
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// on click function works
}
}
int position = 0;
lv.performItemClick(lv.getAdapter().getView(position, null, null), position, lv.getAdapter().getItemId(position));
Note: After creating the setOnItemClickListener only you should call
perform click. Otherwise, it will not correctly.
this may be old but this may help :
lvList.performItemClick(null, index, lvList.getItemIdAtPosition(index) );
NOTE : the first param is null and will still work, if you have a custom adapter, convertView will be filled with custom layout and view and such.
-cheers / happy codings.
mList.performItemClick(
mList.getChildAt(mActivePosition),
mActivePosition,
mList.getAdapter().getItemId(mActivePosition));
where mActivePosition is the position of the child view in List View.
Using the code #sulal proposed, you may place it in onLoadFinished, if you use a LoaderManager. Eg something like
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
//....
// mSelectedId keeps the currently selected id
// INVID is an invalid value
if (mSelectedId == INVID) { // nothing selected
// sulal's code
new Handler().post(new Runnable() {
#Override
public void run() {
mList.performItemClick(
mList.getChildAt(mActivePosition),
mActivePosition,
mList.getAdapter().getItemId(mActivePosition));
mSelectedId = mList.getAdapter().getItemId(mActivePosition);
}
});
}
mActivePosition may be 0 (ie position on the first item) or a position kept during eg onPause
At Firstly I tried to use this code in my Fragment(Master/Detail -> NameListFragment)
getListView().performItemClick(null, 0, getListView().getAdapter().getItemId(0));
But it didn't work. When I did #Override onStart() method in fragment and I moved my code to onStart(). After that it works properly for me.
If you are working on a unit test case.
Try to use getInstrumentation().waitForIdleSync(), to wait the list be loaded, and extend the ActivityInstrumentationTestCase2
See this answer.
I just meet this freak problem today , and I try me best to deal with it.
My condition is , when I first init the layout , I need make some item checked.
But when I use gridView.getChildAt(position) , always return null. I met this problem before , caused by Not finishing drawing layout . So I send a post message . handler.postDelayed( .. , ..) , It works. Thanks who motion this Exception.
This work for me
If you would get weird result when using getView, this is because the list item you want does not exist within visible parts. Use below:
private View getViewFromAdapterByPosition(int position, ListView listView)
{
View view;
int firstVisiblePos = listView.getFirstVisiblePosition();
int lastVisiblePos = listView.getLastVisiblePosition();
if (position < firstVisiblePos || position > lastVisiblePos) {
view = listView.getAdapter().getView(position, null, listView);
} else {
view = listView.getChildAt(position - firstVisiblePos);
}
return view;
}
And then,
listView.performItemClick(getViewFromAdapterByPosition(index, listView), index, 0);
Try this one:
public static boolean performClicKOnLisViewFromIndex(ListView listView, int index){
if(listView != null){
if(listView.getAdapter()!= null && listView.getAdapter().getCount() >0 && listView.getAdapter().getCount() > index ){
listView.performItemClick(
listView.getAdapter().getView(index, null, null),
index, listView.getItemIdAtPosition(index));
return true;
}
}
return false;
}
myListView.getChildAt(0) returns null because used this very soon.
use a delay for it.
or use below code:
private class MyAdapter extends BaseAdapter
{
private final Context context;
private HashMap<Integer, View> views;
public MyAdapter(Context context)
{
this.context = context;
views = new HashMap<>();
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
if(convertView == null)
{
if(views.get(position) == null)
{
final LayoutInflater layoutInflater = LayoutInflater.from(context);
convertView = layoutInflater.inflate(R.layout.my_grid, null, false);
views.put(position, convertView);
}
else
convertView = views.get(position);
}
TextView tv = convertView.findViewById(R.id.langView);
tv.setText(languageList.get(position));
return convertView;
}
}
and
adapter = new MyAdapter(getActivity());
myListView.setAdapter(adapter);
Runnable r = new Runnable()
{
#Override
public void run()
{
myListView.performItemClick(adapter.getView(position, null, myListView), position, 0);
}
};
myListView.postDelayed(r, 100);
just should use performItemClick() and it's okay.
listView.performItemClick(listView.getAdapter().getView(listView.getSelectedItemId, null, null), listView.getSelectedItemId, listView.getAdapter().getItemId(listView.getSelectedItemId));
In my case, none of the options solved my problem, so I made an adaptation in my CursorAdapter class.
I defined a global variable in the scope, so I just call the class changing this value and check the cursor position by passing the position value
mProductsAdapter.currentPosition = requiredPosition
in my ProductsAdapter builder
var currentPosition = 0
in bindView I do the check
if (cursor.position == currentPosition) {
// perform action
}
The performClick is probably called before listview was filled, put breakpoint in getView and on performItemClick and check wich is called first
getListView().performItemClick(null, 0, 0) did the trick for me (for position 0).
Dropping Some Experience.
using listview1.performItemClick, will also trigger your listview1.OnItemClickListener if you are using the listener with same listview in your code.
Hope It helps
If you would get weird result when using getView, this is because the list item you want does not exist within visible parts. Use below:
private View getViewFromAdapterByPosition(int position, ListView listView)
{
View view;
int firstVisiblePos = listView.getFirstVisiblePosition();
int lastVisiblePos = listView.getLastVisiblePosition();
if (position < firstVisiblePos || position > lastVisiblePos) {
view = listView.getAdapter().getView(position, null, listView);
} else {
view = listView.getChildAt(position - firstVisiblePos);
}
return view;
}
And then,
listView.performItemClick(getViewFromAdapterByPosition(index, listView), index, 0);
This works for me:
listview.getSelectedView().performClick();
This worked for me:
listView.getAdapter().getView(1, null, null).performClick();
This is from Begining Android Games. It creates a simple list of items which you can click to open a new activity. Each list item of course, would have to also be added to the AndroidManifest.xml as a separate activity with a .ListItem# name.
public class MainActivity extends ListActivity {
String tests[] = { "ListItem1",
"ListItem2",
"ListItem3",
"ListItem4"};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, tests));
}
#Override
protected void onListItemClick(ListView list, View view, int position, long id) {
super.onListItemClick(list, view, position, id);
String testName = tests[position];
try {
Class<?> classInstance = Class.forName("your.package.name." + testName);
Intent intent = new Intent(this, classInstance);
startActivity(intent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Related
In my app, the user has a list (a recyclerview) of his favorite movies, and for each movie in his favorites he can add a note and in that list I have this recycling problem where when he opens the dialog to add a note he adds the note and the note icon in the recyclerview reflects on that change, it's red in color when a note is added, but then when I click on another item in the list and come back to the item where I just added the note, the note icon goes back to grey and no note is seen in the dialog (the textview where the note should be is empty). The note only shows up when I manage to call my FavoriteFragment's OnResume where a refresh method gets called, it calls CursorAdapter changeCursor.
Also on click of an item in the list, the view gets expanded to reveal the Add/Edit note action, like so:
#Override
public void onBindViewHolder(final FavoriteHolder holder, final int position) {
// Passing the binding operation to cursor loader
final boolean isExpanded = position == mExpandedPosition;
holder.releasesActions.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
holder.itemView.setActivated(isExpanded);
if (isExpanded) {
mPreviousExpandedPosition = position;
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mExpandedPosition = isExpanded ? -1 : position;
notifyItemChanged(mPreviousExpandedPosition);
mCurrentPosition = position;
holder.initReleaseActions((Cursor) mCursorAdapter.getItem(mCurrentPosition));
notifyItemChanged(position);
// fixes recycling issue
// refresh only last position
// holder.initReleaseActions();
// mCursorAdapter.getItem(mPreviousExpandedPosition);
}
});
mCursorAdapter.getCursor().moveToPosition(position);
mCursorAdapter.bindView(holder.itemView, mContext, mCursorAdapter.getCursor());
}
The initReleaseActions method is the one which should update the note actions, to reflect changes on the note icon.
Here's its implementation in the FavoriteHolder:
public void initReleaseActions(Cursor cursor) {
// Setting our components
// User saved release actions
mHeartIcon.setImageResource(R.drawable.ic_favorite_red);
if (cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COL_NOTE_ADDED)) == 1) {
mNoteIcon.setImageResource(R.drawable.ic_note_red);
mNoteText.setText("Edit note");
}
}
My question is why won't this method to its job? I don't understand, in my mind it should work, do I always have to refresh the entire cursor if changes happen?
Oh and here's my implementation of CursorAdapater:
public FavoriteReleasesAdapter(Context context, String sortType, TextView nothingFoundTxt) {
mContext = context;
mDatabaseHelper = DatabaseHelper.getDatabaseHelper(mContext);
mCursor = getUserCursor(sortType);
mInflater = LayoutInflater.from(mContext);
mNothingFoundTxt = nothingFoundTxt;
mAlarmReceiver = new AlarmReceiver();
mIsCoversHidden = SharedPrefManager.read(SharedPrefManager.KEY_PREF_HIDE_COVERS, false);
mCursorAdapter = new CursorAdapter(mContext, mCursor, 0) {
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.favorite_release_item, parent, false);
FavoriteHolder holder = new FavoriteHolder(view);
holder.setFavoriteReleaseAdapter(FavoriteReleasesAdapter.this);
holder.setIsCoversHidden(mIsCoversHidden);
holder.setDatabaseHelper(mDatabaseHelper);
holder.setAlarmReceiver(mAlarmReceiver);
holder.setContext(mContext);
view.setTag(holder);
return view;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
FavoriteHolder holder = (FavoriteHolder) view.getTag();
holder.setFavoriteDetails(cursor);
}
};
}
// this is the update method that gets called on FavoriteFragment onResume
public void updateCursor(String sortType) {
mCursor = getUserCursor(sortType);
mCursorAdapter.changeCursor(mCursor);
notifyDataSetChanged();
toggleEmptyTxt();
// notifyItemRemoved(mCursor.getPosition());
}
Do I always have to refresh the entire cursor if changes happen?
Yes.
Cursor is like a snapshot of the database when the query was executed. If you change anything on the database, you must update your cursor as well (run a new query and replace the old cursor by a new one). If you don't want to do that, you can convert your cursor to an ArrayList<YourModelClass> and this way, you can dinamically update specific positions.
Another point about your code:
if (cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COL_NOTE_ADDED)) == 1) {
mNoteIcon.setImageResource(R.drawable.ic_note_red);
mNoteText.setText("Edit note");
}
You only set the red icon but never set the grey icon. However, RecyclerView re-uses a view. So, it may try to re-use a view with red-icon in a position where the movies does not have notes (grey icon). So, correct approach would be:
if (cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COL_NOTE_ADDED)) == 1) {
mNoteIcon.setImageResource(R.drawable.ic_note_red);
mNoteText.setText("Edit note");
} else {
// Re-apply default text and default icon
}
I create the below code:
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View v, int position,
long id) {
for (int i = 0; i < mListView.getCount(); i++) {
View callLogView = mListView.getChildAt(i);
mRelativeLayout = (LinearLayout)callLogView.findViewById(R.id.myShow);
if(i == position){
if(mRelativeLayout.getVisibility() == View.GONE){
mRelativeLayout.setVisibility(View.VISIBLE);
}
else{
mRelativeLayout.setVisibility(View.GONE);
}
}else{
mRelativeLayout.setVisibility(View.GONE);
}
}
}
});
I want to realize a function like when i click one item of Listview, it will show a view, and the other items of Listview will be hidden. But mListView.getChildAt(i) will have the null pointer after exceed mListView.getChildCount().
How to solve this? Thanks in advance!
AdapterView.getCount() returns the number of data items, which may be larger than the number of visible views, that's why you are getting null pointer exception, because you are trying to find views which do not exist in the current visible ListView items.
To solve this issue you will first need to find the first visible item in the ListView using getFirstVisiblePosition() and the last visible item using getLastVisiblePosition(). Change the for loop condition as:
int num_of_visible_view=mListView.getLastVisiblePosition() -
mListView.getFirstVisiblePosition();
for (int i = 0; i < num_of_visible_view; i++) {
// do your code here
}
you can not implement this in onItemClick.
As you can access only visible child not all child.
What you can do is on onItemClick
you can send the position in adapter
and then set the logic there in getView too change view
and update the adapter in listview, or notify for changes.
I'm still not getting exactly how a custom CursorAdapter should work, so after hard tries, here is the thing :
I implement a listener on a button inside my getView().
The listener launches a delete row event.
To get to it, I use a singleton database where I can perform request from all classes (The purpose of a singleton actually)
I usually update (insert and modify) from the activity class, so I just have to get the new database cursor and re-assign it to a new CursorAdapter, that I re-assign itself to the ListView.
Here it's different : I'm inside the CursorAdapter class in the ListView method, and I don't know how to update my adapter from the inside.
For people who don't want to read my boring explanations, here is the code of the getView :
#Override
public View getView(int position, View convertView, ViewGroup parent){
convertViewParam = convertView;
ViewHolder viewHolder = new ViewHolder();
if (convertViewParam == null) {
int type = getItemViewType(position);
switch (type) {
case TYPE_ADD_NOTE_TOP:
convertViewParam = inflater.inflate(R.layout.add_note_top, null);
viewHolder.contentNote = (EditText)convertViewParam.findViewById(R.id.add_note_top_id);
break;
case TYPE_ITEM:
convertViewParam = inflater.inflate(R.layout.row_note, null);
viewHolder.delete = (Button)convertViewParam.findViewById(R.id.delete);
if (deleteMode){
viewHolder.delete.setVisibility(View.VISIBLE);
}else{
viewHolder.delete.setVisibility(View.GONE);
}
viewHolder.contentNote = (TextView)convertViewParam.findViewById(R.id.note);
getCursor().moveToPosition(position - 1);
int currentPosition = getCursor().getPosition();
Cursor c = getCursor();
c.moveToPosition(currentPosition);
((TextView) viewHolder.contentNote).setText(c.getString(c.getColumnIndex("content_note")));
int rowId = c.getInt(c.getColumnIndex("_id"));
viewHolder.delete.setTag(new Integer(rowId));
viewHolder.contentNote.setTag(new Integer(rowId));
OnClickListener listener = new OnClickListener() {
#Override
public void onClick(View v) {
int thePosition = (Integer) v.getTag();
int posCurs = notesCursorAdapter.getCursor().getPosition();
NoteDataSource.getSingletonObject(context).deleteNote(thePosition, context);
notesCursorAdapter.changeCursor(NoteDataSource.getSingletonObject(context).getAllNotes());
notesCursorAdapter.notifyDataSetChanged();
}
};
viewHolder.delete.setOnClickListener(listener);
break;
case TYPE_ADD_NOTE_BOTTOM:
convertViewParam = inflater.inflate(R.layout.add_note_bottom, null);
LinearLayout linearLayout = (LinearLayout)convertViewParam.findViewById(R.id.bottomLayout);
linearLayout.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
}
});
break;
}
convertViewParam.setTag(viewHolder);
} else {
viewHolder = (ViewHolder)convertViewParam.getTag();
if (viewHolder.contentNote!=null){
if (viewHolder.contentNote.getTag() == convertViewParam.findViewById(R.id.note)){
int test = (Integer) viewHolder.delete.getTag();
System.out.println("current tag " + test);
String txt = getCursor().getString(getCursor().getColumnIndex("content_note"));
((TextView) viewHolder.contentNote).setText(txt);
}
}
}
return convertViewParam;
}
I get the following error when I launch the delete event:
03-28 17:51:32.996: E/AndroidRuntime(7115): android.database.CursorIndexOutOfBoundsException: Index 2 requested, with a size of 2
If you want more info ask.
I'm looking for a bug fix or an just explanation, thanks.
Thats a very well written problem statement. I have had this problem and broke my head over it. Try this.
In your Adapter make a listener, something like
public interface ItemDeletedListener(){
public void itemDeleted(int position);
}
create an instance of ItemDeletedListener in your adapter, m_listener.
Write a setter function, setItemDeletedListener( ItemDeletedListener listener){}
In your getView(), onClickListener, call m_listener and pass the position that needs to be deleted.
Now in your Activity, Do the following.
Implement the listener, that you created above.
In your implementation, do the deletion work and then do a changeCursor() there.
It would help to know on which line you get the exception. First of all one bad thing is that you delete a note from the database based on a position int and after that you fetch again all the data and move the position on that cursor on the previous deleted position.
If you have 3 notes(and you show them in the list) in the database and you want to delete the last one this will happen:
the thePosition will be 2
you delete the note
fetch again all the notes from the database (the cursor will have 2 rows now because you deleted one)
move the cursor to thePosition (value 2)
the cursor has only 2 rows(numbered from 0) and you ask for row with the number 2
exception
If you want to delete a note then pass as a tag the _id for that row and use that to delete the note:
Cursor c = getCursor();
c.moveToPosition(position);
long rowId = c.getLong(c.getColumnIndex("_id"));
viewHolder.delete.setTag(new Long(rowId));
Then in the onClick() method use that to delete the note and stop messing with the moveToPosition() on the cursor.
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 ?
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);
}