I want to add some animation to my ListView with SimpleCursorAdapter when my DB data changes.
I am using SimpleCursorAdapter with LoaderManager
I tried to add animation in getView method
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view=super.getView(position, convertView, parent);
//My animation(nineoldandroid)
ObjectAnimator.ofFloat(view,"alpha",0,1).setDuration(500).start();
return view;
}
But this method animates all items, although some items were the same before updating.
Does Android have methods to detect which items need update, remove or add?
For example, maybe there are methods for getting changes between old and new cursor before calling myAdapter.swapCursor(newCursor); It would be possible to mark items(removed, updated, new) and make removing animation before swapping and ither animation in getView.
How can I solve this problem?
There are many ways to achieve this, you can try the answers in this question :
How to Animate Addition or Removal of Android ListView Rows
The 1st answer is working
No, at least I haven't found that Android can report to me if there are new or deleted items. I made something similar where the items of the ListView animate to their new positions and I had to track their positions myself to do that.
For your use case, it is probably easier to put the fading animation just before you delete the row from the database. So wherever in your code it is that you delete the row from the database and trigger a refresh, you do the fade animation on the item and on completion of the animation you delete the row and trigger a refresh.
Update
Hmmm, I read too quickly. Your example is not a fade-out on deleted items, but a fade-in on new items.
You will have to track item IDs yourself in your adapter. This approach can only work if you have a limited number of items to show because the list of previous item IDs will have to be stored in memory. If you can not be certain the list is never too large then you'll have to find a way to add the 'newness' information to the cursor itself.
Let me see if I can cook something up. Be back later...
Update 2
Below is a SimpleCursorAdapter that keeps track of the item ID from the previous cursor. In getView() it kicks of the fade-in animation if the requested view is for an item that was not present before.
private static class FadeInAdapter extends SimpleCursorAdapter {
private List<Long> previousItemIds = new ArrayList<Long>();
private FadeInAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {
super(context, layout, c, from, to, flags);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View result = super.getView(position, convertView, parent);
// check if this view is for an item that is new
long itemId = getItemId(position);
if (!previousItemIds.contains(itemId)) {
// do the fade-in animation
result.setAlpha(0);
result.animate().alpha(1).setDuration(500).start();
}
return result;
}
#Override
public void changeCursor(Cursor cursor) {
Cursor oldCursor = getCursor();
if (oldCursor != null) {
// store previous item IDs to check against for newness with the new cursor
final int idColumnIndex = oldCursor.getColumnIndexOrThrow(DatabaseContract._ID);
previousItemIds.clear();
for (oldCursor.moveToFirst(); !oldCursor.isAfterLast(); oldCursor.moveToNext()) {
previousItemIds.add(oldCursor.getLong(idColumnIndex));
}
}
super.changeCursor(cursor);
}
}
Maybe RecyclerView helps. Try notifyItemChanged
Related
I have a ListView populated from a database using a customized ResourceCursorAdapter with a bindView to format each list item. The items are displayed in descending chronological order and I need to hightlight the first future event. Unfortunately I only know that it is the first future event when I process the next entry and find that it is in the past. What I really need is the ability to 'look ahead' in the bindView processing but that is not there.
The only alternative (that I can think of) is to process all items and then go back to change the background of the first future event (which I would then know).
The code I'm using is
EventsAdapter db = new EventsAdapter(this);
db.open();
Cursor cursor = db.fetchAllRecords();
final MyAdapter listitems = new MyAdapter(this,R.layout.timeline_detail, cursor, 0);
timeline.setAdapter(listitems);
db.close();
View v = timeline.getChildAt(firstEvent); // firstevent is set in the bindView
v.setBackgroundColor(Color.parseColor("#ffd1d1"));
However the View v is always null and, when I run it in debug mode with a breakpoint at the View statement, the listview has not yet rendered on the screen. I'm assuming, therefore, that that is why the view is still null.
How can I get and changeView the ListView item that needs to be changed at the time that the ListView is first displayed?
Try and extend your adapter and override getView.
final MyAdapter listitems = new MyAdapter(this,R.layout.timeline_detail, cursor, 0){
public View getView(int position, View convertView, ViewGroup parent){
View v = super.getView(position, convertView, parent);
if (position==0){
//Do something to the first item (v)
}else{
//Revert what you did above, since views get recycled.
}
return v;
}
};
or just include that in your MyAdapter class.
I am doing a project where i am a using a custom list view, which contains a textview. Data is coming from the server. I want to change the height of the cell, based on the data size. If the content is more than two lines, i have to trim it to two lines and show a see more button. On clicking the see more button expand the cell to show full details.I googled about it. But i cannot find any useful resource.Could any one help me to solve this, any useful links or suggestions?`
One way to do this is to first invalidate the listView forcing it to redraw the visible elements and then handle the layout change in the getView method of your adapter.
Try this:
Add a showTrimmed(int position, boolean value) method to your adapter. This will update a structure inside your adapter that keeps track of the list items that you want to trim or not trim. Then in the getView method, when creating the item view, you test if the current element should be trimmed or not and based on the result you create the proper view.
I did something similar to achieve a different but similar result and it works.
Remember to call invalidate after calling showTrimmed to force the listView to redraw the displayed items.
Edit: I post my code which is different from what you want to do but it's quite similar in the idea:
public class HeroListViewAdapter extends ArrayAdapter<Hero> {
public static int NO_POS = -1;
private List<Hero> heroes;
private int selected_pos = NO_POS;
public HeroListViewAdapter(Context context, List<Hero> heroes){
super(context, R.layout.hero_list_item_view, heroes);
this.heroes = heroes;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = GuiBuilder.createHeroListItemView(heroes.get(position),getContext(),parent,false);
if(position == selected_pos){
rowView.setBackgroundColor((rowView.getResources().getColor(R.color.list_item_selected_color)));
}
return rowView;
}
public void setSelectedPosition(int selected_pos){
this.selected_pos = selected_pos;
}
public int getSelectedPosition(){
return selected_pos;
}
public Hero getSelectedHero(){
if(selected_pos>=0 && selected_pos<heroes.size())
return this.getItem(selected_pos);
else
return null;
}
}
instead of setSelectedPosition you should have the showTrimmed method which updates an inner member as setSelectedPos does, in order to keep track of the trimmed state of every list item. Then in the getView method you do the test (like I do in if(position == selected_pos)) and then you build your custom trimmed or not trimmed list item based on the result of the test. My listener which uses these functions is:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener(){
public void onItemClick(AdapterView<?> parent, View view, int position, long id){
list_adapter.setSelectedPosition(position);
listView.invalidate();
}
}
);
you can try to update this idea to your needs. In particular in my case I do this to highlight the clicked listview item by programmatically changing its background.
I have a ListView (set to CHOICE_MODE_SINGLE)
I have a SimpleCursorAdapter who fill my ListView. Now i work on selection.
serviceListView.setAdapter(
new SimpleCursorAdapter(this, R.layout.service_listitem, cursor, new String[] { "name" }, new int[] { R.id.service_name }) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final View renderer = super.getView(position, convertView, parent);
if (position == newSelectedPosition) {
renderer.setBackgroundResource(R.layout.list_view_layer_list);
} else {
renderer.setBackgroundResource(android.R.color.transparent);
}
return renderer;
}
}
);
So i want when i select a row my layout to be applied. This works fine.
But in some cases when i push for 2-3 secs a row and then drag a little bit and release the row i obtain 2 rows selected.
I try several ways to get ride of the initial selection, overwriting OnTouchListener, OnScrollListener, OnLongClickListener. No results.
Any help is welcome
Thanks
Have you tried calling notifyDataSetChanged() on your Adapter after the selection has been changed? That should cause all the rows to be rebound again, and all the views (except for the selected one) to revert back to the transparent background.
I quit this implementation.
I will try to "simulate" ListView by using a ScrollView with TextView-s for each row
I have an application in which I'd like one row at a time to have a certain color. This seems to work about 95% of the time, but sometimes instead of having just one row with this color, it will allow multiple rows to have the color. Specifically, a row is set to have the "special" color when it is tapped. In rare instances, the last row tapped will retain the color despite a call to setBackgroundColor attempting to make it otherwise.
private OnItemClickListener mDirectoryListener = new OnItemClickListener(){
public void onItemClick(AdapterView parent, View view, int pos, long id){
if (stdir.getStationCount() == pos) {
stdir.moreStations();
return;
}
if (playingView != null)
playingView.setBackgroundColor(Color.DKGRAY);
view.setBackgroundColor(Color.MAGENTA);
playingView = view;
playStation(pos);
}
};
I have confirmed with print statements that the code setting the row to gray is always called.
Can anyone imagine a reason why this code might intermittently fail? If there is a pattern or condition that causes it, I can't tell.
I thought it might have something to do with the activity lifecycle setting the "playingView" variable back to null, but I can't reliably reproduce the problem by switching activities or locking the phone.
private class DirectoryAdapter extends ArrayAdapter {
private ArrayList<Station> items;
public DirectoryAdapter(Context c, int resLayoutId, ArrayList<Station> stations){
super(c, resLayoutId, stations);
this.items = stations;
}
public int getCount(){
return items.size() + 1;
}
public View getView(int position, View convertView, ViewGroup parent){
View v = convertView;
LayoutInflater vi = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (position == this.items.size()) {
v = vi.inflate(R.layout.morerow, null);
return v;
}
Station station = this.items.get(position);
v = vi.inflate(R.layout.songrow, null);
if (station.playing)
v.setBackgroundColor(Color.MAGENTA);
else if (station.visited)
v.setBackgroundColor(Color.DKGRAY);
else
v.setBackgroundColor(Color.BLACK);
TextView title = (TextView)v.findViewById(R.id.title);
title.setText(station.name);
return v;
}
};
ListViews don't create instances of contained views for every item in the list, but only for ones that are actually visible on the screen. For performance reasons, they try and maintain as few views as possible, and recycle them. That's what the convertView parameter is.
When a view scrolls off the screen, it may be recycled or destroyed. You can't hold a reference to an old view and assume that it will refer to the item you expect it to in the future. You should save the ID of the list item you need and look that up instead.
Moreover, there are a couple of other issues with your implementation (from a best practices standpoint). You seem to be ignoring the convertView parameter and creating a new view from scratch each time. That can cause your application to bog down a bit while scrolling if you have a long list. Secondly, instead of adding the "more" element the way you do, you're better of setting it with setFooterView().
There's an excellent talk on the ListView from Google I/O 2010 that covers these and other issues. It's an hour long, but definitely worth watching in its entirety.
I'm using a listview with an adapter extending CursorAdapter.
The listitems contains a few TextViews which can contain arbitrary lengths of text.
The problem is now as the views (in the listview) are recycled, items might get alot higher than needed since a previous item in the view needed bigger space.
I guess that the solution is to somehow to not allow recycling, or just force set the size of the view upon it being bound. I've been trying a number of different solutions but I haven't found a way. Could someone please help me? ;)
#Override
public View newView(Context context, Cursor c, ViewGroup parent)
{
settings = ctx.getSharedPreferences("myprefs", 0);
View v = inflater.inflate(R.layout.convoview_list_item, parent,false);
ctx2 = context;
parentGroup = parent;
return v;
}
#Override
public void bindView(View v, Context context, Cursor c)
{
//Adding text etc to my views from the cursor here.
}
If you know that the list will always be very small, i.e. 10 entries, here is what I do (override these 2 methods). So, when view is recycled, the same type is returned for each position. If you know that your list could be large but there could be only 3-4 types of list items, then instead of returning position, asssign type 0, 1, 2, etc.
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public int getViewTypeCount() {
return MAX_ITEMS;
}
The problem was that in my bindView implementation made some TextViews "invisible" instead of "gone" and because of this they took up space even though you couldn't see them.