I'm currently making a SMS Application in Android, the following is a code snippet from Inbox Listactivity, I have requested a cursor from the contentresolver and used a custom adapter to add custom views into the list.
Now, in the custom view i've got 2 TextViews (tvFullBody,tvBody)...
tvFullBody contains the Full SMS Text while tvBody contains a short preview (35 characters)
The tvFullBody Visibility is by default set to GONE.
My idea is, when the user clicks on a list item, the tvBody should dissappear(GONE) and the tvFullBody should become visible (VISIBLE). On Clicking again, it should revert back to its original state.
//isExpanded is a BitSet of the size = no of list items...keeps track of which items are expanded and which are not
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
if(isExpanded.get(position))
{
v.findViewById(R.id.tvFullBody).setVisibility(View.GONE);
v.findViewById(R.id.tvBody).setVisibility(View.VISIBLE);
}else
{
v.findViewById(R.id.tvFullBody).setVisibility(View.VISIBLE);
v.findViewById(R.id.tvBody).setVisibility(View.GONE);
}
isExpanded.flip(position);
super.onListItemClick(l, v, position, id);
}
The Code works as it is supposed to :) except for an undesired sideeffect....
Every 10th (or so) List Item also gets "toggled".
eg. If i Expand the 1st, then the 11th, 21th list items are also expanded...Although they remain off screen, but on scrolling you get to see the undesired "expansion".
By my novice analysis, i'm guessing Listview keeps track of 10 list items that are currently visible, upon scrolling, it "reuses" those same variables, which is causing this problem...(i didn't check the android source code yet.)
I'd be gratefull for any suggestion, on how i should tackle this! :)
I'm open to alternative methods aswell....Thanks in advance! :)
Your diagnosis of the problem almost correct. What's happening is that Android is reusing the Views it creates to display the list to save memory. Instead of creating a new View for every item in your list, Android creates just enough to fill the screen, updating them to show the relevant data for the items which are currently visible.
So when you show tvFullBody when the user clicks on an item, when that View is re-used later tvFullBody is still visible.
You may have to write you own ListAdapter to make sure the Views are displayed how you want. It should be as simple as extending the ListAdapter you are currently using and overriding the getView() method to ensure that tvFullBody is hidden.
Related
I have a question about some behavior in an AdapterView.OnItemClickListener that has me a bit flummoxed.
I have a ListView backed by a custom CursorAdapter. The Cursor is being managed by a LoaderManager. The ListView also has an OnItemClickListener. The data loads properly and the ListView is correctly populated - in this case with many items.
What I'm finding is that the position argument to onItemClick is 0-based relative to the items showing on the screen, not the entire list. So for example if I scroll down and select the first viewable item, I'll actually get the first item in the list (which is not visible) instead of the one I selected. When I use a debugger, I see that position actually has a value of 0 even though the item selected was much further down in the list.
What's odd is that this worked fine until I started using the LoaderManager.
I'm using the v4 support library.
If I do something like this:
public void onItemClick( AdapterView<?> parent, View listItem, int position, long id ) {
Cursor c = myListAdapter.getItem( parent.getFirstVisiblePosition() + position );
// do stuff
Then I get the data I actually selected, but I somehow feel this is wrong. I shouldn't have to use getFirstVisiblePosition().
Any ideas?
Thanks
I guess you might be using the convertView parameter of the getView() method to recycle the views. Using this might be causing the issues here. Kindly try using an alternative and check whether the issue still exists or not
I have two spinners. When you select an item in one of them, the item selected (now appearing at the top), turns red. The spinners are tied to one another in a mutually-exclusive sense: If you select something in one, the other one goes back to the initial ("title") selection and turns white.
This is all done via the onItemSelected listeners:
sectionSpin.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View arg1,
int position, long arg3) {
System.out.println("SECTION SPIN # " + position);
issueSpin.setSelection(0);
((TextView) issueSpin.getChildAt(0)).setTextColor(Color.parseColor("#FFFFFF"));
((TextView) arg1).setTextColor(Color.parseColor("#E3170D"));
....
and vice versa for the "issue spinner". My problem is that, if I'm going from one spinner to the other and I select the top item, the onItemSelectedListener doesn't register (the println statement (or, rather, lack thereof, proves this to me) because the item being selected is already selected. So nothing happens.
What I need is something equivalent to a ButtonGroup in Swing, where I can have two seemingly different menus but add all the children to a single "mutually-exclusive" buttongroup so that only one can be selected at a time. Is there any mechanism in android that will give me something like this, or can anyone help me with a workaround?
It is impossible for a Spinner to trigger its onItemSelected() callback for an item that is already selected.
There is are examples of overriding the Spinner class that I have not been able to get to work. There is even an example on this site of overriding some of the AbsSpinner functions to make the Spinner trigger items that are already selected, but unfortunately that example no longer works due to API changes.
Therefore, I believe the best way to do this is to use a combination of a TextView, a Button (for the spindown), and an AlertDialog to hold the same ArrayAdapter that you already send to the Spinner.
You will have to do custom work for the TextView/Button layout as well as more custom work to allow changing color/text properties of the list items, but at least your DialogInterface.onItemClickedListener() will always trigger.
I have a ListView with a custom adapter, displaying information from a database.
When I start the app, the data is read from the database, given to the adapter, which is then attached to the ListView. A LayoutAnimationController is run on the ListView, displaying the contents smoothly.
this._lvList = (ListView)_v.findViewById(R.id.lvList);
this._lvList.setAdapter(new TableAdapter(getActivity(),R.layout.tablerow,Category.getCategories()));
LayoutAnimationController lac = new LayoutAnimationController(AnimationUtils.loadAnimation(getActivity(), R.anim.slide_in_left));
lac.setDelay(0.2f);
this._lvList.setLayoutAnimation(lac);
this._lvList.startLayoutAnimation();
No problem at all.
Now when I click on any entry of the ListView, the entries which were not clicked disappear, the clicked entry becomes the one and only entry in the list and is displayed at the top, as expected.
View v;
for(int i = 0;i<_lvList.getChildCount();i++) {
v = this._lvList.getChildAt(i);
if(v!=clickedView) {
Animation animSlideOut = AnimationUtils.loadAnimation(getActivity(), i%2==0?R.anim.slide_out_left:R.anim.slide_out_right);
animSlideOut.setStartOffset(i*50);
// some more stuff
}
v.startAnimation(animSlideOut);
}
This works as well, but now the problem, if I click again on that single list entry, I want the list to repopulate, displaying all items again.
I thought I could use the code from the start (the first snippet), as it works fine when starting the app, but...this time...it doesn't. No animation happening. The reason is, there are no views to animate in my ListView (except the one from the previous step).
Instead of creating a new Tableadapter I already tried to clear it, fill it new, called notifyDataSetChanged()... no use, as
_lvList.getChildCount();
stills returns 1 view, while the adapter holds all 18 entries.
I as well tried
requestLayout();
forceLayout();
invalidate();
invalidateViews();
to force the ListView to generate its child views before the animation, but it's not working. So atm the ListView just appears instantly, somewhen after my call to the layout animation.
Summary : my ListView contains no child views before the start of the layout animation, how can I force it to generate them?
Thx in advance :)
Hmm... have you done this yet? (below)
in your custom adapter, override the method:
#Override
public View getView(int position, View convertView, ViewGroup parent){
//the position of the item, the convertView is the view layout of what that row is
}
to change how the listview is updated. this getView method gets called by Android every once a while to refresh the view (you don't call it manually)
also, i think you only need to call
listView.notifiyDataSetChanged() and listView.invalidate() to force the update, after you repopulate the list
you can read more on listviews here:
http://www.vogella.com/articles/AndroidListView/article.html
http://developer.android.com/guide/topics/ui/layout/listview.html
I developd a viewpager with custom list views in android. I followed the tutorial View Pager Part 1-3. The major difference in my development is that i used a customlistview in the views of the paager. The list items in the listview also support click events to display more info about an item.
Here is the problem; I handled the onItemClick inside the instantiateItem method but i noticed that when an item is clicked, the content displayed is that of the adjacent listview (i.e if item 2 of page 2 is clicked the info displayed is item 2 of page 3).
I realised this happens because the pager loads adjacent lists so as to make rendering faster for the user so my code uses the preloaded list instead of the list in current display.
I thought to find out the appropriate way to handle listview click events in viewpager, code snippets, links,.. will really be of help. The appropriate method to use to handle the click events.
Thank you
You have to get it from the adapter of the listview (the first parameter of the onItemClick method):
#Override
public void onItemClick(AdapterView<?> a, View v, int position, long id) {
// NOT THIS!: mValue = (HereYourClass) listView.getItemAtPosition(position);
mValue = (HereYourClass)a.getAdapter().getItem(position);
}
I want to have one ListView that when I click on the item the view slide out to the left.
So I have:
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
arg1.startAnimation(openAnimation);
}
});
However, animation applied to the different item in the list instead of the one being clicked on. The behavior seems to be random as sometime it happened to more than one item at the same time.
As I suspected this is because of the way Adapter reuse it's view to create item. I went to modify the getView method in my Adapter to inflate new view every time it's being called. Then the animation doesn't happen any more.
Is there a way to resolve this? I tried to move the animation to inside my Adapter but then I can not associate it with other action on the Listview.
Ultimately, I want the item to be clickable but when swipe left/right reveal delete button (iOS delete behavior). Am I on the wrong track here? This should be possible though as Android can implement swipe to remove in the Notification bar.
I recommend that you check this thread also.
I don't think, that this is possible without having to modify your adapter to suit this type of behavior. For what I understand, you don't have any problems with implementing the code recognizing the swipe gestures on different ListView-rows, only with the animation the should follow this gesture on according row(s).
I'd rewrite the adapter to suit at least 2 row types: normal rows, and rows to be deleted. In the "getView()" method of your adapter, you should only reuse the convertView of normal Views. Rows that are to be deleted should not reuse them, so that animating one would not modify the others.
Upon clicking a normal row, you should first tell the adapter that the row on the clicked position is now of type to-be-deleted, call .notifyDatasetChanged(), and then start the animation on that row.