You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ArrayAdapter中用AsyncTask加载图片解决ListView滚动卡顿

Fixing ListView Scrolling Lag with AsyncTask for Image Loading

Hey there! Let's get that ListView scrolling smoothly again. The lag you're seeing comes from two main issues in your current adapter code:

  • You're inflating a new layout every time getView() is called (no view recycling)
  • You're loading images synchronously on the main UI thread, which blocks rendering during scrolls

Here's a refactored version of your adapter that uses the ViewHolder pattern for view reuse and AsyncTask to load images in the background:

Modified MainActivity.java (Focus on the Adapter)

package com.emmakade.okt;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    protected List<Course> data;
    private static final int DETAIL_REQUEST_CODE = 1; // Added since your onActivityResult references this

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        data = DataProvider.getData();
        ArrayAdapter<Course> courseArrayAdapter = new CourseArrayAdapter(this, 0, data);
        ListView listView = (ListView) findViewById(android.R.id.list);
        listView.setAdapter(courseArrayAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Course course = data.get(position);
                displayDetail(course);
            }
        });
    }

    // Make sure this method is defined (since you call it in onItemClick)
    private void displayDetail(Course course) {
        // Your existing detail activity logic here
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data); // Don't forget this line
        if (requestCode == DETAIL_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                String msg = data.getStringExtra("resultMessage");
                Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
            }
        }
    }

    class CourseArrayAdapter extends ArrayAdapter<Course>{
        Context context;
        List<Course> objects;

        public CourseArrayAdapter(Context context, int resource, List<Course> objects) {
            super(context, resource, objects);
            this.context = context;
            this.objects = objects;
        }

        // ViewHolder class to cache views for reuse
        static class ViewHolder {
            TextView titleTextView;
            ImageView courseImageView;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            Course course = objects.get(position);

            // Reuse existing view if available
            if (convertView == null) {
                LayoutInflater inflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
                convertView = inflater.inflate(R.layout.course_item, parent, false);

                // Initialize ViewHolder and cache views
                holder = new ViewHolder();
                holder.titleTextView = (TextView) convertView.findViewById(R.id.tvTitle);
                holder.courseImageView = (ImageView) convertView.findViewById(R.id.imageCourse);
                convertView.setTag(holder);
            } else {
                // Get existing ViewHolder from the reused view
                holder = (ViewHolder) convertView.getTag();
            }

            // Set the course title immediately (this is fast)
            holder.titleTextView.setText(course.getTitle());

            // Cancel any ongoing image load task for this view to prevent image misalignment
            ImageLoadTask existingTask = (ImageLoadTask) holder.courseImageView.getTag();
            if (existingTask != null) {
                existingTask.cancel(true);
            }

            // Start a new AsyncTask to load the image in the background
            ImageLoadTask task = new ImageLoadTask(holder.courseImageView, course.getCourseNumber());
            holder.courseImageView.setTag(task);
            task.execute();

            return convertView;
        }

        // AsyncTask to load images in background thread
        private class ImageLoadTask extends AsyncTask<Void, Void, Drawable> {
            private ImageView imageView;
            private String courseNumber;

            public ImageLoadTask(ImageView imageView, String courseNumber) {
                this.imageView = imageView;
                this.courseNumber = courseNumber;
            }

            @Override
            protected Drawable doInBackground(Void... voids) {
                // Load the drawable in background thread
                int resId = context.getResources().getIdentifier(
                        "image_" + courseNumber, "drawable", context.getPackageName()
                );
                return context.getResources().getDrawable(resId);
            }

            @Override
            protected void onPostExecute(Drawable drawable) {
                // Only update the ImageView if the task wasn't cancelled and the view is still valid
                if (!isCancelled() && imageView != null && drawable != null) {
                    imageView.setImageDrawable(drawable);
                }
            }
        }
    }
}

Key Improvements Explained:

  1. ViewHolder Pattern:

    • Caches the TextView and ImageView for each list item, so we avoid repeated findViewById() calls (a major source of UI thread overhead)
    • Reuses existing convertView instead of inflating new layouts for every item, cutting down on memory usage and layout processing time
  2. AsyncTask for Image Loading:

    • Moves drawable loading to a background thread (doInBackground()), so the main UI thread stays free to handle scrolling and rendering
    • Cancels ongoing tasks when a view is reused, preventing old images from appearing in the wrong list items
    • Only updates the ImageView if the task completed successfully and wasn't cancelled, ensuring consistent UI state

Notes:

  • Your layout files (activity_main.xml and course_item.xml) don't need any changes
  • I added a missing DETAIL_REQUEST_CODE constant and displayDetail() method since your original code references them but didn't define them
  • For even better performance, you could add an LRU image cache to avoid reloading the same drawable multiple times as the list scrolls

内容的提问来源于stack exchange,提问作者kayode emmanuel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:52:10