I am using android.support.v7.widget.RecyclerView to show simple items containing text and in some cases also images. Basically I followed the tutorial from here https://developer.android.com/training/material/lists-cards.html and just changed the type of mDataset from String[] to JSONArray and the xml of the ViewHolder. My RecyclerView is located in a simple Fragment:
我使用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>
In this fragment I am loading all information from a webserver. After receiving the information I initialize the items in the RecyclerView. I use a LinearLayoutManager which is set in the onCreate method of my Fragment. After adding the Adapter, I call the setHasFixedSize(true) method on my RecyclerView. My Adapter class looks like this:
在这个片段中,我将从Web服务器加载所有信息。收到信息后,我初始化RecyclerView中的项目。我使用了一个在我的片段的onCreate方法中设置的LinearLayoutManager。添加适配器后,我在RecyclerView上调用setHasFixedSize(true)方法。我的Adapter类看起来像这样:
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();
}
}
And my xml for a view inside my RecyclerView:
和我的RecyclerView中的视图的xml:
<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>
So now here is my problem: Everything works fine. Text and picture are shown correctly in my RecyclerView. However if I reach the end of the list by scrolling down and scroll up again, pictures are shown in items which should not contain pictures. At the moment I have got nine items. The last two of them contain a picture. So I scroll down and just see text in the first seven items. Reaching the end of the list shows me two pictures as expected. But if I scroll up again those pictures appear in other items.
所以现在这是我的问题:一切正常。我的RecyclerView中正确显示了文本和图片。但是,如果我通过向下滚动并再次向上滚动到达列表的末尾,则图片将显示在不应包含图片的项目中。目前我有九件物品。最后两个包含一张图片。所以我向下滚动,只看到前七项中的文字。到达列表的末尾显示了我预期的两张图片。但如果我再次向上滚动,那些图片会显示在其他项目中。
Scrolling up and down changes the pictures always in the same order:
向上和向下滚动会以相同的顺序更改图片:
- The first scrolling up (after being down once) creates a picture in the second item.
- 第一次向上滚动(在向下一次之后)在第二项中创建一张图片。
- Scrolling down creates a picture in the 7th item.
- 向下滚动会在第7项中创建一张图片。
- Scrolling up creates a picture in the first item.
- 向上滚动会在第一个项目中创建一张图片。
- Scrolling down creates nothing.
- 向下滚动不会产生任何结果。
- Scrolling up creates a picture in the third item and the picture in the second item was overwritten with the other picture.
- 向上滚动会在第三个项目中创建一个图片,而第二个项目中的图片会被另一个图片覆盖。
- Scrolling down creates a picture in the 6th item and lets the picture in the 7th item disappear.
- 向下滚动会在第6个项目中创建一张图片,并让第7个项目中的图片消失。
- And so on ...
- 等等 ...
I already discovered that the onBindViewHolder(ViewHolder holder, int position) method is called everytime an item appears again on the screen. I checked for the position and the given information in my mDataset. Everything works fine here: The position is right and the given information too. The text in all items does not change and is always shown correctly. Does anyone have any kind of this problem and knows some solution?
我已经发现,每当一个项目再次出现在屏幕上时,就会调用onBindViewHolder(ViewHolder holder,int position)方法。我在mDataset中检查了位置和给定的信息。这里的一切都很好:位置是正确的,也是给定的信息。所有项目中的文本都不会更改,并始终正确显示。有没有人有任何这种问题,并知道一些解决方案?
3 个解决方案
#1
44
I think you need to add an else
clause to your if ( object.has("image") )
statement in the onBindViewHolder(ViewHolder holder, int position)
method, setting the image to null:
我认为你需要在onBindViewHolder(ViewHolder holder,int position)方法的if(object.has(“image”))语句中添加一个else子句,将image设置为null:
holder.image.setImageDrawable(null);
I think the documentation for onBindViewHolder describe it best why this is necessary:
我认为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.
由RecyclerView调用以在指定位置显示数据。此方法应更新itemView的内容以反映给定位置的项目。
You should always assume that the ViewHolder passed in needed to be completely updated :)
你应该总是假设传入的ViewHolder需要完全更新:)
#2
11
Not sure if people still have issues with this, but the if statement did not work for me. What did work for me is this:
不确定人们是否仍有这方面的问题,但if语句对我不起作用。对我有用的是:
Using an override on this method in the adapter
在适配器中使用此方法的覆盖
@Override
public int getItemViewType(int position) {
return position;
}
followed by calling the if statement based on this for the image itself within the onBindViewHolder, like so:
然后在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);
}
I am using Picasso but it should work with any other else situation. Hope this helps someone!
我正在使用毕加索,但它应该适用于任何其他情况。希望这有助于某人!
#3
2
Based on @Lion789's answer. I'm using Google's Volley for image and json loading.
基于@ Lion789的回答。我正在使用Google的Volley进行图像和json加载。
This allows us to retrieve the position on the view that's going to be recycled later.
这允许我们检索稍后将要回收的视图上的位置。
@Override
public int getItemViewType(int position) {
return position;
}
When making the request, set the position as the TAG
发出请求时,将位置设置为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);
}
And when the view is being recycled we'll cancel the request, and remove the current image
当视图被回收时,我们将取消请求,并删除当前图像
@Override
public void onViewRecycled(ItemHolder holder) {
super.onViewRecycled(holder);
mQueue.cancelAll(String.valueOf(holder.getItemViewType()));
holder.itemThumbnail.setImageDrawable(null);
}
Volley garanties that canceled requests won't be delivered.
取消请求的Volley garanties将不会被发送。
#1
44
I think you need to add an else
clause to your if ( object.has("image") )
statement in the onBindViewHolder(ViewHolder holder, int position)
method, setting the image to null:
我认为你需要在onBindViewHolder(ViewHolder holder,int position)方法的if(object.has(“image”))语句中添加一个else子句,将image设置为null:
holder.image.setImageDrawable(null);
I think the documentation for onBindViewHolder describe it best why this is necessary:
我认为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.
由RecyclerView调用以在指定位置显示数据。此方法应更新itemView的内容以反映给定位置的项目。
You should always assume that the ViewHolder passed in needed to be completely updated :)
你应该总是假设传入的ViewHolder需要完全更新:)
#2
11
Not sure if people still have issues with this, but the if statement did not work for me. What did work for me is this:
不确定人们是否仍有这方面的问题,但if语句对我不起作用。对我有用的是:
Using an override on this method in the adapter
在适配器中使用此方法的覆盖
@Override
public int getItemViewType(int position) {
return position;
}
followed by calling the if statement based on this for the image itself within the onBindViewHolder, like so:
然后在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);
}
I am using Picasso but it should work with any other else situation. Hope this helps someone!
我正在使用毕加索,但它应该适用于任何其他情况。希望这有助于某人!
#3
2
Based on @Lion789's answer. I'm using Google's Volley for image and json loading.
基于@ Lion789的回答。我正在使用Google的Volley进行图像和json加载。
This allows us to retrieve the position on the view that's going to be recycled later.
这允许我们检索稍后将要回收的视图上的位置。
@Override
public int getItemViewType(int position) {
return position;
}
When making the request, set the position as the TAG
发出请求时,将位置设置为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);
}
And when the view is being recycled we'll cancel the request, and remove the current image
当视图被回收时,我们将取消请求,并删除当前图像
@Override
public void onViewRecycled(ItemHolder holder) {
super.onViewRecycled(holder);
mQueue.cancelAll(String.valueOf(holder.getItemViewType()));
holder.itemThumbnail.setImageDrawable(null);
}
Volley garanties that canceled requests won't be delivered.
取消请求的Volley garanties将不会被发送。