Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
409 views
in Technique[技术] by (71.8m points)

(android) Problems with random changes in dynamic nested recycler view items

??

enter image description here

Hi, I'm making a dynamic Nested RecyclerView.

The Outer RecyclerView (Routine) consists of parts (A, B, C...), add button,

and delete button, and the inner RecyclerView (Detail Routine) consists of text view (set, weight and count).

When i add an outer item, it have one inner RecyclerView item by default.

I thought the implementation was successful, but it wasn't.

The picture is a condition that I have made some items in advance.

This is a picture that creates additional items there.

However, in the picture, when I added the G item, you can see that the F item

and the B item have a change.

And the G item, oddly enough, has two basic items. Originally, it is one.

The second picture is the later item addition.

If i add a new outer recyclerview item, there will be a new change in the whole

item again.

I am not sure why this change is happening. If you add a new item, you want the

existing item to remain.

Please tell me the cause

Here is Code

RoutineAdapter.java ( Outer RecyclerView Adpater )

public class RoutineAdapter extends RecyclerView.Adapter<RoutineAdapter.ViewHolder> {
    Context context;
    ArrayList<RoutineModel> routineItems = new ArrayList<>();
    public RoutineDetailAdapter detailAdapter;
    OnRoutineItemClickListener listener;

    public void setOnRoutineClickListener(OnRoutineItemClickListener listener) {
        this.listener = listener;
    }

    public void addItem(RoutineModel item) {
        routineItems.add(item);
        notifyDataSetChanged();
    }

    public void addDetailItem(RoutineDetailAdapter curDetailAdapter) {
        curDetailAdapter.addItem(new RoutineDetailModel());
    }
    public void deleteDetailItem(RoutineDetailAdapter curDetailAdapter) {
        curDetailAdapter.deleteItem();
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);
        View itemView = inflater.inflate(R.layout.routine_item, parent, false);
        ViewHolder holder = new ViewHolder(itemView);
        detailAdapter = new RoutineDetailAdapter();
        holder.setRoutineDetailRecyClerView();
        holder.rv_detail.setAdapter(detailAdapter);
        detailAdapter.addItem(new RoutineDetailModel());

        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        RoutineModel curRoutineItem = routineItems.get(position);
        holder.setItems(curRoutineItem);
    }

    @Override
    public int getItemCount() {
        return routineItems.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder  {
        RecyclerView rv_detail;
        TextView routine;
        Button addSet;
        Button deleteSet;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            initViews();

            addSet.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    RoutineDetailAdapter curDetailAdapter = (RoutineDetailAdapter) rv_detail.getAdapter();
                    listener.OnAddBtnClick(curDetailAdapter);
                }
            });

            deleteSet.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    RoutineDetailAdapter curDetailAdapter = (RoutineDetailAdapter) rv_detail.getAdapter();
                    listener.OnDeleteBtnClick(curDetailAdapter);
                }
            });
        }

        private void initViews() {
            routine = itemView.findViewById(R.id.routine);
            rv_detail = itemView.findViewById(R.id.detail_routine);
            addSet = itemView.findViewById(R.id.add_set);
            deleteSet = itemView.findViewById(R.id.delete_set);
        }
        private void setItems(RoutineModel routineItem) {
            routine.setText(routineItem.getRoutine());
        }

        public void setRoutineDetailRecyClerView() {
            rv_detail.setLayoutManager(new LinearLayoutManager(context, RecyclerView.VERTICAL, false));
        }
    }

    public interface OnRoutineItemClickListener {
        public void OnAddBtnClick(RoutineDetailAdapter detailAdapter);
        public void OnDeleteBtnClick(RoutineDetailAdapter detailAdapter);
    }
}

RoutineDetailAdapter.java ( Inner RecyclerView Adapter )

public class RoutineDetailAdapter extends  RecyclerView.Adapter<RoutineDetailAdapter.ViewHolder>{
    ArrayList<RoutineDetailModel> items = new ArrayList<>();
    Context context;
    int insertPosition;
    int deletePosition;

    public void addItem(RoutineDetailModel item) {
        items.add(item);
        notifyItemInserted(insertPosition);
    }

    public void deleteItem() {
        try {
            if(items.size() > 1) { // Leave at least one set
                items.remove(deletePosition);
                notifyItemRemoved(deletePosition);
                deletePosition -= 1;
            }
        } catch (Exception e) {
            // empty
        }
    }

    public ArrayList<RoutineDetailModel> getItem() {
        return this.items;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View itemView = inflater.inflate(R.layout.routine_detail_item, parent, false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        RoutineDetailModel item = items.get(position);
        holder.setItem(item, position);
        insertPosition = position + 1;
        deletePosition = position;
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView set;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            set = itemView.findViewById(R.id.set);
        }

        private void setItem(RoutineDetailModel item, int setCount) {
            set.setText(setCount + 1 + "set");
        }
    }
}

WriteRoutineActivity.java

public class WriteRoutineActivity extends AppCompatActivity {
    Button add_routine_btn;
    TextView title;
    RecyclerView routine_rv;

    RoutineAdapter routineAdapter;
    LinearLayoutManager routineLayoutManger;
    ArrayList<String> titleData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_write_routine);

        initViews();
        setPageTitle(getIntent());
        setRoutineRecyclerview();

        routineAdapter = new RoutineAdapter();
        routine_rv.setAdapter(routineAdapter);

        add_routine_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                WorkoutListDialogFragment routineDialog = new WorkoutListDialogFragment();
                routineDialog.show(getSupportFragmentManager(), "RoutineListDialog");
            }
        });

        routineAdapter.setOnRoutineClickListener(new RoutineAdapter.OnRoutineItemClickListener() {
            @Override
            public void OnAddBtnClick(RoutineDetailAdapter curDetailAdapter) {
                routineAdapter.addDetailItem(curDetailAdapter);
            }

            @Override
            public void OnDeleteBtnClick(RoutineDetailAdapter curDetailAdapter) {
                routineAdapter.deleteDetailItem(curDetailAdapter);
            }
        });
    }

    private void initViews() {
        title = findViewById(R.id.body_part_detail_title);
        routine_rv = findViewById(R.id.routine_recyclerview);
        add_routine_btn = findViewById(R.id.add_routine);
    }

    private void setRoutineRecyclerview() {
        routineLayoutManger = new LinearLayoutManager(getApplicationContext(), RecyclerView.VERTICAL, false);
        routine_rv.setLayoutManager(routineLayoutManger);
        routine_rv.setHasFixedSize(true);
        RecyclerView.ItemDecoration divider = new DividerItemDecorator(ContextCompat.getDrawable(getApplicationContext(), R.drawable.divider));
        routine_rv.addItemDecoration(divider);
    }

    public void setPageTitle(Intent intent) {
        if(intent != null) {
            titleData = intent.getStringArrayListExtra("bodypart");
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                String new_title = String.join(" / ", titleData);
                title.setText(new_title);
            }
            else {
                StringBuilder new_title = new StringBuilder();
                for (int i = 0; i < titleData.size(); i++) {
                    if (i == titleData.size() - 1) {
                        new_title.append(titleData.get(i));
                        break;
                    }
                    new_title.append(titleData.get(i)).append(" / ");
                }
                title.setText(new_title);
            }
        }
    }

    public void addRoutine(String routine) {
        routineAdapter.addItem(new RoutineModel(routine));
        routine_rv.smoothScrollToPosition(routineAdapter.getItemCount() - 1);
    }
}

ADDED

Main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".data.DailyRecordDetailActivity">
    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/Theme.AppBarOverlay"
        app:elevation="0dp">
        <androidx.appcompat.widget.Toolbar
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                <TextView
                    android:id="@+id/body_part_detail_title"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

For every RoutineModel item you recreate detailAdapter but you need new instance. Check TODO in code below:

public class RoutineAdapter extends RecyclerView.Adapter<RoutineAdapter.ViewHolder> {
        Context context;

        ArrayList<RoutineModel> routineItems = new ArrayList<>();
        //public RoutineDetailAdapter detailAdapter; //TODO move it to local variable in method onCreateViewHolder
        OnRoutineItemClickListener listener;

like this

@NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);
        View itemView = inflater.inflate(R.layout.routine_item, parent, false);
        ViewHolder holder = new ViewHolder(itemView);
        RoutineDetailAdapter detailAdapter = new RoutineDetailAdapter(); //TODO Create local variable
        detailAdapter.addItem(new RoutineDetailModel());
        holder.setRoutineDetailRecyClerView();
        holder.rv_detail.setAdapter(detailAdapter);

        return holder;
    }

But this is not good way to solve this... Its better to use one Adapter with multiple item views. I have no time now to explain, I will post some code later...

EDITED

I had same problem and and solved this on this (better) way:

Create Adapter that support multiple views

public class MultipleViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private Context context;
    private List<Object> items; //List<Object or Generic class or abstarct base Model class parent of Routine and RoutineDetails>
    private OnItemClickListener listener; //TODO new interface

        ..............

}

Override onCreateViewHolder like this

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if(viewType == 1){
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.routine_item, parent, false);
            return new RoutineViewHolder (itemView);
        }/*else{*/
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.routine_detail_item, parent, false);
            return new RoutineDetailsViewHolder (itemView);
        //}
    }

Override onBindViewHolder like this and create your updateViews methods

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    Object object = items.get(position); //Object or Generic class or abstarct base Model class parent of Routine and RoutineDetails>
    if(object instanceof RoutineModel) {
        //create method
        updateRoutineViews((RoutineViewHolder) holder, (RoutineModel) object, position);
    }else if(object instanceof RoutineDetailsModel) {
        //create method
        updateRoutineDetailsViewHolder((RoutineDetailsViewHolder) holder, (RoutineDetailModel) object);
    }
}

and override getItemViewType method

@Override
    public int getItemViewType(int position) {
        Object object = items.get(position);
        if(object instanceof RoutineModel){
            return 1;
        }
        //else if instanceOf RoutineDetailModel return 0
        return 0; 
    }

This is so simple and better for performance...

Also in OnItemClickListener get item by position and check instanceOf object like above. You can add new item to the clickedItemPosition + 1 with adapter.notifyDataSetChanges()

In MainActivity you have to create mixed list with Routine and Routine details

List<Object> mixedList = new ArrayList<>();
for(RoutineModel rm: routineList){
    mixedList.add(rm);
    if(rm has routineDetailModels){ //pseudo code
        for(RoutineDetailModels rmdetilas: rm.getRoutineDetailModels()){
            mixedList.add(rmdetilas);
        }
    }
}
adpater.swapData(mixedList); //create your method to swap or set data to adapter

EDITED handle click

public interface OnItemClickListener{
    void onClick(View view, int position);
    void onLongClick(View view, int position);
}

and

adapter.setOnItemClickListener(new OnItemClickListener() {
        @Override
        public void onClick(View view, int position) {
            Object item = (Object) adapter.getItem(position);
            if(item instanceof Routine) {
                if (view.getId() == R.id.delete_id) {
                    //TODO delete form list, notifyItemRemoved or generate all mixeddata again and swap
                } else if (view.getId() == R.id.whateveryouwant) {
                    //TODO doSomething()
                }

            }
        }

        @Override
        public void onLongClick(View view, int position) {

        }
    });

The code above is just an example of how to solve the problem For more help feel free to comment

EDITED 2

public class RoutineViewHolder extends RecyclerView.ViewHolder  {
    public RecyclerView rv_detail;
    public TextView routine;
    public Button addSet;
    public Button deleteSet;

    public ViewHolder(@NonNull View itemView) {
        super(itemView);
        //initViews(); in constructor
        routine = itemView.findViewById(R.id.routine);
        rv_detail = itemView.findViewById(R.id.detail_routine);
        addSet = itemView.findViewById(R.id.add_set);
        deleteSet = itemView.findViewById(R.id.delete_set);
    } 
}

update method called from onBindViewHolder

private void updateRoutineViews(RoutineViewHolder holder, RoutineModel routineItem, position){
    holder.routine.setText(routineItem.getRoutine());
    holder.rv_detail.setText(routineItem.getWhateverYouWant());

    holder.deleteSet.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onItemClickListener.onClick(v, position); //or holder.getAdapterPosition()
        }
    });
}

COMPLETE CODE

Create complete new app to test it and understand (copy/paste this code), then integrate in your app...

There are more improvements but I don't have time, this is a quick fix

Activity

public class RoutineActivity extends AppCompatActivity {

    private MultipleViewAdapter adapter;
    private List<RoutineModel> routineList;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_routine);

        RecyclerView contentList = findViewById(R.id.list);
        contentList.setLayoutManager(new LinearLayoutManager(this));
        adapter = new MultipleViewAdapter(this, new ArrayList<>());
        contentList.setAdapter(adapter);
        adapter.setOnItemClickListener(new MultipleViewAdapter.OnItemClickListener() {
            @Override
            public void onClick(View view, int position) {
                Object item = (Object) adapter.getItem(position);
                if(item instanceof RoutineModel) {
                    RoutineModel routineModel = (RoutineModel) item;
                    if (view.getId() == R.id.add_set) {
                        int weight = randomInt(99);
                        Toast.makeText(RoutineActivity.this, "New item with weight: " + weight + " kg", Toast.LENGTH_SHORT).show();
                        routineModel.addDetails(new RoutineDetailsModel(routineModel.getDetailsSize() + 1, weight));
                        adapter.swapData(getMixedList()); // OR add item to adapter and notify item inserted
                        //TODO implement SnappingLinearLayoutManager and set list.smoothScroleto... routineModel
                    } else if (view.getId() == R.id.delete_set) {
                        //TODO doSomething()
                        boolean deleted = routineModel.removeDetails(routineModel.getDetailsSize() - 1); // -1 !!! to delete last item
                        Toast.makeText(RoutineActivity.this, deleted ? "Last item is deleted": "No more items", Toast.LENGTH_SHORT).show();
                        adapter.swapData(getMixedList()); // OR remove item from adapter and notify item removed
                        //TODO implement SnappingLinearLayoutManager and set list.smoothScroleto.... routineModel
                    }

                }else if(item instanceof RoutineDetailsModel) {
                    RoutineDetailsModel routineModel = (RoutineDetailsModel) item;
                    Toast.makeText(RoutineActivity.this, "Weight: " + routineModel.getWeight() + " kg", Toast.LENGTH_SHORT).show();

                //TODO EDITED 4 (copy/paste this) random new Weight
                routineModel.setWeight(randomInt(99));
                adapter.notifyItemChanged(position);
                Toast.makeText(RoutineActivity.this, "New Weight: " + routineModel.getWeight() + " kg", Toast.LENGTH_SHORT).show();

                }
            }

            @Override
            public void onLongClick(View view, int position) {

            }
        });

        initFakeData();
        adapter.swapData(getMixedList());
    }


    private List<Object> getMixedList() {
        List<Object> mixedList = new ArrayList<>();
        for(RoutineModel rm: routineList){
            mixedList.add(rm);
            if(rm.getRoutineDetailsModel() != null && rm.getRoutineDetailsModel().size() > 0){
                for(RoutineDetailsModel rmdetilas: rm.getRoutineDetailsModel()){
                    mixedList.add(rmdetilas);
                }
            }
        }
        return mixedList;
    }


    private void initFakeData() {
        routineList = new ArrayList<>();
        for(int i = 0; i < 5; i++){
            RoutineModel routineModel = new RoutineModel(String.valueOf(i + 1));
            for(int j = 0; j < 4; j++){
                routineModel.addDetails(new RoutineDetailsModel(j+1, randomInt(99)));
            }
            routineList.add(routineModel);
        }
    }

    private int randomInt(int max) {
        return (int) Math.floor(Math.random() * max);
    }
}

Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

Adapter

public class MultipleViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private final Context context; // TODO only if you need conetxt
    private List<Object> items;
    private OnItemClickListener onItemClickListener;

    public MultipleViewAdapter(Context context, List<Object> items) {
        this.context = context;
        this.items = items;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewH

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...