滚动未完成前,RecyclerView 滑动不流畅

10

我在滚动时遇到问题,我的意思是滚动很快,但看起来卡顿,滚动完成前就停止了。以下是我定义的RecyclerView:

RecyclerView recyclerView=fragment.getRecyclerView();
        LinearLayoutManager layoutManager = new LinearLayoutManager(fragment.getActivity(), LinearLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.addItemDecoration(new DividerItemDecoration(fragment.getActivity(), LinearLayoutManager.VERTICAL));
 ArrayList<InstaPostModel> rowListItems=getInstaPostListSample();
        InstaPostAdapter rcAdapter = new InstaPostAdapter(rowListItems);
        recyclerView.setAdapter(rcAdapter);

这里是onBindViewHolder

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    final InstaPostModel p = items.get(position);
    context = holder.itemView.getContext();
    Glide.with(context).load(R.mipmap.mee_small).into(holder.userPhoto);
    Glide.with(context).load(R.drawable.post_image).into(holder.photo_content);
    Glide.with(context).load(R.mipmap.love_gray_icon).into(holder.bt_like);
    Glide.with(context).load(R.mipmap.comment_icon).into(holder.bt_comment);
    Glide.with(context).load(R.mipmap.share_icon).into(holder.bt_share);

    holder.userNameTextView.setText(p.getPosterUserName());
    holder.postContent.setText(p.getText());
    holder.post_date.setReferenceTime(p.getDate().getTime());
}

这里是 RecyclerView.xml

<?xml version="1.0" encoding="utf-8"?>

    <android.support.v7.widget.RecyclerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/qatar_now_posts_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none"
        tools:context=".uis.fragments.home.QatarNowFragment"
        />

编辑: 我还在同一个片段中有底部导航栏,当滚动时它会显示出来。

编辑 2:这里是视频链接展示了卡顿的情况。我尝试了所有的解决办法,但没有一个有效果。我已经添加了所有的代码,请帮忙看看?


我刚刚收到了RecyclerView元素。 - Mohammad Sommakia
不,它大约有50KB。 - Mohammad Sommakia
为什么在XML中有“src”标签时,您需要使用Glide加载资源? - Khemraj Sharma
你修复了什么? - Khemraj Sharma
抱歉,我的意思是我将资源放在 XML 中而不是 Glide 中,没有做出更改。 - Mohammad Sommakia
显示剩余18条评论
7个回答

4

我认为你的代码没有问题,但是以下几点需要考虑:

(1) 从API获取到的图像可能太大了。请检查API的大小,并将其最多减小200KB(100-150 KB也足够了)。

(2) 不应禁用缓存,因为这会通过缓存存储图像,下次加载时会通过缓存而不是URL来提高用户体验。

(3) 对于现在Android中的一个简单的涟漪效果,您不需要使用material-ripple。只需向ImageView添加此属性即可。

 android:background="?attr/selectableItemBackground"

(4) 检查一次加载的项目数量。一次不应该加载太多项目。请参见Architecture component paging library

(5) 还要检查您的资源大小,例如love_gray_icon。我认为它的最大尺寸应该是70*70。如果您的图片比这还要大,那么您应该调整其大小。批量调整多个图像的大小。在使用之前对图像进行压缩,这将使图像大小减小80%。调整大小后,请压缩图像

(6) Glide是一个维护良好的库,因此onViewRecycled是多余的。

最后,在您的代码中进行一些微小的修改。

  private void loadImage(ImageView iv, String url) {
        Glide.with(iv.getContext()).load(url).thumbnail(0.5f).into(iv);
    }

    private void loadImage(ImageView iv, int resId) {
        Glide.with(iv.getContext()).load(resId).thumbnail(0.5f).into(iv);
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        final InstaPostModel p = items.get(position);

        loadImage(holder.userPhoto, Urls.BASE_URI + p.getUserPhotoUrl());
        loadImage(holder.photo_content, Urls.BASE_URI + p.getPostPhotoUrl());
        loadImage(holder.bt_like, R.mipmap.love_gray_icon);
        loadImage(holder.bt_comment, R.mipmap.comment_icon);
        loadImage(holder.bt_share, R.mipmap.share_icon);

        holder.userNameTextView.setText(p.getPosterUserName());
        holder.postContent.setText(p.getText());
        holder.post_date.setReferenceTime(p.getDate().getTime());
    }

4
可能是因为图片太大了。你可以使用 Glide 来重新调整大小,比如:
Glide.with(context)
    .load(imgUrl)
    .diskCacheStrategy(DiskCacheStrategy.ALL)
    .error(placeholder)
    .placeholder(placeholder)
    .dontTransform()
    .override(200,200)
    .into(imageView);

2
我有几个改进建议。
  • 在列表项中加载太多图片。尽可能减少图像的加载。例如,您可以考虑设置默认图像为喜欢和分享按钮,这些按钮通常不会在应用程序中更改。在列表项布局中将它们设置为android:src
  • 检查Instagram图片是否过大。您可以获取API以加载平均大小的图像。
  • 使用选择器而不是创建嵌套视图来在列表项上单击时产生涟漪效果。尽可能使列表项布局简单,并尽量避免嵌套视图层次结构。
  • 从适配器中删除无用的onViewRecycled函数。
  • 使用传递给构造函数的Context初始化适配器。您不必每次在onBindViewHodler内获取它。
  • 您不必在onBindViewHolder函数中每次都初始化RequestOption。只需初始化一次并在每个Glide图像加载器中使用即可。
  • 删除按钮、评论和分享图像加载器,并按照前面所述从布局中提供图像作为源。
  • 使用DiskCacheStrategy.ALL缓存您的图像,以便在打开应用程序或加载RecyclerView时不必每次加载。
因此,最终的适配器应该是这样的。
public class InstaPostAdapter
        extends RecyclerView.Adapter<InstaPostAdapter.ViewHolder> {

    private List<InstaPostModel> items = new ArrayList<>();
    Context context;

    // Initialize the context once, use it everywhere
    public InstaPostAdapter(List<InstaPostModel> items, Context context) {
        this.items = items;
        this.context = context;
    }

    @Override
    public InstaPostAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // create a new view
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.insta_post_list_item, parent, false);
        // set the view's size, margins, paddings and layout parameters
        return new ViewHolder(v);
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        final InstaPostModel p = items.get(position);
        RequestOptions requestOptions = new RequestOptions()
                .diskCacheStrategy(DiskCacheStrategy.ALL);

        Glide.with(context)
                .load(Urls.BASE_URI + items.get(position).getUserPhotoUrl()).thumbnail(0.5f)
                .apply(requestOptions)
                .into(holder.userPhoto);
        Glide.with(context)
                .load(Urls.BASE_URI + items.get(position).getPostPhotoUrl()).thumbnail(0.5f)
                .apply(requestOptions)
                .into(holder.photo_content);

        // Removed the like, comment, share images which should be set from layout.

        holder.userNameTextView.setText(p.getPosterUserName());
        holder.postContent.setText(p.getText());
        holder.post_date.setReferenceTime(p.getDate().getTime());
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return items.size();
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    public void add(InstaPostModel post, int i) {
        items.add(post);
        notifyItemInserted(i);
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        // each data item is just a string in this case
        public ImageView userPhoto;
        public TextView userNameTextView;
        public ImageView bt_more;
        public ImageView photo_content;
        public ImageView bt_like;
        public ImageView bt_comment;
        public ImageView bt_share;
        public TextView postContent;
        public RelativeTimeTextView post_date;

        public ViewHolder(View v) {
            super(v);
            userPhoto = v.findViewById(R.id.insta_user_photo);
            userNameTextView = v.findViewById(R.id.insta_user_name);
            bt_more = v.findViewById(R.id.insta_bt_more);
            photo_content = v.findViewById(R.id.insta_photo_content);
            bt_like = v.findViewById(R.id.insta_bt_like);
            bt_comment = v.findViewById(R.id.insta_bt_comment);
            bt_share = v.findViewById(R.id.insta_bt_share);
            postContent = v.findViewById(R.id.insta_post_content);
            post_date = v.findViewById(R.id.insta_post_date);
            setClickListener();
        }

        private void setClickListener() {
            bt_more.setOnClickListener(view -> {
                PopupMenu popupMenu = new PopupMenu(context, view);
                popupMenu.setOnMenuItemClickListener(item -> {
                    Snackbar.make(view, item.getTitle() + " Clicked", Snackbar.LENGTH_SHORT).show();
                    return true;
                });
                popupMenu.inflate(R.menu.menu_feed_popup);
                popupMenu.show();
            });
            bt_like.setOnClickListener(view -> Snackbar.make(view, "Like Clicked", Snackbar.LENGTH_SHORT).show());
            bt_comment.setOnClickListener(view -> Snackbar.make(view, "Comment Clicked", Snackbar.LENGTH_SHORT).show());
            bt_share.setOnClickListener(view -> Snackbar.make(view, "Share Clicked", Snackbar.LENGTH_SHORT).show());
        }
    }
}

我认为我已经为Flickr图像搜索完成了类似的项目。您可以在Github上找到该项目
希望这能有所帮助!

我认为问题不在于图像,而是由于Java和Android图形中的内存泄漏。在Android Profilers中,Java占用25MB,图形占用25MB。 - Mohammad Sommakia

1
我认为滚动问题可能是因为您在绑定时重新加载图像。您可以在加载照片时使用缓存,当然还有分页。

如果你有大量需要加载的图片,你可以每次加载10张,当列表末尾到达时,再加载下一批图片。 - Anu Bhalla
在我的示例中,只有大约5个帖子,但我看到内存使用量大约为100MB,我不知道该如何减少它。 - Mohammad Sommakia
尝试使用缓存。如果问题仍然存在,请在分析器中检查内存。 - Anu Bhalla

1

有人提到你应该使用分页适配器。我也在使用它,但是滚动不流畅。在我的情况下,我把RecyclerView放在可滚动的视图中,这导致尽管使用分页适配器,整个RecyclerView仍然被加载。简单来说:不要把RecyclerView放在可滚动的视图中。如果你需要一些标题,请将其作为不同类型的项放在RecyclerView中。


1
您可以将您的代码进行优化,如下所示:
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    final InstaPostModel p = items.get(position);
    context = holder.itemView.getContext();
   setImage(holder.userPhoto, p.getUserPhotoUrl());
   setImage(holder.photo_content, p.getPostPhotoUrl());
   setImage(holder.bt_like, R.mipmap.love_gray_icon);

        holder.userNameTextView.setText(p.getPosterUserName());
        holder.postContent.setText(p.getText());
        holder.post_date.setReferenceTime(p.getDate().getTime());

}

private void setImage(ImageView iv, String url)
{
          Glide.with(context)
            .load(Urls.BASE_URI +url).thumbnail(0.5f)
            .apply(new RequestOptions()
                    .diskCacheStrategy(DiskCacheStrategy.NONE)
                    .skipMemoryCache(true).dontAnimate()
            )
            .into(iv);
}
private void setImage(ImageView iv, int resource)
{
          Glide.with(context)
            .load(resource).thumbnail(0.5f)
            .apply(new RequestOptions()
                    .diskCacheStrategy(DiskCacheStrategy.NONE)
                    .skipMemoryCache(true).dontAnimate()
            )
            .into(iv);
}

这肯定会在渲染时减少负载。请确保仅在适配器遍历时使用变量,因为每次调用list.get(position)的成本更高。

你刚刚在代码中创建了一个方法,这并不会提高性能,因为它与原来的代码相同。如果你在构造函数中初始化Glide,那么就会有所不同。 - Khemraj Sharma
是的,那将重复使用 Glide 的同一实例。 - Seeya
你的代码不会重复使用 Glide 实例,因为你把它放在了方法中,每次都会重新初始化。 - Khemraj Sharma
那么,@Khemraj,如何在构造函数中初始化Glide并使用它呢? - Mohammad Sommakia
@MohammadSommakia 这不会有明显的差异。Glide是一个维护良好的库。但你仍然需要考虑很多事情,可以看看我的回答。 - Khemraj Sharma

0
如果RecyclerView中的项大小相同,则可以尝试: recyclerview.setHasFixedSize(true);

尝试使用drawable设置图像,无需使用Glide。 - TieuNhi
setHasFixedSize是关于RecyclerView的,而不是它所包含的项目。如果您的RecyclerView将保持相同的高度,则应将其设置为true。 - Magnetic Llama

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接