问题
I have the following JSON which I'm parsing in GSON and Retrofit. I want to display the values of the same id
in one TextView. What's happening right now is that all the values are added to the array and they are being displayed in separate TextViews. I want to show all values which have the same id
to be displayed in one TextView. For eg. id: 240
ab
values should be in one TextView. Currently, all ab
values are in separate TextView.
This is how the data is currently displaying:
This is how I want the data to be:
JSON::
{
"abc": {
"1": {
"1": {
"ab": "some content",
"id": "240",
"key": "value"
},
"2": {
"ab": "some content",
"id": "240",
"key": "value"
},
"3": {
"ab": "some content",
"id": "240",
"key": "value"
}
},
"2": {
"1": {
"ab": "more content",
"id": "241",
"key": "value"
},
"2": {
"ab": "more content 1",
"id": "241",
"key": "value"
},
"3": {
"ab": "more content 2",
"id": "241",
"key": "value"
}
}
}
}
POJOContent::
public class POJOContent {
@SerializedName("ab")
public String content;
@SerializedName("id")
public String id;
@SerializedName("key")
public String key;
@Override
public String toString() {
return content;
}
//getters and setters
}
MyContentWrapper::
public class MyContentWrapper {
public Map<Integer, MyMap> abc;
}
MyMap::
public class MyMap extends HashMap<Integer, POJOContent> {
@Override
public POJOContent put(Integer key, POJOContent value) {
if(null==value.getContent() || value.getContent().isBlank()) {
return null;
}
// Added only if content = "ab" is not blank.
return super.put(key, value);
}
}
Callback:
Callback<MyContentWrapper> myCallback = new Callback<MyContentWrapper>() {
@Override
public void onResponse(Call<MyContentWrapper> call, Response<MyContentWrapper> response) {
if (response.isSuccessful()) {
Log.d("Callback", " Message: " + response.raw());
Log.d("Callback", " Message: " + response.body().abc.values());
MyContentWrapper contentWrapper = response.body();
List<POJOContent> pojo = new ArrayList<>();
for (Map.Entry<Integer, MyMap> entry : contentWrapper.abc.entrySet()) {
Integer key = entry.getKey();
MyMap map = entry.getValue();
if (!map.isEmpty()){
Log.d("Callback", " Key: " + key);
Log.d("Callback", " Value: " + map.values());
pojo.addAll(map.values());
}
}
MyContentViewAdapter adapter = new MyContentViewAdapter(pojo);
recyclerView.setAdapter(adapter);
} else {
Log.d("Callback", "Code: " + response.code() + " Message: " + response.message());
}
}
@Override
public void onFailure(Call<MyContentWrapper> call, Throwable t) {
t.printStackTrace();
}
};
RecyclerAdapter::
public class MyContentViewAdapter extends RecyclerView.Adapter<MyContentViewAdapter.ViewHolder> {
private List<POJOContent> data;
private MyClickListener clickListener;
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView text;
private LinearLayout itemLayout;
public ViewHolder(View v) {
super(v);
text = (TextView) v.findViewById(R.id.text_content);
}
}
public MyContentViewAdapter(List<POJOContent> data) {
this.data = data;
Log.d("Recyclerview Data", data.toString());
}
@Override
public MyContentViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v;
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_content_card, parent, false);
return new MyContentViewAdapter.ViewHolder(v);
}
@Override
public void onBindViewHolder(MyContentViewAdapter.ViewHolder holder, int position) {
POJOContent pojo = data.get(position);
Log.d("Recyclerview", pojo.getContent());
holder.text.setText(pojo.getContent());
holder.itemView.setTag(pojo.getContent());
}
public void setOnItemClickListener(MyClickListener clickListener) {
this.clickListener = clickListener;
}
@Override
public int getItemCount() {
return data.size();
}
}
回答1:
EDIT:
I added nested RecyclerView
inside ViewHolder
, so the content and value fields will be displayed dynamically. I'm Adding full code of 2 Adapter
and
2 ViewHolder
classes, 2 xml layouts and screenshot
I'm pretty sure it will run really smoothly with very large list too.
Everything under ID(240,241) is another recyclerView.
The idea is that list's size, for adapter to populate itself, should be as many as the number of distinct ids, so that only that many Viewholders
are inflated.
List<List<POJOContent>> listOfPojoLists = new ArrayList<>();
for (Map.Entry<Integer, MyMap> entry : contentWrapper.abc.entrySet()) {
Integer key = entry.getKey();
MyMap map = entry.getValue();
if (!map.isEmpty()){
Log.d("Callback", " Key: " + key);
Log.d("Callback", " Value: " + map.values());
listOfPojoLists.add(new ArrayList<>(map.values()));
}
}
MyContentViewAdapter adapter = new MyContentViewAdapter(listOfPojoLists);
recyclerView.setAdapter(adapter);
MyContentViewAdapter.java
public class MyContentViewAdapter extends RecyclerView.Adapter<MyContentViewAdapter.ViewHolder> {
private List<List<POJOContent>> data;
private MyClickListener clickListener;
MyContentViewAdapter(List<List<POJOContent>> data) {
this.data = data;
}
@Override
public MyContentViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.fragment_content_card, parent, false);
return new MyContentViewAdapter.ViewHolder(v);
}
@Override
public void onBindViewHolder(MyContentViewAdapter.ViewHolder holder, int position) {
holder.bind(data.get(position));
}
public void setOnItemClickListener(MyClickListener clickListener) {
this.clickListener = clickListener;
}
@Override
public int getItemCount() {
return data.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
private TextView textId;
private InnerListAdapter innerAdapter;
// inside constructor we are initializing inner recyclerView and inner Adapter.
// there will only be 3 - 5 instances of them ever created(using this
// particular viewHolder layouts), no more.
// might be slightly more if u use layouts with lower height
ViewHolder(View v) {
super(v);
textId = v.findViewById(R.id.tv_Id);
RecyclerView innerRecycler = v.findViewById(R.id.rv_inner_list);
// I added DividerItemDecoration so it would be clear that there are actually different viewHolders
// displayed by recyclerView
innerRecycler.addItemDecoration(new DividerItemDecoration(v.getContext(), DividerItemDecoration.VERTICAL));
innerAdapter = new InnerListAdapter();
innerRecycler.setAdapter(innerAdapter);
}
/* We just submit new list for our inner adapter
so it will handle rebinding values to its viewHolders */
void bind(List<POJOContent> pojoList){
textId.setText(pojoList.get(0).id);
innerAdapter.setNewItems(pojoList);
}
}
}
InnerListAdapter.java
public class InnerListAdapter extends RecyclerView.Adapter<InnerListAdapter.InnerViewHolder> {
private List<POJOContent> items = new ArrayList<>();
@NonNull
@Override
public InnerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new InnerViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_inner_list, parent, false));
}
@Override
public void onBindViewHolder(@NonNull InnerViewHolder holder, int position) {
holder.bind(items.get(position));
}
@Override
public int getItemCount() {
return items.size();
}
void setNewItems(List<POJOContent> newItems){
items.clear();
items.addAll(newItems);
notifyDataSetChanged();
}
class InnerViewHolder extends RecyclerView.ViewHolder{
TextView tv_value;
TextView tv_content;
InnerViewHolder(@NonNull View itemView) {
super(itemView);
tv_value = itemView.findViewById(R.id.tv_value);
tv_content = itemView.findViewById(R.id.tv_content);
}
void bind(POJOContent pojoContent){
tv_value.setText(pojoContent.getKey());
tv_content.setText(pojoContent.getContent());
}
}
}
fragment_content_card.xml layout for main recyclerView viewholder
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
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"
android:layout_margin="8dp"
android:padding="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_Id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:textSize="32sp"
android:textColor="@color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="ID" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_inner_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_Id" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
item_inner_list.xml layout for inner recylerVoews' viewholder
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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/tv_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:textColor="@color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="value" />
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:textColor="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_value"
tools:text="content" />
</androidx.constraintlayout.widget.ConstraintLayout>
回答2:
create another pojo class for RV List Items with content and keys list
public class POJOListItem {
public String id;
public List<String> contents = new ArrayList<>();
public List<String> keys = new ArrayList<>();
}
then validate and map MyContentWrapper
object to List<POJOListItem>
, check the list if it contains the id, if it has the same id then add them to the contents
and keys
list or create a new item and add it. based on the list size dynamically add item in a container layout inside the view holder.
Check out this small demo project https://github.com/toxic-charger/SO-63045236
Tested with 50000+ items no performance issus
回答3:
Based on your question, you have separate issues to tackle:
1 - Define better the data you have. You currently have POJOContent
but you want to display something like a POJOContentGroup
, so before getting to the view part you pre-process your data and map it from MyContentWrapper
to List<POJOContentGroup>
.
data class POJOContentGroup(val id: String, val contentList: List<POJOContent>)
(This is in Kotlin for brevity)
2 - Having the previous defined, now you have a simple task: Display a list of POJOContentGroup
. Create your recycler view based on that, similar to what you've done initially, with the difference that the item layout will be a TextView
for the id
and a RecyclerView
for the contentList
, which is the one you currently have.
回答4:
As per my understanding of problem description, you are trying to construct array like this
[
"some content"+"some content"+"some content", // id = 240
"more content"+"more content 1"+"more content 2" // id = 241
]
and you want to show these 2 values in list of TextView
but the array you constructed will be,
[
"some content",
"some content",
"some content",
"more content",
"more content 1",
"more content 2"
]
and hence each entry is displayed in separate TextView
To debug further, please check value of pojo list after
pojo.addAll(map.values())
// may be if you want to group entries with comma delimiter, use
// pojo.addAll(TextUtils.join(", ", map.values()))
回答5:
Hi its late but i would say create one class ListItem
class ListItem {
private var item: HashMap<Int, POJOContent>? = null
}
and overwrite toString in your POJOContent to return the values you want to display on text view like
@Override
public String toString() {
return value + "\n" + content;
}
Then use List in your adapter Then in bind
//take care of null checks and you can use System.getProperty("line.separator"); in place of line saperator also don't forgate to set maxLine property of your text view to eome higher value
void bind(ListItem item){
Array<POJOContent> contents = item.values().toArray()
tv_value.setText(contents[0].getKey());
tv_content.setText(contents.toString().replace("[","").replace("]","").replace(",","\n"));
}
回答6:
I think nested-expandable-recyclerview should be the right choice to achieve your goal, which will also increase your user experience. By default can open the nested recycler with your child data of the JSON.
来源:https://stackoverflow.com/questions/63045236/show-values-of-same-key-in-one-textview-in-recyclerview