Привязка данных Recyclerview и onClick

Хорошо попробую еще раз. В прошлый раз я спрашивал о передаче данных между recyclerview и элементом, и один человек помог мне открыть элемент по клику, но я до сих пор не знаю, как отобразить данные элемента, на который нажали, в новой активности. Я хочу щелкнуть элемент, а затем отобразить данные этого элемента в новом действии. В этом упражнении я хочу редактировать данные. Кто-нибудь знает, как это сделать? Мне нужна любая идея.

Адаптер RecyclerView с интерфейсом OnItemClickListener:

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.TaskViewHolder> {

private List<MainViewModel> mTasks;
private List<Task> tasks = new ArrayList<>();
private Context context;
private EditTaskViewModel editTaskViewModel;


public RecyclerViewAdapter(List<MainViewModel> tasks, Context context, EditTaskViewModel editTaskViewModel) {
    this.mTasks = tasks;
    this.context = context;
    this.editTaskViewModel = editTaskViewModel;
}


@NonNull
@Override
public RecyclerViewAdapter.TaskViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    final RecyclerViewItemBinding binding = DataBindingUtil.inflate(inflater, R.layout.recycler_view_item, parent, false);

    binding.setItemClickListener(new OnItemClickListener() {
        @Override
        public void onItemClick(View view) {
            Intent intent = new Intent(view.getContext(), EditTaskActivity.class);
            intent.putExtra("id", binding.getPosition());
            view.getContext().startActivity(intent);
            Toast.makeText(view.getContext(), "ID " + binding.getPosition(), Toast.LENGTH_SHORT).show();

        }
    });
    return new TaskViewHolder(binding);
}

@Override
public void onBindViewHolder(@NonNull final RecyclerViewAdapter.TaskViewHolder holder, final int position) {
    Task currentTask = tasks.get(position);
    holder.mBinding.descriptionItem.setText(currentTask.getDescription());
    holder.mBinding.dateItem.setText(currentTask.getDate());
    holder.mBinding.timeItem.setText(currentTask.getTime());
    holder.mBinding.setPosition(position);
}

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

public void setTasks(List<Task> tasks) {
    this.tasks = tasks;
    notifyDataSetChanged();
}

public Task getTaskPosition(int position) {
    return tasks.get(position);
}

public class TaskViewHolder extends RecyclerView.ViewHolder {
    private final RecyclerViewItemBinding mBinding;

    public TaskViewHolder(RecyclerViewItemBinding binding) {
        super(binding.getRoot());
        this.mBinding = binding;
    }

        public void bind (MainViewModel mainViewModel){
            mBinding.setItemView(mainViewModel);
            mBinding.executePendingBindings();
        }
    }

    public interface OnItemClickListener {
        void onItemClick(View view);
    }

XML-файл элемента:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

<data>
    <variable
        name="itemView"
        type="com.example.daniellachacz.taskmvvm.viewmodel.MainViewModel">
    </variable>

    <variable
        name="itemClickListener"
        type="com.example.daniellachacz.taskmvvm.adapter.RecyclerViewAdapter.OnItemClickListener">
    </variable>

    <variable
        name="task"
        type="com.example.daniellachacz.taskmvvm.model.Task">
    </variable>

    <variable
        name="position"
        type="int">
    </variable>

</data>

<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="120dp"
android:shadowColor="@color/colorPrimary"
android:backgroundTint="@color/cardview_shadow_end_color">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="110dp"
android:layout_marginBottom="6dp"
android:layout_marginTop="6dp"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:onClick="@{(view)-> itemClickListener.onItemClick(view)}">

<TextView
    android:id="@+id/description_item"
    android:layout_width="250dp"
    android:layout_height="96dp"
    android:layout_marginStart="5dp"
    android:layout_marginTop="9dp"
    android:layout_marginBottom="5dp"
    android:text="@{itemView.description}"
    android:textSize="18sp"
    android:textColor="#020202"
    android:focusable="true" />

<TextView
    android:id="@+id/date_item"
    android:layout_width="90dp"
    android:layout_height="40dp"
    android:layout_alignParentTop="true"
    android:layout_alignParentEnd="true"
    android:layout_marginTop="9dp"
    android:layout_marginEnd="10dp"
    android:gravity="center"
    android:text="@{itemView.date}"
    android:textColor="#020202"
    android:textSize="16sp" />

<TextView
    android:id="@+id/time_item"
    android:layout_width="90dp"
    android:layout_height="40dp"
    android:layout_alignParentBottom="true"
    android:layout_alignStart="@+id/date_item"
    android:layout_marginBottom="10dp"
    android:layout_marginEnd="10dp"
    android:gravity="center"
    android:text="@{itemView.time}"
    android:textColor="#020202"
    android:textSize="16sp" />

    </RelativeLayout>
    </android.support.v7.widget.CardView>

    </layout>

при создании:

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

floatingActionButton = findViewById(R.id.floating_action_button);

List<Task> tasks = new ArrayList<>();

RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);

final RecyclerViewAdapter recyclerViewAdapter = new     RecyclerViewAdapter(context, tasks);
recyclerView.setAdapter(recyclerViewAdapter);

mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
mainViewModel.getAllTasks().observe(this, recyclerViewAdapter::setTasks);

person user9897182    schedule 09.11.2018    source источник


Ответы (3)


Вот несколько советов, которые могут оказаться полезными:

Не полагайтесь на ViewModel в своих адаптерах. ViewModel предназначены для обработки событий из представлений (фрагментов или действий) и передачи обновлений обратно в представления с помощью некоторого наблюдаемого механизма (чаще всего LiveData экземпляров). Ссылаться на ваши ViewModel непосредственно внутри адаптера плохо, так как это связывает их вместе. Это означает, что вам будет очень сложно повторно использовать адаптер с другим ViewModel, если это необходимо. Я знаю, что на данный момент это маловероятно, но просто поверьте мне в этом. После применения изменений ваш адаптер должен выглядеть примерно так:

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.TaskViewHolder> {

    private LayoutInflater mLayoutInflater;
    private List<Task> mTasks;

    private OnItemClickListener mOnItemClickListener;

    public RecyclerViewAdapter(@NonNull Context context, @NonNull List<Task> tasks) {
         mLayoutInflater = LayoutInflater.fromContext(context);
         mTasks = tasks;
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
    }

    @NonNull
    @Override
    public RecyclerViewAdapter.TaskViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        final RecyclerViewItemBinding binding = DataBindingUtil.inflate(mLayoutInflater, R.layout.recycler_view_item, parent, false);
        return new TaskViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull final RecyclerViewAdapter.TaskViewHolder holder, final int position) {
        Task currentTask = tasks.get(position);
        holder.bind(currentTask, mOnItemClickListener);
    }

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

    public void setTasks(List<Task> tasks) {
        this.tasks = tasks;
        notifyDataSetChanged();
    }

    public Task getTaskPosition(int position) {
        return tasks.get(position);
    }

    public class TaskViewHolder extends RecyclerView.ViewHolder {
        private final RecyclerViewItemBinding mBinding;

         public TaskViewHolder(RecyclerViewItemBinding binding) {
             super(binding.getRoot());
             this.mBinding = binding;
         }

         public void bind (Task item, OnItemClickListener onItemClickListener) {
             mBinding.setItem(item);
             mBinding.executePendingBindings();
             itemView.setOnClickListener(view -> {
                 if (onItemClickListener != null) {
                     onItemClickListener.onItemClick(view, item);
                 }
             }
         }
    }

    public interface OnItemClickListener {
        void onItemClick(View view, Task item);
    }
}

Метод OnItemClickListener.onItemClick() теперь передает представление и сам элемент в качестве параметров. Это самый простой способ показать выбранный элемент всем, кто может быть заинтересован. Прослушиватель кликов не установлен на уровне адаптера, используя setOnItemClickListener().

Настройка OnClickListener вида элемента теперь выполняется в методе bind() метода TaskViewHolder. При привязке мы знаем точный элемент, который будет заполнять представление, поэтому мы можем вернуть его в файл OnItemClickListener.

Вам также придется упростить макет, так как есть много вещей, которые на самом деле не нужны. Это может выглядеть так:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

<data>
    <variable
        name="task"
        type="com.example.daniellachacz.taskmvvm.model.Task">
    </variable>
</data>

<android.support.v7.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="120dp"
    android:shadowColor="@color/colorPrimary"
    android:backgroundTint="@color/cardview_shadow_end_color">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="110dp"
        android:layout_marginBottom="6dp"
        android:layout_marginTop="6dp"
        android:layout_marginStart="6dp"
        android:layout_marginEnd="6dp">

        <TextView
            android:id="@+id/description_item"
            android:layout_width="250dp"
            android:layout_height="96dp"
            android:layout_marginStart="5dp"
            android:layout_marginTop="9dp"
            android:layout_marginBottom="5dp"
            android:text="@{item.description}"
            android:textSize="18sp"
            android:textColor="#020202"
            android:focusable="true" />

        <TextView
            android:id="@+id/date_item"
            android:layout_width="90dp"
            android:layout_height="40dp"
            android:layout_alignParentTop="true"
            android:layout_alignParentEnd="true"
            android:layout_marginTop="9dp"
            android:layout_marginEnd="10dp"
            android:gravity="center"
            android:text="@{item.date}"
            android:textColor="#020202"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/time_item"
            android:layout_width="90dp"
            android:layout_height="40dp"
            android:layout_alignParentBottom="true"
            android:layout_alignStart="@+id/date_item"
            android:layout_marginBottom="10dp"
            android:layout_marginEnd="10dp"
            android:gravity="center"
            android:text="@{item.time}"
            android:textColor="#020202"
            android:textSize="16sp" />

        </RelativeLayout>
    </android.support.v7.widget.CardView>

</layout>

Единственная переменная — это item, и мы привязываем ее свойства к TextViews.

Я думаю, этого должно быть достаточно, чтобы вы начали.

Еще пара вещей, которые не имеют прямого отношения к вопросу, но важны.

  • Нулевая безопасность — вы никогда не проверяете ввод при вызове setTask() в адаптере. Клиент может передать null и вызвать сбои повсюду. Вы должны попытаться предотвратить это.
  • Вызов notifyDataSetChanged() не рекомендуется при работе с RecyclerView.Adapter, так как это отменит все встроенные анимации RecyclerView. Лучше использовать другие notify... методы. Возможно, вам захочется проверить DiffUtil в какой-то момент.
person Danail Alexiev    schedule 09.11.2018
comment
Хорошо, я сделал все, что вы предложили, и у меня есть ошибка: Невозможно запустить активность виртуальный метод 'java.lang.Object android.content.Context.getSystemService (java.lang.String)' для нулевой ссылки на объект - person user9897182; 10.11.2018
comment
Похоже, вы пытаетесь передать конструктору null в качестве контекста. Проверьте трассировку стека — она укажет вам точную строку, в которой произошло исключение. Затем вы можете отладить и посмотреть, что не так с кодом. - person Danail Alexiev; 11.11.2018
comment
Теперь все в порядке, но куда мне вставить код для перехода к элементу? - person user9897182; 11.11.2018
comment
Вы должны вызвать setOnItemClickListener для экземпляра RecyclerViewAdapter. Вы можете сделать это в своем фрагменте или действии, где вы обычно должны создавать экземпляры своих адаптеров. Затем вы можете передать элемент модели представления для обработки. - person Danail Alexiev; 11.11.2018
comment
Он открывает активность деталей элемента! Но есть проблема. Я вызываю метод viewmodel в действии деталей элемента, а представление и переменная элемента равны нулю. Это может быть неправильно? - person user9897182; 12.11.2018
comment
Проверьте параметры, которые вы передаете через дополнительные намерения, и попробуйте отладить свой код. Это лучший способ обнаружить потенциальные баги и ошибки. - person Danail Alexiev; 12.11.2018
comment
В Activity детали элемента при вызове метода из Viewmodel параметры: представление и элемент пусты. Вы уверены, что идея передачи данных между Activity хороша? Я уже немного запутался с этим. Привязку данных в Android поначалу немного сложно понять:/Документация Android не помогает... - person user9897182; 12.11.2018

Это зависит от того, как хранятся ваши данные и доступны ли они во втором действии. Если у вас есть статический ArrayList ваших данных, вы можете извлечь данные из него, используя индекс, который вы передали при щелчке элемента RV. Например:

class myData{
private ArrayList<Data> myDataArray;

static ArrayList<Data> getMyDataArray(){
   return myDataArray;
}

static void setMyDataArray(array)
   myDataArray = array;
}  

Таким образом, вы заполняете свой RV с помощью getMyDataArray(), а затем устанавливаете onlclick для отправки индекса, щелкнутого в RV, к следующему действию. В onLoad второго действия:

int myDataIndex = getIntent().getIntExtra("id",0);
Data myData = myData.getMyDataArray().get(myDataIndex);

Примечание. Данные — это любые ваши данные, это могут быть строки, целые числа или пользовательский класс/объект с данными.

person Notsileous    schedule 09.11.2018

Вот что я сделал для прослушивателя кликов по элементам в представлении переработчика, используя привязку данных.

КОД АДАПТЕРА

public class TC_DashboardRecViewAdapter extends RecyclerView.Adapter<TC_DashboardRecViewAdapter.ViewHolder> {
Context context;
List<String> list;
private TcDashboardItemBinding tcDashboardItemBinding;
ItemClickListener itemClickListener;

public TC_DashboardRecViewAdapter(Context context, List<String> dashboardItems, ItemClickListener itemClickListener) {
    this.context = context;
    this.list = dashboardItems;
    this.itemClickListener = itemClickListener;
}

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    ViewDataBinding binding = DataBindingUtil.inflate(inflater, R.layout.tc_dashboard_item, parent, false);
    tcDashboardItemBinding = (TcDashboardItemBinding) parent.getTag();
    return new ViewHolder(binding);
}

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.bind(list.get(position), itemClickListener, position);
}

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


class ViewHolder extends RecyclerView.ViewHolder {

    private ViewDataBinding binding;

    public ViewHolder(ViewDataBinding binding) {
        super(binding.getRoot());
        this.binding = binding;
    }

    public void bind(String s, ItemClickListener itemClickListener, int position) {
        this.binding.setVariable(BR.itemModel, s);
        this.binding.executePendingBindings();
        binding.getRoot().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(itemClickListener !=null){
                    itemClickListener.onItemClicked(binding.getRoot(), s, position);
                }
            }
        });
    }
}

ПУНКТ НАЖМИТЕ ИНТЕРФЕЙС:

public interface ItemClickListener {
void onItemClicked(View vh, Object item, int pos);
}

ФРАГМЕНТ/КОД АКТИВНОСТИ:

public class TC_DashboardFragment extends BaseFragment implements ItemClickListener {


public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

 tc_dashboardRecViewAdapter = new TC_DashboardRecViewAdapter(getContext(), getDashboardItems(), this);
    linearLayoutManager = new LinearLayoutManager(getContext());
    dashboardrecyclerview.setLayoutManager(new LinearLayoutManager(getContext()));
    binding.setAdapter(tc_dashboardRecViewAdapter);
}

@Override
public void onItemClicked(View vh, Object item, int pos) {
    Toast.makeText(mainActivity, item.toString(), Toast.LENGTH_SHORT).show();
}

Когда вы запускаете код и нажимаете на элемент просмотра ресайклера, он будет отображать тост с текстом из выбранного элемента.

person VIVEK CHOUDHARY    schedule 13.05.2019