Update GridView/ListView without re-populating
Asked Answered
C

3

7

whenever you update a GridView/ListView you call notifyDatasetChanged on the adapter which re-populates the the list with the new data removing everything that was currently in the list.

now take this little video for example from the L Preview design guidelines

is it possible to accomplish this same effect where the only change you see is the new items coming in without any "flicker" when the adapter reloads or is this something that can only be done in L right now?

This effect can also be found in the Google Keep app when adding new notes

Catlaina answered 19/8, 2014 at 0:22 Comment(3)
Maybe this is useful for you : android.googlesource.com/platform/packages/apps/Launcher/+/…Donela
@Donela care to explain how this may help with my question?Catlaina
That launcher is adding icons to home screen(GridView) when you install a new app, without re-populating. I thought this is what you want?Donela
S
8

I'm afraid that its all visual trickery - aka animation.

...removing everything that was currently in the list.

Actually, no.

notifyDataSetChanged only tells the underlying observers that the data has changed. That's it. In response, getView(...) is called for each visible view - the number accessible by ListView#getChildCount(). Since either indices have changed(adding/deleting items), or the data held in objects at those indices has(^) changed(one or more items have changed), the data is(^) refreshed visually by subsequent calls to ListView#getView(...).

There is an interesting video by 'Android Developers' that explains how you can produce the effect you are after: DevBytes: ListView Cell Insertion Animation.

The video/example only talks about single item insertion. But it should be extendable to all data operations with some time and math skills.

I was able to extend the example code to insert multiple items. I changed the animation to a simpler alpha-fade-in. Clicking the Add row button adds a random number(between 1 and 3 inclusive) of items to the listview. Here is what it looks like:

enter image description here

Psuedo-workflow:

  • before passing the new items to the adapter, loop through all visible items in the listview and save their bounds(in Rect) and snapshot(in BitmapDrawables)

  • pass items to adapter

  • add an OnPreDrawListener to ListView's ViewTreeObserver.

  • when onPreDraw(...) is called, the new items are ready to be drawn. We can access their views using ListView#getChildAt(...) with respective indices.

  • all new items are set to invisible and alpha animators are assigned

  • all old items are assigned translation_y animators

  • items that will no longer be accessible through getChildAt(..) - because of adding new items - are assigned translation_y animators - to get them off the screen(or out of listview bounds)

  • translation_y animators are fired. When these animators are done running, alpha animators are started.

Notice that the kind of animation (alpha, translation, scale, or any combination of these) is relatively insignificant. I suppose this will be much harder in case of a GridView or StaggeredGridView(keep) - only because the math will involve translations in both X and Y. The workflow should remain the same though.

(^) - grammatically incorrect, but natural IMO.

Suffice answered 21/8, 2014 at 19:54 Comment(2)
In response, getView(...) is called for each visible view that is what I meant by removing everything in the list. I had a feeling that this was going to involve creating a copy of sorts and moving that copy. This is probably going to be the answer that gets me to what want unless someone else provides something better (which I doubt).Catlaina
@Catlaina ... that is what I meant by removing everything in the list. Oh, ok. I too, will be waiting for a better(and/or easier) solution. Its an interesting question... thanks.Suffice
L
1

This could achieved by grabbing the view and changing the data there in. I have included one example where on long click you change the data without refreshing the visible items and without calling notifyDataSetChanged().

This question has been asked at the Google I/O 2010, you can watch it here:

The world of ListView, time 52:30

Code adapted from Vogella

package com.example.stableids;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import android.os.Build;
import android.os.Bundle;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.database.DataSetObserver;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
@SuppressLint("NewApi")
public class MainActivity extends Activity {

      @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @SuppressLint("NewApi")
    @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final ListView listview = (ListView) findViewById(R.id.listview);
        String[] values = new String[] { "Android", "iPhone", "WindowsMobile",
            "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X",
            "Linux", "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux",
            "OS/2", "Ubuntu", "Windows7", "Max OS X", "Linux", "OS/2",
            "Android", "iPhone", "WindowsMobile" };

        final ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i < values.length; ++i) {
          list.add(values[i]);
        }
        final StableArrayAdapter adapter = new StableArrayAdapter(this,
            android.R.layout.simple_list_item_1, list);
        listview.setAdapter(adapter);

        listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {

          @SuppressLint("NewApi")
        @Override
          public void onItemClick(AdapterView<?> parent, final View view,
              final int position, long id) {
            final String item = (String) parent.getItemAtPosition(position);
            view.animate().setDuration(2000).alpha(0)
                .withEndAction(new Runnable() {
                  @Override
                  public void run() {
                    int i = list.indexOf(item);

                    list.remove(item);
                    list.add(i, "MODIFIED");
                    adapter.mIdMap.put("MODIFIED", position);
                    TextView tv = (TextView)view.findViewById(android.R.id.text1);
                    tv.setText("MODIFIED");
                    //adapter.notifyDataSetChanged();
                    view.setAlpha(1);
                  }
                });
          }

        });
      }

      private class StableArrayAdapter extends ArrayAdapter<String> {

        HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();

        public StableArrayAdapter(Context context, int textViewResourceId,
            List<String> objects) {
          super(context, textViewResourceId, objects);
          for (int i = 0; i < objects.size(); ++i) {
            mIdMap.put(objects.get(i), i);
          }
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            Log.d("STABLE ID", "getView called for " + position + " position");
            return super.getView(position, convertView, parent);

        }

        @Override
        public long getItemId(int position) {
          String item = getItem(position);
          return mIdMap.get(item);
        }

        @Override
        public boolean hasStableIds() {
          return true;
        }

      }

}
Latticed answered 27/8, 2014 at 9:59 Comment(0)
W
0

Updating GridView/ListView without repopulating

If you use the same array List for every time Update And Delete time without creating new ArrayList then your adapter.notifyDatasetChanged() method work. Please you same arrayList each and every time and do n't create new Instance of the Adapter class

Wunder answered 21/8, 2014 at 13:15 Comment(1)
I dont create a new instance of the adapter every time and use the same list. As you can read in my question I use notifyDatasetChanged to update my gridCatlaina

© 2022 - 2024 — McMap. All rights reserved.