Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update GridView/ListView without re-populating

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

like image 318
tyczj Avatar asked Aug 19 '14 00:08

tyczj


2 Answers

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.

like image 165
Vikram Avatar answered Oct 02 '22 10:10

Vikram


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;
        }

      }

}
like image 43
mesh Avatar answered Oct 03 '22 10:10

mesh