为什么ListView展开/折叠动画在DialogFragment中比在Activity中显得慢得多

时间:2013-10-11 19:52:02

标签: android performance listview animation

最近,我发现ListView展开{/ 1}}展开/折叠动画的速度比DialogFragment慢得多。

Activity中,动画FPS为10 fps。每个动画需要100毫秒。

DialogFragment中,动画FPS为50 fps。每个动画需要20毫秒。

我把这个很棒的开源库作为基准示例:https://github.com/nhaarman/ListViewAnimations

我在https://github.com/nhaarman/ListViewAnimations/blob/master/example/src/com/haarman/listviewanimations/itemmanipulationexamples/ItemManipulationsExamplesActivity.java#L51上进行了修改,以测试ActivityDialogFragment中的效果。

Activity

我在https://github.com/nhaarman/ListViewAnimations/blob/master/example/src/com/haarman/listviewanimations/itemmanipulationexamples/ExpandableListItemActivity.java

上做了一些小修改
    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");        
    }

我创建了自己的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(); } 课程进行测试。完全基于DialogFragment

ExpandableListItemActivity

然后,我将基准代码放在此文件中:https://github.com/nhaarman/ListViewAnimations/blob/master/library/src/com/haarman/listviewanimations/itemmanipulation/ExpandableListItemAdapter.java#L277

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

}

这是我得到的结果

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

有什么想法,为什么会这样?我们如何改善// 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 的效果?

我放置整个项目文件,以防你有兴趣测试。

https://www.dropbox.com/s/5q6ttn11o92g39n/ListViewAnimations-master.zip

更新

通过使用TraceView进行跟踪,我注意到当使用DialogFragment扩展DialogFragment项时,ListView将被频繁调用。我不确定为什么会如此。我在不使用开源库的情况下执行另一个独立的实现。我仍然面临同样的问题。

ArrayAdapter上执行动画,DialogFragment的{​​{1}}将在动画进行过程中频繁触发。

对于活动,当动画正在进行时,ArrayAdapter的{​​{1}}将不会被触发。

使用DialogFragment时的TraceView enter image description here

使用活动时的TraceView enter image description here

1 个答案:

答案 0 :(得分:1)

我发现这个问题的关键解决方案是,始终修复对话框大小。一旦我们修改了大小,动画期间就不会花费任何资源来执行大小计算。

    final ViewTreeObserver vto = view.getViewTreeObserver();
    vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        @SuppressLint("NewApi")
        @SuppressWarnings("deprecation")
        @Override
        public void onGlobalLayout() {
            // Key area to perform optimization. Fix the dialog size!
            // During ListView's rows animation, dialog will never need to 
            // perform computation again.
            int width = dialog.getWindow().getDecorView().getWidth();
            int height = dialog.getWindow().getDecorView().getHeight();
            dialog.getWindow().setLayout(width, height);

            ViewTreeObserver obs = view.getViewTreeObserver();
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                obs.removeOnGlobalLayoutListener(this);
            } else {
                obs.removeGlobalOnLayoutListener(this);
            }
        }
    });