RecyclerView 中图像的奇怪行为
Strange behaviour of images in RecyclerView
我正在使用 android.support.v7.widget.RecyclerView 来显示包含文本的简单项目,在某些情况下还包含图像。基本上我按照这里的教程 https://developer.android.com/training/material/lists-cards.html 只是将 mDataset 的类型从 String[] 更改为 JSONArray 和 ViewHolder 的 xml。我的 RecyclerView 位于一个简单的片段中:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<!-- A RecyclerView with some commonly used attributes -->
<android.support.v7.widget.RecyclerView
android:id="@+id/my_hopps_recycler_view"
android:scrollbars="vertical"
android:layout_centerInParent="true"
android:layout_width="200dp"
android:layout_height="wrap_content"/>
</RelativeLayout>
在此片段中,我从网络服务器加载所有信息。收到信息后,我初始化 RecyclerView 中的项目。我使用在片段的 onCreate 方法中设置的 LinearLayoutManager。添加适配器后,我在 RecyclerView 上调用 setHasFixedSize(true) 方法。我的适配器 class 看起来像这样:
import android.graphics.BitmapFactory;
import android.support.v7.widget.RecyclerView;
import android.util.Base64;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.apache.commons.lang3.StringEscapeUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import saruul.julian.MyFragment;
import saruul.julian.R;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
/**
* Fragment holding the RecyclerView.
*/
private MyFragment myFragment;
/**
* The data to display.
*/
private JSONArray mDataset;
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView text;
ImageView image;
public ViewHolder(View v) {
super(v);
text = (TextView) v.findViewById(R.id.my_view_text);
image = (ImageView) v.findViewById(R.id.my_view_image);
}
}
public MyAdapter(MyFragment callingFragment, JSONArray myDataset) {
myFragment = callingFragment;
mDataset = myDataset;
}
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_view, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
try {
JSONObject object = mDataset.getJSONObject(position);
if ( object.has("image") ){
if ( !hopp.getString("image").equals("") ){
try {
byte[] decodedString = Base64.decode(StringEscapeUtils.unescapeJson(object.getString("image")), Base64.DEFAULT);
holder.image.setImageBitmap(BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length));
holder.image.setVisibility(View.VISIBLE);
} catch ( Exception e ){
e.printStackTrace();
}
}
}
if ( object.has("text") ){
holder.text.setText(object.getString("text"));
}
} catch ( Exception e ){
e.printStackTrace();
}
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return mDataset.length();
}
}
还有我的 xml 我的 RecyclerView 内部视图:
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardCornerRadius="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:id="@+id/my_hopp_people_reached"/>
<ImageView
android:id="@+id/my_hopp_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:adjustViewBounds="true"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:id="@+id/my_hopp_text"/>
<ImageButton
android:id="@+id/my_hopp_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:src="@drawable/ic_action_discard"
/>
</LinearLayout>
</android.support.v7.widget.CardView>
现在我的问题是:一切正常。文本和图片在我的 RecyclerView 中正确显示。但是,如果我通过向下滚动并再次向上滚动到达列表末尾,图片将显示在不应包含图片的项目中。
目前我有九件物品。最后两个包含一张照片。所以我向下滚动,只看到前七个项目中的文本。到达列表末尾时,我看到了两张预期的图片。但如果我再次向上滚动,这些图片会出现在其他项目中。
上下滚动会始终按相同顺序更改图片:
- 第一次向上滚动(向下滚动一次后)在第二项中创建图片。
- 向下滚动会在第 7 项中创建图片。
- 向上滚动会在第一项中创建图片。
- 向下滚动不会创建任何内容。
- 向上滚动会在第三项中创建一张图片,而第二项中的图片会被另一张图片覆盖。
- 向下滚动会在第 6 项中创建图片并让第 7 项中的图片消失。
- 等等...
我已经发现每次屏幕上再次出现项目时都会调用 onBindViewHolder(ViewHolder holder, int position) 方法。我检查了我的 mDataset 中的位置和给定信息。这里一切正常:位置正确,给定的信息也正确。所有项目中的文本都不会改变并且始终正确显示。
有没有人遇到过这种问题并且知道一些解决方案?
我认为您需要在 onBindViewHolder(ViewHolder holder, int position)
方法中的 if ( object.has("image") )
语句中添加一个 else
子句,将图像设置为空:
holder.image.setImageDrawable(null);
我认为 the documentation for onBindViewHolder 最好描述为什么这是必要的:
Called by RecyclerView to display the data at the specified position. This method should update the contents of the itemView to reflect the item at the given position.
您应该始终假设传入的 ViewHolder 需要完全更新:)
不确定人们是否仍然对此有疑问,但 if 语句对我不起作用。对我有用的是:
在适配器中使用此方法的覆盖
@Override
public int getItemViewType(int position) {
return position;
}
然后在 onBindViewHolder 中调用基于此的 if 语句为图像本身,如下所示:
int pos = getItemViewType(position);
if(theList.get(pos).getPicture() == null) {
holder.picture.setVisibility(View.GONE);
} else {
Picasso.with(context).load(R.drawable.robot).into(holder.picture);
}
我正在使用 Picasso,但它应该适用于任何其他情况。希望这对某人有所帮助!
基于@Lion789 的回答。
我正在使用 Google 的 Volley 图像和 json 加载。
这使我们能够检索稍后将要回收的视图上的位置。
@Override
public int getItemViewType(int position) {
return position;
}
发起请求时,将位置设置为TAG
@Override
public void onBindViewHolder(final ItemHolder holder, int position) {
ImageRequest thumbnailRequest = new ImageRequest(url,
new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
holder.itemThumbnail.setImageBitmap(response);
}
},[...]);
thumbnailRequest.setTag(String.valueOf(position)); //setting the TAG
mQueue.addToRequestQueue(thumbnailRequest);
}
当视图被回收时,我们将取消请求,并删除当前图像
@Override
public void onViewRecycled(ItemHolder holder) {
super.onViewRecycled(holder);
mQueue.cancelAll(String.valueOf(holder.getItemViewType()));
holder.itemThumbnail.setImageDrawable(null);
}
不会交付取消请求的齐发保证。
我正在使用 android.support.v7.widget.RecyclerView 来显示包含文本的简单项目,在某些情况下还包含图像。基本上我按照这里的教程 https://developer.android.com/training/material/lists-cards.html 只是将 mDataset 的类型从 String[] 更改为 JSONArray 和 ViewHolder 的 xml。我的 RecyclerView 位于一个简单的片段中:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<!-- A RecyclerView with some commonly used attributes -->
<android.support.v7.widget.RecyclerView
android:id="@+id/my_hopps_recycler_view"
android:scrollbars="vertical"
android:layout_centerInParent="true"
android:layout_width="200dp"
android:layout_height="wrap_content"/>
</RelativeLayout>
在此片段中,我从网络服务器加载所有信息。收到信息后,我初始化 RecyclerView 中的项目。我使用在片段的 onCreate 方法中设置的 LinearLayoutManager。添加适配器后,我在 RecyclerView 上调用 setHasFixedSize(true) 方法。我的适配器 class 看起来像这样:
import android.graphics.BitmapFactory;
import android.support.v7.widget.RecyclerView;
import android.util.Base64;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.apache.commons.lang3.StringEscapeUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import saruul.julian.MyFragment;
import saruul.julian.R;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
/**
* Fragment holding the RecyclerView.
*/
private MyFragment myFragment;
/**
* The data to display.
*/
private JSONArray mDataset;
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView text;
ImageView image;
public ViewHolder(View v) {
super(v);
text = (TextView) v.findViewById(R.id.my_view_text);
image = (ImageView) v.findViewById(R.id.my_view_image);
}
}
public MyAdapter(MyFragment callingFragment, JSONArray myDataset) {
myFragment = callingFragment;
mDataset = myDataset;
}
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_view, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
try {
JSONObject object = mDataset.getJSONObject(position);
if ( object.has("image") ){
if ( !hopp.getString("image").equals("") ){
try {
byte[] decodedString = Base64.decode(StringEscapeUtils.unescapeJson(object.getString("image")), Base64.DEFAULT);
holder.image.setImageBitmap(BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length));
holder.image.setVisibility(View.VISIBLE);
} catch ( Exception e ){
e.printStackTrace();
}
}
}
if ( object.has("text") ){
holder.text.setText(object.getString("text"));
}
} catch ( Exception e ){
e.printStackTrace();
}
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return mDataset.length();
}
}
还有我的 xml 我的 RecyclerView 内部视图:
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardCornerRadius="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:id="@+id/my_hopp_people_reached"/>
<ImageView
android:id="@+id/my_hopp_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:adjustViewBounds="true"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:id="@+id/my_hopp_text"/>
<ImageButton
android:id="@+id/my_hopp_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:src="@drawable/ic_action_discard"
/>
</LinearLayout>
</android.support.v7.widget.CardView>
现在我的问题是:一切正常。文本和图片在我的 RecyclerView 中正确显示。但是,如果我通过向下滚动并再次向上滚动到达列表末尾,图片将显示在不应包含图片的项目中。 目前我有九件物品。最后两个包含一张照片。所以我向下滚动,只看到前七个项目中的文本。到达列表末尾时,我看到了两张预期的图片。但如果我再次向上滚动,这些图片会出现在其他项目中。
上下滚动会始终按相同顺序更改图片:
- 第一次向上滚动(向下滚动一次后)在第二项中创建图片。
- 向下滚动会在第 7 项中创建图片。
- 向上滚动会在第一项中创建图片。
- 向下滚动不会创建任何内容。
- 向上滚动会在第三项中创建一张图片,而第二项中的图片会被另一张图片覆盖。
- 向下滚动会在第 6 项中创建图片并让第 7 项中的图片消失。
- 等等...
我已经发现每次屏幕上再次出现项目时都会调用 onBindViewHolder(ViewHolder holder, int position) 方法。我检查了我的 mDataset 中的位置和给定信息。这里一切正常:位置正确,给定的信息也正确。所有项目中的文本都不会改变并且始终正确显示。 有没有人遇到过这种问题并且知道一些解决方案?
我认为您需要在 onBindViewHolder(ViewHolder holder, int position)
方法中的 if ( object.has("image") )
语句中添加一个 else
子句,将图像设置为空:
holder.image.setImageDrawable(null);
我认为 the documentation for onBindViewHolder 最好描述为什么这是必要的:
Called by RecyclerView to display the data at the specified position. This method should update the contents of the itemView to reflect the item at the given position.
您应该始终假设传入的 ViewHolder 需要完全更新:)
不确定人们是否仍然对此有疑问,但 if 语句对我不起作用。对我有用的是:
在适配器中使用此方法的覆盖
@Override
public int getItemViewType(int position) {
return position;
}
然后在 onBindViewHolder 中调用基于此的 if 语句为图像本身,如下所示:
int pos = getItemViewType(position);
if(theList.get(pos).getPicture() == null) {
holder.picture.setVisibility(View.GONE);
} else {
Picasso.with(context).load(R.drawable.robot).into(holder.picture);
}
我正在使用 Picasso,但它应该适用于任何其他情况。希望这对某人有所帮助!
基于@Lion789 的回答。 我正在使用 Google 的 Volley 图像和 json 加载。
这使我们能够检索稍后将要回收的视图上的位置。
@Override
public int getItemViewType(int position) {
return position;
}
发起请求时,将位置设置为TAG
@Override
public void onBindViewHolder(final ItemHolder holder, int position) {
ImageRequest thumbnailRequest = new ImageRequest(url,
new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
holder.itemThumbnail.setImageBitmap(response);
}
},[...]);
thumbnailRequest.setTag(String.valueOf(position)); //setting the TAG
mQueue.addToRequestQueue(thumbnailRequest);
}
当视图被回收时,我们将取消请求,并删除当前图像
@Override
public void onViewRecycled(ItemHolder holder) {
super.onViewRecycled(holder);
mQueue.cancelAll(String.valueOf(holder.getItemViewType()));
holder.itemThumbnail.setImageDrawable(null);
}
不会交付取消请求的齐发保证。