Recently, I find out the ListView
expand/collapse animation appears much slower in DialogFragment
than in Activity
.
In DialogFragment
, the animation FPS is 10 fps. Each animation takes 100ms.
In Activity
, the animation FPS is 50 fps. Each animation takes 20ms.
I took this wonderful open source library as benchmark example : https://github.com/nhaarman/ListViewAnimations
I made modification on https://github.com/nhaarman/ListViewAnimations/blob/master/example/src/com/haarman/listviewanimations/itemmanipulationexamples/ItemManipulationsExamplesActivity.java#L51, to test between performance in DialogFragment
and Activity
.
public void onExpandListItemAdapterClicked(View view) {
// For Activity testing.
Intent intent = new Intent(this, ExpandableListItemActivity.class);
startActivity(intent);
// For DialogFragment testing.
//FragmentManager fm = this.getSupportFragmentManager();
//DemoDialogFragment demoDialogFragment = new DemoDialogFragment();
//demoDialogFragment.show(fm, "DemoDialogFragment");
}
I made a minor modification on https://github.com/nhaarman/ListViewAnimations/blob/master/example/src/com/haarman/listviewanimations/itemmanipulationexamples/ExpandableListItemActivity.java
public class ExpandableListItemActivity extends MyListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyExpandableListItemAdapter myExpandableListItemAdapter = new MyExpandableListItemAdapter(this, getItems());
//AlphaInAnimationAdapter alphaInAnimationAdapter = new AlphaInAnimationAdapter(myExpandableListItemAdapter);
//alphaInAnimationAdapter.setAbsListView(getListView());
getListView().setAdapter(myExpandableListItemAdapter);
Toast.makeText(this, R.string.explainexpand, Toast.LENGTH_LONG).show();
}
I created my own DialogFragment
class for testing. Based entirely from ExpandableListItemActivity
package com.haarman.listviewanimations.itemmanipulationexamples;
import java.util.ArrayList;
import java.util.List;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.haarman.listviewanimations.ArrayAdapter;
import com.haarman.listviewanimations.R;
import com.haarman.listviewanimations.itemmanipulation.ExpandableListItemAdapter;
public class DemoDialogFragment extends DialogFragment {
private ListView mListView;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mListView = new ListView(getActivity());
mListView.setDivider(null);
MyExpandableListItemAdapter myExpandableListItemAdapter = new MyExpandableListItemAdapter(getActivity(), getItems());
//AlphaInAnimationAdapter alphaInAnimationAdapter = new AlphaInAnimationAdapter(myExpandableListItemAdapter);
//alphaInAnimationAdapter.setAbsListView(getListView());
getListView().setAdapter(myExpandableListItemAdapter);
Toast.makeText(getActivity(), R.string.explainexpand, Toast.LENGTH_LONG).show();
final AlertDialog dialog = new AlertDialog.Builder(this.getActivity())
.setView(mListView)
.create();
return dialog;
}
public ListView getListView() {
return mListView;
}
protected ArrayAdapter<Integer> createListAdapter() {
return new MyListAdapter(getActivity(), getItems());
}
public static ArrayList<Integer> getItems() {
ArrayList<Integer> items = new ArrayList<Integer>();
for (int i = 0; i < 1000; i++) {
items.add(i);
}
return items;
}
private static class MyListAdapter extends ArrayAdapter<Integer> {
private Context mContext;
public MyListAdapter(Context context, ArrayList<Integer> items) {
super(items);
mContext = context;
}
@Override
public long getItemId(int position) {
return getItem(position).hashCode();
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv = (TextView) convertView;
if (tv == null) {
tv = (TextView) LayoutInflater.from(mContext).inflate(R.layout.list_row, parent, false);
}
tv.setText("This is row number " + getItem(position));
return tv;
}
}
private static class MyExpandableListItemAdapter extends ExpandableListItemAdapter<Integer> {
private Context mContext;
private LruCache<Integer, Bitmap> mMemoryCache;
/**
* Creates a new ExpandableListItemAdapter with the specified list, or an empty list if
* items == null.
*/
private MyExpandableListItemAdapter(Context context, List<Integer> items) {
super(context, R.layout.activity_expandablelistitem_card, R.id.activity_expandablelistitem_card_title, R.id.activity_expandablelistitem_card_content, items);
mContext = context;
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory;
mMemoryCache = new LruCache<Integer, Bitmap>(cacheSize) {
@Override
protected int sizeOf(Integer key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
}
@Override
public View getTitleView(int position, View convertView, ViewGroup parent) {
TextView tv = (TextView) convertView;
if (tv == null) {
tv = new TextView(mContext);
}
tv.setText(mContext.getString(R.string.expandorcollapsecard, getItem(position)));
return tv;
}
@Override
public View getContentView(int position, View convertView, ViewGroup parent) {
ImageView imageView = (ImageView) convertView;
if (imageView == null) {
imageView = new ImageView(mContext);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
}
int imageResId;
switch (getItem(position) % 5) {
case 0:
imageResId = R.drawable.img_nature1;
break;
case 1:
imageResId = R.drawable.img_nature2;
break;
case 2:
imageResId = R.drawable.img_nature3;
break;
case 3:
imageResId = R.drawable.img_nature4;
break;
default:
imageResId = R.drawable.img_nature5;
}
Bitmap bitmap = getBitmapFromMemCache(imageResId);
if (bitmap == null) {
bitmap = BitmapFactory.decodeResource(mContext.getResources(), imageResId);
addBitmapToMemoryCache(imageResId, bitmap);
}
imageView.setImageBitmap(bitmap);
return imageView;
}
private void addBitmapToMemoryCache(int key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
private Bitmap getBitmapFromMemCache(int key) {
return mMemoryCache.get(key);
}
}
}
Then, I place my benchmark code in this file : https://github.com/nhaarman/ListViewAnimations/blob/master/library/src/com/haarman/listviewanimations/itemmanipulation/ExpandableListItemAdapter.java#L277
private ValueAnimator createHeightAnimator(int start, int end) {
Log.i("CHEOK", start + " -> " + end);
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = mContentParent.getLayoutParams();
layoutParams.height = value;
Log.i("CHEOK", "height = " + value);
mContentParent.setLayoutParams(layoutParams);
}
});
return animator;
}
Here is the result I'm getting
// Using DialogFragment
10-12 03:36:05.695: I/CHEOK(29407): height = 0
10-12 03:36:06.258: I/CHEOK(29407): height = 0
10-12 03:36:06.347: I/CHEOK(29407): height = 74
10-12 03:36:06.547: I/CHEOK(29407): height = 358
10-12 03:36:06.621: I/CHEOK(29407): height = 360
// Using Activity
10-12 03:37:02.375: I/CHEOK(29956): height = 0
10-12 03:37:02.387: I/CHEOK(29956): height = 0
10-12 03:37:02.406: I/CHEOK(29956): height = 3
10-12 03:37:02.461: I/CHEOK(29956): height = 51
10-12 03:37:02.476: I/CHEOK(29956): height = 72
10-12 03:37:02.492: I/CHEOK(29956): height = 98
10-12 03:37:02.512: I/CHEOK(29956): height = 131
10-12 03:37:02.531: I/CHEOK(29956): height = 164
10-12 03:37:02.547: I/CHEOK(29956): height = 196
10-12 03:37:02.562: I/CHEOK(29956): height = 228
10-12 03:37:02.582: I/CHEOK(29956): height = 260
10-12 03:37:02.601: I/CHEOK(29956): height = 290
10-12 03:37:02.617: I/CHEOK(29956): height = 312
10-12 03:37:02.633: I/CHEOK(29956): height = 331
10-12 03:37:02.652: I/CHEOK(29956): height = 348
10-12 03:37:02.672: I/CHEOK(29956): height = 357
10-12 03:37:02.687: I/CHEOK(29956): height = 360
Is there any idea, why this happens? How we can improve the performance in DialogFragment
?
I place the entire project files, just in case you're interested to test out.
https://www.dropbox.com/s/5q6ttn11o92g39n/ListViewAnimations-master.zip
Update
By tracing using TraceView, I notice that when using DialogFragment
to expand ListView
item, the ArrayAdapter
will be called frequent. I'm not exactly sure why it is so. I do another independent implementation without using the open source library. I'm still facing the same problem.
Perform animation on DialogFragment
, the ArrayAdapter
's getView
will be triggered frequently while animation is in progress.
For Activity, when animation is in progress, ArrayAdapter
's getView
will NOT be triggered.
TraceView while using DialogFragment
TraceView while using Activity
I find out the key solution to this problem is, fix the dialog size always. Once we fix the size, no resource will be spent to perform size computation during animation.