I wish to manually rearrange the order of a ListView. The way I (want to) achieve this is by tapping on the item to be moved, setting the background of that item to a different colour, storing its position (oldPosition) and then tapping on the item which it is to appear below and finally resetting the background of the original item location.
The code I use to do this is:-
List<String> catarray; // string array declared in main activity
ArrayAdapter<String> catadapter; // adapter for Spinner declared in main activity
ListView cats; // listview declared in list activity
ArrayAdapter<String> adapter; // adapter for listview declared in list activity
int oldPosition = -1;
cats.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (oldPosition < 0) {
oldPosition = position;
try {
dr = parent.getChildAt(position).getBackground();
parent.getChildAt(position).setBackgroundColor(Color.argb(255, 0, 153, 204));
}
catch (Exception e) {
}
}
else {
String item = PhotoActivity.catarray.remove(oldPosition);
PhotoActivity.catarray.add(position,item);
try {
parent.getChildAt(oldPosition).setBackground(dr);
}
catch (Exception e) {
}
oldPosition = -1;
changed = true;
PhotoActivity.catadapter.notifyDataSetChanged();
adapter.notifyDataSetChanged();
}
}
});
The problem that I have is that if the list is larger than the displayed view then both the item that I tap on gets the background changed but so does an additional item that is below the visible range.
So, for example, if the full list is, say, 12 items long of which the first 8 are being displayed, if I tap on the second item then both that item but also item 11 (ie the second item plus one below the visible range) is highlighted.
Why does this happen?
How can I either stop it or, if I can't do that, reset the incorrectly highlighted item given that it is not visible and therefore not accessible via parent.getChildAt ...
The only answer I've found is to add a getView method for the adapter, call super.getView and then change the background colour there.
Also, you need to call view.setBackgroundColor in the OnItemClickListener event to make sure that the background is changed at the point of clicking.
So, my code ends up as being:-
cats.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (oldPosition < 0) {
oldPosition = position;
try {
view.setBackgroundColor(Color.argb(255, 0, 153, 204));
}
catch (Exception e) {
}
}
else {
String item = PhotoActivity.catarray.remove(oldPosition);
PhotoActivity.catarray.add(position,item);
oldPosition = -1;
changed = true;
PhotoActivity.catadapter.notifyDataSetChanged();
adapter.notifyDataSetChanged();
}
}
});
adapter = new ArrayAdapter<String>(c, android.R.layout.simple_list_item_1, PhotoActivity.catarray) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = (TextView) super.getView(position, convertView, parent);
if (oldPosition> -1 && oldPosition==position) {
textView.setBackgroundColor(Color.argb(255, 0, 153, 204));
}
else {
textView.setBackgroundColor(Color.argb(0, 0, 0, 0));
}
return textView;
}
};
cats.setAdapter(adapter);
My thanks to Cameron Saul at SO answer here for pointing me in the right direction.
Related
My code :
ArrayList<Integer> selectedItemIds = new ArrayList<>();
boolean isItemSelected = false;
main_listview.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
Object object = parent.getItemAtPosition(position);
if(object instanceof Listview_Item_Details)
{
TextView itemName = (TextView) view.findViewById(R.id.listview_itemName);
TextView itemDetails = (TextView) findViewById(R.id.listview_itemDetails);
if(!selectedItemIds.isEmpty())
{
for(int i = 0; i < selectedItemIds.size();i++)
{
if(selectedItemIds.get(i) == position)
{
isItemSelected = true;
if(selectedItemIds.size() == 1)
{
selectedItemIds.clear();
}
else
{
selectedItemIds.remove(i);
}
}
}
if(isItemSelected)
{
view.findViewById(R.id.custom_listview_item).setBackgroundResource(R.drawable.listview_item_background);
itemName.setTextColor(getResources().getColor(R.color.colorBlack));
itemDetails.setTextColor(getResources().getColor(R.color.colorGray_Dark));
isItemSelected = false;
}
else
{
selectedItemIds.add(position);
view.findViewById(R.id.custom_listview_item).setBackgroundResource(R.drawable.listview_selected_item_background);
itemName.setTextColor(getResources().getColor(R.color.colorWhite));
itemDetails.setTextColor(getResources().getColor(R.color.colorPrimaryDark));
}
}
else
{
selectedItemIds.add(position);
view.findViewById(R.id.custom_listview_item).setBackgroundResource(R.drawable.listview_selected_item_background);
itemName.setTextColor(getResources().getColor(R.color.colorWhite));
itemDetails.setTextColor(getResources().getColor(R.color.colorPrimaryDark));
}
}
return true;
}
});
NOTE : "custom_listview_item" is RelativeLayout of custom layout for item, in which both textviews are.
Now everythings works but like i said in question, if i DONT scroll the listview and selected/unselect multiple items, it works but i scroll it randomly changes background of multiple items and even changes background of selected items too.
What is the problem here?
Use Recyclerview instead of Listview to handle this kind of situations and customizing the view items.
I am working on the custom Listview using BaseAdapter. I am facing one small issue after tap on listview item that particular row gets highlighted but after that if i'll tap one another list item it's highlighted but still the older one it remains same it's not going to it's previous state.
I want to see one item should select at one time.
MainActivity.java
if (musicRealmResults.get(currentIndex).isSelected() == false) {
musicRealmResults.get(currentIndex).setIsSelected(true);
playSong(currentIndex, true);
adapter.notifyDataSetChanged();
} else {
musicRealmResults.get(currentIndex).setIsSelected(false);
adapter.notifyDataSetChanged();
}
MusicAdapter.java
if (musicRealmResults.get(position).isSelected()) {
Typeface tf = Typeface.createFromAsset(mContext.getAssets(), "fonts/fonts_bold.otf");
holder1.albumsTextView.setTypeface(tf);
holder1.equalizerView.setVisibility(View.VISIBLE);
holder1.albumsImageView.setVisibility(View.INVISIBLE);
holder1.equalizerView.animateBars();
} else {
Typeface tf = Typeface.createFromAsset(mContext.getAssets(), "fonts/fonts_regular.otf");
holder1.albumsTextView.setTypeface(tf);
holder1.equalizerView.setVisibility(View.GONE);
holder1.albumsImageView.setVisibility(View.VISIBLE);
holder1.equalizerView.stopBars();
}
Please kindly go through my post and suggest me how to do select and deselect in a listview row.
Loop through all your array elements and set check state to false, then set currentIndex to true.
MainActivity
for(int i =0 ; i < musicRealmResults.size() ; ++i){
musicRealmResults.get(i).setIsSelected(false);
}
musicRealmResults.get(currentIndex).setIsSelected(true);
playSong(currentIndex, true);
adapter.notifyDataSetChanged();
Seems that you weren't able to setSelected(false) your previous item.
Please check setChoiceMode() of ListView or you may just reset your previous item to setSelected(false).
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
musicRealmResults.get(currentSelected).setIsSelected(false); //Reset previous
currentSelected = position; //Save currentPosition to for reset later
.....todos
}
This is a good alternative too, if u dont want to loop through the rest of positions to deselect them.
int prevSelection =-1; //nothing selected
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (prevSelection==-1) //nothing selected
{
musicRealmResults.get(position).setIsSelected(true);
adapter.notifyDataSetChanged();
prevSelection = position;
}else if (prevSelection == position) //deselect previously selected
{
musicRealmResults.get(position).setIsSelected(false);
adapter.notifyDataSetChanged();
prevSelection = -1;
}else // Some other selection
{
musicRealmResults.get(prevSelection).setIsSelected(false);
musicRealmResults.get(position).setIsSelected(true);
adapter.notifyDataSetChanged();
prevSelection = position;
}
You can manage this in your model class. Just make a toggle Boolean isSelected with its getter and setter, When user tap on list item check whether its already selected or not, if not then mark it as selected and update the Boolean value in your model class.
mListLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(list.get(position).getisSelected()){
// list item is already selected.
mListLayout.setBackgroundColor(Color.WHITE); // normal color
list.get(position).setisSelected(false);
}
else{
// list item is not selected, make it selected
mListLayout.setBackgroundColor(Color.GRAY); // selected color
list.get(position).setisSelected(true);
}
notifyDataSetChanged();
}
});
Try Below Code:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
if (musicRealmResults.get(position).isSelected() == false)
{
musicRealmResults.get(position).setIsSelected(true);
playSong(position, true);
adapter.notifyDataSetChanged();
}
else
{
musicRealmResults.get(position).setIsSelected(false);
adapter.notifyDataSetChanged();
}
}
});
I've a problem with my simple application. It uses a listview that has to:
- open a new Activity when pressed and it's on view mode
- highlight the selected item when it's on edit mode
I'm doing the following:
ListView lv = (ListView)findViewById(R.id.categoryListView);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
String entry = (String) parent.getItemAtPosition(position);
if (_editMode)
view.setBackgroundColor(Color.parseColor("#5B5B5B"));
else
{
Intent intent = new Intent(MainActivity.this, CategoryActivity.class);
intent.putExtra("CATEGORY", entry);
startActivity(intent);
}
}
});
Then I want that when I turn to view mode, all items must be deselected, and I do this:
for (int i = 0; i < lv.getAdapter().getCount(); i++)
{
lv.getChildAt(i).setBackgroundColor(Color.parseColor("#FFFFFF"));
}
But this is working fine only if all the items in listview are visible.
I've tried implementing this on the adapter without success...
Any suggestion?
Thanks!
EDIT
Ok after Jawad answer I just figured out how does "getView" method work. So this is the solution I've used:
I declared an arraylist containing selected items:
ArrayList<String> itemSelected = new ArrayList<String>();
This is the ListView onItemClick listener (where you can select and deselect items):
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
String entry = (String) parent.getItemAtPosition(position);
if (itemSelected.contains(entry))
{
itemSelected.remove(entry);
}
else
{
itemSelected.add(entry);
}
((ArrayAdapter<String>)lv.getAdapter()).notifyDataSetChanged();
}
});
This is the ovverride of getView method:
itmList.setAdapter(new ArrayAdapter<String>(this,R.layout.list_black_text,R.id.list_content, itmStrList){
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
if (convertView != null)
{
if (itemSelected.contains(itmList.getAdapter().getItem(position))) {
convertView.setBackgroundColor(Color.RED);
} else {
convertView.setBackgroundColor(Color.parseColor("#5B5B5B"));
}
}
return super.getView(position, convertView, parent);
}
});
And this is how to deselect all items:
itemSelected.clear();
((ArrayAdapter<String>)lv.getAdapter()).notifyDataSetChanged();
Thank you man :)
I think your problem is your are not paying attention to view recycling. When you are changing the color of a view's background, the view can be recycled and you will have something not desired. Check this link for details.
You should have a, lets say boolean variable, in your underlyning data called something like isSelected. And add this code in your getView() method.
if(item.isSelected()){
setBackgroundColor(Color.parseColor("#5B5B5B"));
}
else{
setBackgroundColor(Color.parseColor("#FFFFFF"));
}
Then in your onItemClick add replace view.setback... by
lv.getAdapter().getItem(position).setSelected(true);
lv.getAdapter().notifyDataSetChanged();
You may need a cast.
Finally change this
for (int i = 0; i < lv.getAdapter().getCount(); i++)
{
lv.getChildAt(i).setBackgroundColor(Color.parseColor("#FFFFFF"));
}
to this:
for (int i = 0; i < lv.getAdapter().getCount(); i++)
{
lv.getAdapter().getItem(i).setSelected(false);
}
lv.getAdapter().notifyDataSetChanged();
The reason why your portion of code works only if all the items are visible is view recycling. Moreover lv.getChildAt() gives you only the views that are visible. Your code may then crash because adapter.getcount maybe bigger then the number of listview childs.
Hope it helps.
I have an Activity that hosts multiple fragments using the actionbar's tab functionality. One of those fragments contains a ListView. Upon this tab being selected, I'd like to select a certain item.
To do this programmatically, I use the following code (where calls is the ListView)
private void selectItem(int position)
{
long itemId = calls.GetItemIdAtPosition(position);
calls.PerformItemClick(calls, position, itemId);
}
If this ListView has been rendered, and I'm calling this, no problem. However, if I call it from onResume, then the code executes but nothing is selected in the end. I figure this is because at the point where I'm calling selectItem, not all items of the ListView have been rendered yet. If however I start off a background thread, sleep for a couple hundred milliseconds, then run the same code (in the ui thread of course), everything is fine, but this is an ugly hack.
Now you might be wondering, "why isn't he using calls.setSelection"? The thing is, I'm using a custom layout that performs expansion - so I need to actually click on the item I want selected (which in turn triggers the layout expansion for the item selected). However, I can call the code that is performed on PerformItemClick directly, the results will be the same (the layout expansion isn't performed).
Isn't there any way for me to catch the "Listview has finished rendering all viewable items" point in time, and then execute my selectItem call at that point? In ASP.NET, I have an event on every UI item telling me when it is done rendering, so I do item selection at that point but I haven't found anything.
Regards
Stephan
Here's the Adapter I'm using
public class ActiveCallsAdapter: ObservableAdapter<Call>
{
public ActiveCallsAdapter(Activity activity, ObservableCollection<Call> calls)
: base(activity, calls)
{
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var item = items[position];
var view = (convertView ?? context.LayoutInflater.Inflate(Resource.Layout.Call, parent, false)) as LinearLayout;
//View view = convertView;
//if (view == null) // no view to re-use, create new
// view = context.LayoutInflater.Inflate(Resource.Layout.Call, null);
SetTextView(view, Resource.Id.CallerName, item.CallerName);
SetTextView(view, Resource.Id.CallerNumber, item.CallerNumber);
SetTextView(view, Resource.Id.CallStatus, item.State.ToString());
SetTextView(view, Resource.Id.CallDuration, item.Duration);
return view;
}
public void Update(LinearLayout view, Call item)
{
SetTextView(view, Resource.Id.CallerName, item.CallerName);
SetTextView(view, Resource.Id.CallerNumber, item.CallerNumber);
string identifier = "callState_" + item.State.ToString();
int resourceId = Application.Context.Resources.GetIdentifier(identifier, "string", Application.Context.PackageName);
string callStateString = item.State.ToString();
if (resourceId != 0)
{
try
{
callStateString = Application.Context.Resources.GetString(resourceId);
}
catch (Exception e)
{
AndroidLogModel.Model.AddLogMessage("ActiveCallsAdapter", "Unable to find call state string with resource id " + resourceId + " state string: " + identifier, 3);
}
}
SetTextView(view, Resource.Id.CallStatus, callStateString);
//SetTextView(view, Resource.Id.CallDuration, item.Duration);
}
public void UpdateDuration(LinearLayout view, Call item)
{
SetTextView(view, Resource.Id.CallDuration, item.Duration);
}
}
And the base class of that adapter
public class ObservableAdapter<T>: BaseAdapter<T>
{
protected readonly Activity context;
protected readonly ObservableCollection<T> items;
public ObservableAdapter(Activity context, ObservableCollection<T> collection)
{
this.context = context;
this.items = collection;
//this.collection.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(collection_CollectionChanged);
this.items.CollectionChanged += (sender, e) => NotifyDataSetChanged();
}
void collection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NotifyDataSetChanged();
}
public override T this[int position]
{
get { return items[position]; }
}
public override int Count
{
get { return items.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var item = items[position];
var view = (convertView ?? context.LayoutInflater.Inflate(Resource.Layout.Call, parent, false)) as LinearLayout;
// configure view here
return view;
}
protected void SetTextView(LinearLayout view, int id, string text)
{
var textView = view.FindViewById<TextView>(id);
if (textView != null)
textView.SetText(text, TextView.BufferType.Normal);
}
}
My Mono skills are limited so I don't know if I fully understood your adapter, anyway I've adapted some old code and made an adapter that expands a single item when click, also it will move the ListView in onResume to a desired position:
private static class CustomAdapter extends BaseAdapter {
// the data
private ArrayList<String> mData;
// an int pointing to a position that has an expanded layout,
// for simplicity I assume that you expand only one item(otherwise use
// an array or list)
private int mExpandedPosition = -1; // -1 meaning no expanded item
private LayoutInflater mInflater;
public CustomAdapter(Context context, ArrayList<String> items) {
mInflater = LayoutInflater.from(context);
mData = items;
}
public void setExpandedPosition(int position) {
// if the position equals mExpandedPosition then we have a click on
// the same row so simply toggle the row to be gone again
if (position == mExpandedPosition) {
mExpandedPosition = -1;
} else {
// else change position of the row that was expanded
mExpandedPosition = position;
}
// notify the adapter
notifyDataSetChanged();
}
#Override
public int getCount() {
return mData.size();
}
#Override
public String getItem(int position) {
return mData.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.ad_expandedelement,
parent, false);
}
((TextView) convertView.findViewById(R.id.textView1))
.setText(getItem(position));
// see if there is an expanded position and if we are at that
// position
if (mExpandedPosition != -1 && mExpandedPosition == position) {
// if yes simply expand the layout
convertView.findViewById(R.id.button1).setVisibility(
View.VISIBLE);
} else {
// this is required, we must revert any possible changes
// otherwise the recycling mechanism will hurt us
convertView.findViewById(R.id.button1).setVisibility(View.GONE);
}
return convertView;
}
}
The onListItemClick will simply be:
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// set the expanded(or collapsed if it's a click on the same row that
// was previously expanded) row in the adapter
((CustomAdapter) getListView().getAdapter())
.setExpandedPosition(position);
}
and in onResume will have:
#Override
protected void onResume() {
super.onResume();
// set the position to the desired element
((CustomAdapter) getListView().getAdapter()).setExpandedPosition(15);
// set the selection to that element so we can actually see it
// this isn't required but has the advantage that it will move the
// ListView to the desired
// position if not visible
getListView().setSelection(15);
}
The R.layout.ad_expandedelement is a simple vertical LinearLayout with a TextView and an initially hidden(visibility set to gone) Button. For this Button I change the visibility to simulate expanding/collapsing a row in the ListView. You should be able to understand my code, if you want I can post on github the full sample.
While I'm not sure of the exact equivalent in C#/Mono, the Android framework provides a callback on Activity called onWindowFocusChanged() that indicates the period when the Window associated with a given Activity is visible to the user. You may have better luck waiting to call your selection method until that time, as the ListView should be measured and laid out by that point. In Java, it would be something like this:
#Override
public void onWindowFocusChanged (boolean hasFocus) {
if (hasFocus) {
selectItem(position);
}
}
You may need to have a bit more logic in there, this callback is directly associated with window focus and isn't a true lifecycle method. I can get called multiple times if you are displaying Dialogs or doing other similar operations.
I have a ListView backed by SimpleCursorAdapter and custom ViewBinder. I want to make items in this listview change their color on clicking. If I do that in the OnClickListener - it works paritally, changing the color of the item clicked, and of the items down the list, each 7th (I guess, the period depends on on the viewable area of the listview).
Can anyone suggest how to deal with this? Or, maybe point to a more elegant way of making items in the listView selectable?
Thank you.
UPD: (sorry for bad formatting - this is the first time I post a question):
Below is how I try to make an item in the ListView "selected":
private void setupListView(final ListView lv) {
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> adapterView, View view, int position, final long id) {
RelativeLayout layout = (RelativeLayout) view;
int color;
if (conditionMet) {
color = R.color.gray;
} else {
color = R.color.red;
}
for(int i = 0; i < layout.getChildCount(); i++) {
((TextView)layout.getChildAt(i)).setTextColor(getResources().getColor(color));
}
return;
}}
This is how I init the adapter:
final SimpleCursorAdapter adapter =
new SimpleCursorAdapter(
this,
itemId,
cursor,
from,
to
);
adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
final TextView textView = (TextView) view;
// do necessary conversions
return true;
}
});
listView.setAdapter(adapter);
You can use the property android:listSelector to set the theme or any drawable or color of the currently selected item in a list.
Since no other answer, and, I think, I had some troubles with the suggestion below, I post how I did it:
I store ids of the items clicked in a special map
in the listview onclick I check whether the id of the just clicked item is in the map: if yes, I remove it and make the item and its children color A, otherwise I add the id to the map and set the color to B
public void onItemClick(AdapterView<?> adapterView, View view, int position, final long id) {
Context ctx = MainActivity.this;
RelativeLayout layout = (RelativeLayout) view;
try {
int color;
if (items.containsKey(id)) {
items.remove(id);
color = R.color.gray;
tempIds.remove(id);
} else {
items.put(id, sum);
color = R.color.red;
tempIds.add(id);
}
for (int i = 0; i < layout.getChildCount(); i++) {
final TextView textView = (TextView) layout.getChildAt(i);
textView.setTextColor(getResources().getColor(color));
}
} catch (ParseException e) {
Log.e(MainActivity.class.toString(), "Exception parsing", e);
}
return;
}
}