简体   繁体   中英

Scrolling ListView up- and down fast changes the order of the ListView-items

I have a ListView with a Custom ArrayAdapter and Custom Items. When I fill these Items on creation, on resume or when I'm scrolling slowly with the getView() method, everything works great.

But when I scroll down real fast up and down, the order of items changes in the ListView.

This only happens when I use the "correct" way of handling a custom ArrayAdapter in it's getView() method:

@Override
public View getView(int position, View convertView, ViewGroup parent){
    View view = convertView;
    MyViewHolder myViewHolder = null;
    if(view == null){
        view = inflater.inflate(layoutResourceId, parent, false);

        // Get the View Elements

        // Create a new ViewHolder and save the View-Elements in it
        myViewHolder = new MyViewHolder(TextView txtView1, TextView txtView2, ...);

        view.setTag(myViewHolder);
    }
    else
        myViewHolder = (MyViewHolder) view.getTag();

    // Do something with the Views like changing a text
    myViewHolder.myTextView.setText("some text");
}

I did find a solution for this fast scrolling bug provided in the following two links:

This solution is to inflate the View and create a new ViewHolder every time you come in the getView() method:

@Override
public View getView(int position, View convertView, ViewGroup parent){
    View view = inflater.inflate(layoutResourceId, parent, false);

    // Get the View Elements

    view.setTag(new MyViewHolder(TextView txtView1, TextView txtView2, ...));

    myViewHolder = (MyViewHolder) view.getTag();

    // Do something with the Views like changing a text
    myViewHolder.myTextView.setText("some text");
}

This does indeed solve the scrolling-bug, but it creates a new problem: When I'm scrolling up and down real fast I get Out of Memory Runtime errors and Memory Leaks. Here is the info in Logcat of Eclipse:

06-17 07:15:30.728: I/dalvikvm-heap(2920): Forcing collection of SoftReferences for 111376-byte allocation
06-17 07:15:30.908: I/dalvikvm-heap(2920): Clamp target GC heap from 17.614MB to 16.000MB
06-17 07:15:30.908: D/dalvikvm(2920): GC_BEFORE_OOM freed 72K, 4% free 15843K/16360K, paused 180ms, total 181ms
06-17 07:15:30.908: E/dalvikvm-heap(2920): Out of memory on a 111376-byte allocation.
06-17 07:15:30.918: I/dalvikvm(2920): "main" prio=5 tid=1 RUNNABLE
06-17 07:15:30.918: I/dalvikvm(2920):   | group="main" sCount=0 dsCount=0 obj=0xb4a9cca8 self=0xb730e398
06-17 07:15:30.918: I/dalvikvm(2920):   | sysTid=2920 nice=0 sched=0/0 cgrp=apps handle=-1225232044
06-17 07:15:30.918: I/dalvikvm(2920):   | state=R schedstat=( 20550000000 16710000000 17134 ) utm=1825 stm=230 core=0
06-17 07:15:30.918: I/dalvikvm(2920):   at android.graphics.Bitmap.nativeCreate(Native Method)
06-17 07:15:30.918: I/dalvikvm(2920):   at android.graphics.Bitmap.createBitmap(Bitmap.java:809)
06-17 07:15:30.928: I/dalvikvm(2920):   at android.graphics.Bitmap.createBitmap(Bitmap.java:769)
06-17 07:15:30.928: I/dalvikvm(2920):   at android.view.View.buildDrawingCache(View.java:13608)
06-17 07:15:30.928: I/dalvikvm(2920):   at android.view.View.getDrawingCache(View.java:13463)
06-17 07:15:30.938: I/dalvikvm(2920):   at android.view.View.draw(View.java:14156)
06-17 07:15:30.938: I/dalvikvm(2920):   at android.view.ViewGroup.drawChild(ViewGroup.java:3103)
06-17 07:15:30.938: I/dalvikvm(2920):   at android.widget.ListView.drawChild(ListView.java:3363)
06-17 07:15:30.948: I/dalvikvm(2920):   at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2940)
06-17 07:15:30.948: I/dalvikvm(2920):   at android.widget.AbsListView.dispatchDraw(AbsListView.java:2458)
06-17 07:15:30.948: I/dalvikvm(2920):   at android.widget.ListView.dispatchDraw(ListView.java:3358)
06-17 07:15:30.948: I/dalvikvm(2920):   at android.view.View.draw(View.java:14468)
06-17 07:15:30.948: I/dalvikvm(2920):   at android.widget.AbsListView.draw(AbsListView.java:3817)
06-17 07:15:30.958: I/dalvikvm(2920):   at android.view.View.draw(View.java:14350)
06-17 07:15:30.958: I/dalvikvm(2920):   at android.view.ViewGroup.drawChild(ViewGroup.java:3103)
06-17 07:15:30.958: I/dalvikvm(2920):   at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2940)
06-17 07:15:30.958: I/dalvikvm(2920):   at android.view.View.draw(View.java:14348)
06-17 07:15:30.958: I/dalvikvm(2920):   at android.view.ViewGroup.drawChild(ViewGroup.java:3103)
06-17 07:15:30.958: I/dalvikvm(2920):   at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2940)
06-17 07:15:30.958: I/dalvikvm(2920):   at android.view.View.draw(View.java:14348)
06-17 07:15:30.958: I/dalvikvm(2920):   at android.view.ViewGroup.drawChild(ViewGroup.java:3103)
06-17 07:15:30.958: I/dalvikvm(2920):   at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2940)
06-17 07:15:30.958: I/dalvikvm(2920):   at android.view.View.draw(View.java:14468)
06-17 07:15:30.968: I/dalvikvm(2920):   at com.android.internal.widget.ActionBarOverlayLayout.draw(ActionBarOverlayLayout.java:381)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.View.draw(View.java:14350)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.ViewGroup.drawChild(ViewGroup.java:3103)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2940)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.View.draw(View.java:14468)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.widget.FrameLayout.draw(FrameLayout.java:472)
06-17 07:15:30.968: I/dalvikvm(2920):   at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2326)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.ViewRootImpl.drawSoftware(ViewRootImpl.java:2496)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.ViewRootImpl.draw(ViewRootImpl.java:2409)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2253)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1883)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1000)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5670)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.Choreographer.doCallbacks(Choreographer.java:574)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.Choreographer.doFrame(Choreographer.java:544)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.os.Handler.handleCallback(Handler.java:733)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.os.Handler.dispatchMessage(Handler.java:95)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.os.Looper.loop(Looper.java:136)
06-17 07:15:30.968: I/dalvikvm(2920):   at android.app.ActivityThread.main(ActivityThread.java:5017)
06-17 07:15:30.968: I/dalvikvm(2920):   at java.lang.reflect.Method.invokeNative(Native Method)
06-17 07:15:30.968: I/dalvikvm(2920):   at java.lang.reflect.Method.invoke(Method.java:515)
06-17 07:15:30.968: I/dalvikvm(2920):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
06-17 07:15:30.968: I/dalvikvm(2920):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
06-17 07:15:30.968: I/dalvikvm(2920):   at dalvik.system.NativeStart.main(Native Method)
06-17 07:15:30.998: I/Choreographer(2920): Skipped 294 frames!  The application may be doing too much work on its main thread.

I scrolled up and down for about 10 second and got the entire Stack-Trace above, over 50 times in my Logcat..

Does anyone know a solution to fix the fast scrolling bug, without creating a new View every time like in the second code?

And if not, what is more preferable? Ignoring this bug and just let it change the order, which might lead to complains from users OR fixing this bug but having the risk of Memory Leaks and App Crashes?


SOLUTION:

With the suggestions of using a Cache I first tried Vigneshwaran Thenraj's LazyList suggestion. However, I forgot to mention I only use Images from my /res/drawable folder and don't use Images from the web. Because of this I used a very simple cache created myself. Because I re-inflate the View and re-create the ViewHolder every time it's still a bit slow on the Emulator when I scroll up and down very fast, but on a real device it isn't.

So, the solution I used to cache the images is:

// ImageLoader class to cache the Images from the Drawable folder
public class ImageLoader
{
    // Logcat tag
    private static final String TAG = "ImageLoader";

    private Context context;

    // The HashMap where we save the cached Bitmaps for the ImageViews
    // TODO: Find a way to use a synchronized SparseArray to fix the warning below
    private Map<Integer, Bitmap> cachedBitmaps = Collections.synchronizedMap(new HashMap<Integer, Bitmap>());

    // Default Checkbox Image
    private static final int DEFAULT_CHECKBOX = D.CHECKBOX_IMAGES[0];

    public ImageLoader(Context c){
        context = c;
        // Add the default Resource-ID to the WeakHashMap
        Bitmap defaultBitmap = BitmapFactory.decodeResource(context.getResources(), DEFAULT_CHECKBOX);
        if(defaultBitmap != null){
            cachedBitmaps.put(DEFAULT_CHECKBOX, defaultBitmap);
            if(D.SHOW_INFO_LOGS)
                L.Log(TAG, "Default Image added to the list: " + context.getResources().getResourceEntryName(DEFAULT_CHECKBOX), LogType.INFO);
        }
        else
            L.Log(TAG, "The Default Resource-ID for the Drawable-Image is incorrect and we can't retrieve the Bitmap with the BitmapFactory!", LogType.ERROR);
    }

    // The method to save the Resource-ID key and it's current Bitmap value in the HashMap
    // And also to retrieve the Bitmap with the given Resource-ID
    public void imageForImageView(int id, ImageView iv){
        // Get the Bitmap from the HashMap (or null if it doesn't exist yet)
        Bitmap bitmap = cachedBitmaps.get(id);

        // Does this Bitmap already exist in the HashMap?
        if(bitmap != null)
            // Then assign it to the ImageView
            iv.setImageBitmap(bitmap);
        // If it doesn't exists yet:
        else{
            // Create it locally using the BitmapFactory
            bitmap = BitmapFactory.decodeResource(context.getResources(), id);
            // Does the Drawable with this given Resource-ID exist?
            if(bitmap != null){
                // Put it in the HashMap
                cachedBitmaps.put(id, bitmap);
                // and assign it to the ImageView
                iv.setImageBitmap(bitmap);
                L.Log(TAG, "New Image added to the list: " + context.getResources().getResourceEntryName(id), LogType.INFO);
            }
            // If it doesn't exist:
            else
                // Use the default Resource-ID instead
                iv.setImageBitmap(cachedBitmaps.get(DEFAULT_CHECKBOX));
        }
    }
}

Added this ImageLoader to my MainActivity:

private static ImageLoader imageLoader;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ... // Do more stuff

    imageLoader = new ImageLoader();
}

public static ImageLoader getImageLoader(){
    return imageLoader;
}

And use this ImageLoader in my getView() method and other places where I change these Images:

MainActivity.getImageLoader().imageForImageView(D.CHECKBOX_IMAGES[currentItem.getCheckState()], imageView);

And now I don't get the Logcat Memory Leaks / App Crashes anymore.

PS: I use a Default class where I saved the Image-Resouces IDs:

// Class for all the Default values
public class D
{
    ... // Other defaults

    public static final Integer[] CHECKBOX_IMAGES = {
        R.drawable.checkbox_unchecked,
        R.drawable.checkbox_checked,
        R.drawable.checkbox_error,
        R.drawable.checkbox_partly
    };
}

PSS: And a Logger class:

import android.util.Log;

// Logger class to show Logcat-messages
public class L
{
    // Show normal Logs based on the given LogType
    public static void Log(String TAG, String message, LogType type){
        if(D.SHOW_LOGS){
            switch(type){
                case INFO:
                    Log.i(TAG, message);
                    break;
                case WARNING:
                    Log.w(TAG, message);
                    break;
                case ERROR:
                    Log.e(TAG, message);
                    break;
            }
        }
    }

    // Show Exception Error Logs
    public static void LogException(String TAG, String message, Throwable ex){
        if(D.SHOW_LOGS)
            Log.e(TAG, message, ex);
    }
}

With LogType as an Enum with the three LogTypes available.

if you have images in the viewholder, it is better to add them to cache so it will be more performant and only get the images once. This is the correct way you should create a new viewholder for every row

if you have images in the list and you are trying to display this then i would suggest you use some image loading library, there is a nice image loader Universal image loader you can download sample app from the following link and check out, you wont get the memory issue using it... Click here

In this case you have to use lazylist loading

https://github.com/thest1/LazyList

It might help you by avoid image displacement and fast scrolling

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM