gpt4 book ai didi

android-recyclerview - 双向数据绑定(bind)、RecyclerView、ViewModel、Room、LiveData、Oh My

转载 作者:行者123 更新时间:2023-12-04 08:20:49 26 4
gpt4 key购买 nike

刚接触 Android 开发,我正在尝试结合 RecyclerView、ViewModel、Room 和 LiveData 进行双向数据绑定(bind)。我摸索单向绑定(bind),但无法弄清楚双向绑定(bind)。

简单地说,我希望能够点击 id/switch_enabled 开关并更新 Db 以反射(reflect)这一点(然后我计划利用它来更新类/Db 中的其他成员)。我想我需要一些关于我的 ViewModel 上的 set(value) 和在 Db 中更新正确的 RecyclerView 项目的帮助,但我不确定如何执行此操作,或者这是否是正确或最佳的方法。

谢谢你。

类(class):

data class Person (@ColumnInfo(name = "first_name") val firstName: String,
@ColumnInfo(name = "last_name") val lastName: String,

//...

val enabled: Boolean = true
){
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

RecyclerView 的布局细节:
<layout 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">

<data>
<variable
name="p" type="com.example.data.Person" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/first_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{p.firstName}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="John" />

<TextView
android:id="@+id/last_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:text="@{' ' + p.lastName}"
app:layout_constraintStart_toEndOf="@id/first_name"
app:layout_constraintTop_toTopOf="parent"
tools:text=" Doubtfire" />

<Switch
android:id="@+id/switch_enabled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="@={p.enabled}"
app:layout_constraintBaseline_toBaselineOf="@id/last_name"
app:layout_constraintEnd_toEndOf="parent" />

<!--...-->

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View 模型:
class MainViewModel(private val repository: DataRepository) : ViewModel() {
private val _people: LiveData<List<Person>>
// @Bindable?
// @get:Bindable?
var people: LiveData<List<Person>>
@Bindable
get() = _people
set(value) {
//Find out which member of the class is being changed and update the Db?
Log.d(TAG, "Value for set is $value!")
}
init {
_people = repository.livePeople()
}
}

分段:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val binding = FragmentPeopleBinding.inflate(inflater, container, false)
val context = context ?: return binding.root

val factory = Utilities.provideMainViewModelFactory(context)
viewModel = ViewModelProviders.of(requireActivity(), factory).get(MainViewModel::class.java)

val adapter = PeopleViewAdapter()
viewModel.people.observe(this, Observer<List<Person>> {
adapter.submitList(it)
})

binding.apply {
vm = viewModel
setLifecycleOwner(this@PeopleFragment)
executePendingBindings()
rvPeopleDetails.adapter = adapter
}
return binding.root
}

列表适配器:
class PeopleViewAdapter: ListAdapter<Person, PeopleViewAdapter.ViewHolder>(PeopleDiffCallback()) {
class PeopleDiffCallback : DiffUtil.ItemCallback<Person>() {
override fun areItemsTheSame(oldItem: Person, newItem: Person): Boolean {
return oldItem.id == newItem.id
}

override fun areContentsTheSame(oldItem: Person, newItem: Person): Boolean {
return oldItem.number == newItem.number
}
}

class ViewHolder(val binding: FragmentPeopleDetailBinding) : RecyclerView.ViewHolder(binding.root) {

fun bind(person: Person) {
binding.p = person
}
}

@NonNull
override fun onCreateViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(FragmentPeopleDetailBinding.inflate(LayoutInflater.from(parent.context), parent, false))

@NonNull
override fun onBindViewHolder(@NonNull holder: ViewHolder, position: Int) {
holder.apply {
bind(getItem(position))
}
}
}

最佳答案

我得出了相同的结论,即最好的方法是将 View 模型提供给托管在回收器 View 中显示的项目的布局绑定(bind)。我为这种情况创建了一个通用解决方案。

该适配器可以在下面看到,并且还支持多种类型的布局。

public abstract class ViewModelBaseAdapter<T extends Diffable, VM extends ViewModel>
extends ListAdapter<T, DoubleItemViewHolder<T, VM>> {

private final int itemVariableId;

private final int viewModelVariableId;

/**
* Constructor
*
* @param diffCallback the comparison strategy between items in {@code this} adapter
* @param variableId the variable in the data binding layout to set with the items
*/
public ViewModelBaseAdapter(int itemVariableId, int viewModelVariableId) {

super(new DiffUtil.ItemCallback<T>() {

@Override
public boolean areItemsTheSame(@NonNull Diffable oldItem,
@NonNull Diffable newItem) {

return oldItem.isSame(newItem);
}

@Override
public boolean areContentsTheSame(@NonNull Diffable oldItem,
@NonNull Diffable newItem) {

return oldItem.isContentSame(newItem);
}
});

this.itemVariableId = itemVariableId;
this.viewModelVariableId = viewModelVariableId;
}

@NonNull
@Override
public DoubleItemViewHolder<T, VM> onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

ViewDataBinding binding = DataBindingUtil.inflate(
LayoutInflater.from(parent.getContext()), viewType, parent, false);

return new DoubleItemViewHolder<>(binding, itemVariableId, viewModelVariableId);
}

@Override
public void onBindViewHolder(@NonNull DoubleItemViewHolder<T, VM> holder, int position) {

holder.bind(getItem(position), getItemViewModel(position));
}

@Override
public abstract int getItemViewType(int position);

/**
* Provides the {@code ViewModel} to be bound together with the item at
* a specified position.
*
* @param position the position of the item
* @return the view model
*/
public abstract VM getItemViewModel(int position);
}

接口(interface)和 ViewHolder 定义如下。

public interface Diffable {

boolean isSame(Diffable other);

boolean isContentSame(Diffable other);
}

public final class DoubleItemViewHolder<V1, V2> extends RecyclerView.ViewHolder     {

private final ViewDataBinding binding;

private final int firstVariableId;

private final int secondVariableId;

/**
* Constructor
*
* @param binding the binding to use
* @param firstVariableId the first variable set on the binding
* @param secondVariableId the second variable set on the binding
*/
public DoubleItemViewHolder(ViewDataBinding binding,
int firstVariableId,
int secondVariableId) {

super(binding.getRoot());
this.binding = Objects.requireNonNull(binding);
this.firstVariableId = firstVariableId;
this.secondVariableId = secondVariableId;
}

/**
* Sets the data binding variables to the provided items
* and calls {@link ViewDataBinding#executePendingBindings()}.
*
* @param firstItem the first item to bind
* @param secondItem the second item to bind
* @throws NullPointerException if {@code firstItem} or {@code secondItem} is {@code null}
*/
public void bind(@NonNull V1 firstItem, @NonNull V2 secondItem) {

Objects.requireNonNull(firstItem);
Objects.requireNonNull(secondItem);
binding.setVariable(firstVariableId, firstItem);
binding.setVariable(secondVariableId, secondItem);
binding.executePendingBindings();
}
}

现在“样板”已经设置好,使用起来就很简单了。

例子

该示例的目的是提供一个完整的答案,包括为希望使用此方法的任何人设置,它可以非常简单地概括。

首先定义模型。

public class AppleModel implements Diffable {
// implementation...
}

public class DogModel implements Diffable {
// implementation...
}

然后我们像这样在 View 模型中公开 diffables。

private final MutableLiveData<List<Diffable>> diffables = new MutableLiveData<>();

public LiveData<List<Diffable>> getDiffables() {

return diffables;
}

并通过覆盖 ViewModelBaseAdapter 来实现适配器。

public class ModelAdapter
extends ViewModelBaseAdapter<Diffable, MyViewModel> {

private final MyViewModel myViewModel;

public SalesmanHistoryAdapter(MyViewModel myViewModel) {

super(BR.item, BR.vm);
myViewModel = myViewModel;
}

@Override
public int getItemViewType(int position) {

final Diffable item = getItem(position);

if (item instanceof AppleModel) {
return R.layout.item_apple_model;
}

if (item instanceof DogModel) {
return R.layout.item_dog_model;
}

throw new IllegalArgumentException("Adapter does not support " + item.toString());
}

@Override
public MyViewModel getItemViewModel(int position) {
// You can provide different viewmodels if you like here.
return myViewModel;
}
}

然后将这些项目和适配器附加到布局中的回收站 View 。

<variable
name="adapter"
type="ModelAdapter" />

<variable
name="vm"
type="MerchantLogViewModel" />

<androidx.recyclerview.widget.RecyclerView
list_adapter="@{adapter}"
list_adapter_items="@{vm.diffables}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

它们是使用此绑定(bind)适配器连接的。

@BindingAdapter(value = {
"list_adapter",
"list_adapter_items"
})
public static <T> void setRecyclerViewListAdapterItems(RecyclerView view,
@NonNull ListAdapter<T, ?> adapter,
@Nullable final List<T> items) {

Objects.requireNonNull(adapter);

if (view.getAdapter() == null) {
view.setAdapter(adapter);
Timber.w("%s has no adapter attached so the supplied adapter was added.",
view.getClass().getSimpleName());
}

if (items == null || items.isEmpty()) {

adapter.submitList(new ArrayList<>());
Timber.w("Only cleared adapter because items is null");

return;
}

adapter.submitList(items);
Timber.i("list_adapter_items added %s.", items.toString());
}

您的项目布局在哪里(此处仅显示 DogModel,但 AppleModel 也是如此)。

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

<data>
<variable
name="item"
type="DogModel" />

<variable
name="vm"
type="MyViewModel" />

</data>
<!-- Add rest below -->

现在,您可以使用数据绑定(bind)将 View 模型与布局中的项目一起使用。

关于android-recyclerview - 双向数据绑定(bind)、RecyclerView、ViewModel、Room、LiveData、Oh My,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54206907/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com