ListView加载本地Drawable滚动卡顿,已做优化求后续解决方案
ListView滚动卡顿优化方案
你已经通过裁剪图片、使用ViewHolder、禁用缓存、改用ConstraintLayout做了不少性能优化,但滚动仍有卡顿,下面这些方案可以进一步尝试:
一、优化图片加载与处理
- 使用图片加载库异步加载:哪怕是本地drawable资源,Glide或Picasso这类库也能自动处理图片解码、内存缓存和异步加载,避免主线程阻塞。示例代码:
Glide.with(getContext()) .load(currentLocation.getImageResourceId()) .into(viewHolder.image); - 为多分辨率设备提供适配资源:在
drawable-mdpi、drawable-hdpi、drawable-xhdpi等目录下放对应尺寸的图片,避免系统运行时动态缩放图片,减少计算开销。 - 手动采样解码图片:如果图片原始尺寸远大于ImageView显示尺寸,可通过采样降低内存占用:
在private Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; }getView中调用该方法获取Bitmap后设置给ImageView即可。
二、优化ListView配置
- 开启平滑滚动条:给ListView添加
android:smoothScrollbar="true",让滚动条绘制更顺滑,减少不必要的重绘。 - 确认硬件加速开启:在对应Activity的AndroidManifest.xml中添加
android:hardwareAccelerated="true"(默认通常开启,但可确认),利用GPU加速绘制提升流畅度。 - 避免
getView中做耗时操作:所有数据处理提前在后台线程完成,比如Location对象的属性提前准备好,不要在滚动时才计算。
三、精简布局层级
- 去除冗余嵌套:你当前列表项外层的LinearLayout可以去掉,直接用ConstraintLayout作为根布局,减少布局层级。修改后的列表项XML:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/name_text_view" android:layout_width="0dp" android:layout_height="22dp" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:textSize="18sp" android:textStyle="bold" android:textColor="@android:color/white" app:layout_constraintBottom_toBottomOf="@+id/image_view" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" tools:text="Oleviste church" android:elevation="2dp" /> <ImageView android:id="@+id/image_view" android:layout_width="match_parent" android:layout_height="160dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:contentDescription="@string/image_content_description" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/description_text_view" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/image_view" tools:text="This modern museum is located inside a former seaplane hangar and boasts a submarine and an ice breaker among its exhibits." /> </android.support.constraint.ConstraintLayout> - 用ViewStub懒加载非必要视图:如果列表项中有不是每次都显示的视图,用ViewStub延迟加载,降低初始化布局的开销。
四、性能监控与内存优化
- 用Android Profiler定位卡顿点:通过Android Studio的Profiler查看滚动时的CPU、内存、GPU使用情况,比如是否有频繁GC,或者某个方法占用过多CPU时间。
- 排查内存泄漏:确保ViewHolder没有持有Activity强引用,检查Context使用是否合理,防止内存泄漏导致的性能下降。
附:你提供的相关代码
LocationAdapter 代码
public class LocationAdapter extends ArrayAdapter<Location> { public LocationAdapter(@NonNull Context context, ArrayList<Location> locationArrayList) { super(context, 0, locationArrayList); } static class ViewHolder { public TextView nameText; public TextView descriptionText; public ImageView image; } @NonNull @Override public View getView(int position, @Nullable View listItemView, @NonNull ViewGroup parent) { ViewHolder viewHolder; // check if the existing view is being reused if (listItemView == null) { // if not, inflate the view using location_list_itemlist_item.xml listItemView = LayoutInflater.from(getContext()).inflate(R.layout.location_list_item, parent, false); // configure view holder viewHolder = new ViewHolder(); viewHolder.nameText = listItemView.findViewById(R.id.name_text_view); viewHolder.image = listItemView.findViewById(R.id.image_view); viewHolder.descriptionText = listItemView.findViewById(R.id.description_text_view); listItemView.setTag(viewHolder); } else { viewHolder = (ViewHolder) listItemView.getTag(); } // getting Location object in this position in ArrayList Location currentLocation = getItem(position); // using ViewHolder to set text/images to views viewHolder.nameText.setText(currentLocation.getName()); viewHolder.descriptionText.setText(currentLocation.getDescription()); viewHolder.image.setImageResource(currentLocation.getImageResourceId()); return listItemView; } }
列表项布局(location_list_item.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/linearLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/name_text_view" android:layout_width="368dp" android:layout_height="22dp" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:textSize="18sp" android:textStyle="bold" android:textColor="@android:color/white" app:layout_constraintBottom_toBottomOf="@+id/image_view" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" tools:text="Oleviste church" android:elevation="2dp" /> <ImageView android:id="@+id/image_view" android:layout_width="match_parent" android:layout_height="160dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:contentDescription="@string/image_content_description" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/description_text_view" android:layout_width="0dp" android:layout_height="51dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/image_view" tools:text="This modern museum is located inside a former seaplane hangar and boasts a submarine and an ice breaker among its exhibits." /> </android.support.constraint.ConstraintLayout> </LinearLayout>
ListView布局
<?xml version="1.0" encoding="utf-8"?> <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent" android:overScrollMode="never" android:scrollingCache="false" android:animationCache="false"/>
内容的提问来源于stack exchange,提问作者Igor
相关产品推荐
相关产品推荐

