带有 viewModel 和实时数据的 RecyclerView 适配器

问题描述 投票:0回答:4

我开始使用 android jetpack arch 组件,但遇到了一些困惑。另请注意,数据绑定不是一个选项。

我有一个带有 RecyclerView 的活动。我有一个如下所示的 ViewModel

public class Movie extends ViewModel {


public Movie movie;
public URL logoURL;

private MutableLiveData<Drawable> logo;

public MutableLiveData<Drawable> getLogo() {

    if (logo == null) {
        logo = new MutableLiveData<>();
    }
    return logo;
}


public PikTvChannelItemVM(Movie movie, URL logo) {

    this.movie = movie;
    this.logoURL = logoURL;


}

public Bitmap getChannelLogo() {

  //Do some network call to get the bitmap logo from the url
 }

}

以上一切都很好,尽管在我的 I recyclerview 中有以下代码。尽管在 onbindviewholder 中,当我尝试观察视图模型实时数据返回的图像时,它需要一个生命周期所有者引用,而我的回收器视图中没有该引用。请帮忙谢谢

public class MovieRecyclerViewAdapter extends RecyclerView
    .Adapter<MovieRecyclerViewAdapter.MovieItemViewHolder> {

public List<MovieViewModel> vmList;


public MovieRecyclerViewAdapter(List<MovieViewModel> vmList) {

    this.vmList = vmList;
    setHasStableIds(true);
}

@Override
public MovieItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    MovieView itemView = new MovieView(parent.getContext(), null);
    itemView.setLayoutParams(new ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
    ));
    return new MovieItemViewHolder(itemView);
}

@Override
public void onBindViewHolder(@NonNull MovieItemViewHolder holder, int position) {


    vmList.get(position).observe(?????, users -> {

  // As you can see I have no reference to the life cycle owner
       holder.imageView.setimage(some drawable returned by the View Model)
    });
}


@Override
public long getItemId(int position) {

    return position;
}

@Override
public int getItemViewType(int position) {

    return position;
}


@Override
public int getItemCount() {

    return vmList.size();
}

class MovieItemViewHolder extends RecyclerView.ViewHolder {

    private MovieView channelView;

    public MovieItemViewHolder(View v) {

        super(v);
        channelView = (MovieView) v;
    }

    public MovieView getChannelView() {

        return channelView;
    }
 }

}
java android mvvm android-recyclerview
4个回答
1
投票

我会尝试向适配器发送一个列表来显示它。我会观察适配器外部的数据,因为适配器的责任不是观察数据而是显示数据。


1
投票

您的适配器列表类型不应该是viewmodel。如果您想在不绑定的情况下使用实时数据,则需要在列表更新时自动更改 ui。

你可以这样做:

首先,你的电影类必须是模型类,而不是视图模型。

像平常一样安装适配器。只需添加一个

setList(Movie)
方法即可。此方法应该更新适配器列表。更新列表后不要忘记通知适配器。

然后创建实时数据列表,如

MutableLiveData<List<Movie>> movieLiveData = 
    new MutableLiveData<>();

您想要的地方。

在 Activity 中观察此列表,并在observe{} 中调用适配器 setList(Movie) 方法。

完成这一切后,如果您的列表更新,

setList(Movie)
方法将被触发,然后您的用户界面将被更新。


0
投票

你可以这样做:

视图模型:

class MyViewModel : ViewModel() {
    private val items = MutableLiveData<List<String>>()

    init {
        obtainList()
    }

    fun obtainList() { // obtain list from repository
        items.value = listOf("item1", "item2", "item3", "item4")
    }

    fun getItems(): LiveData<List<String>> {
        return items
    }
}

您的片段(或活动):

public class ContentFragment extends Fragment {
    private MyViewModel viewModel;
    private RecyclerView recyclerView;
    private MyAdapter adapter;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewModel = new ViewModelProvider(this).get(MyViewModel.class);
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, 
            Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_main, container, false);
        recyclerView = root.findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 4));
       
        // add observer 
        viewModel.getItems().observe(this, items -> {
            adapter = new MyAdapter(items); // add items to adapter
            recyclerView.setAdapter(adapter);
        });

        return root;
    }

适配器:

class MyAdapter(val list: List<String>) : RecyclerView.Adapter<MyAdapter.TextViewHolder {
    ...
    override fun onBindViewHolder(holder: TextViewHolder, position: Int) {
        holder.textView.text = list[position] // assign titles from the list.
        ...
    }
}

可以使用任何自定义对象来代替 String 对象。


0
投票

通常提出以下方案作为解决方案:

  1. Model
    调用
    ViewModel
    中的方法;
  2. ViewModel
    将变量保存在
    List
    中并将更新发送到
    LiveData
  3. View
    看到
    List
    中更新的
    LiveData
    并更新
    RecyclerView

也就是说,当你改变数据时,你通过

List
重新传输
LiveData
,重新定义它的值。然而,这会更新整个
RecyclerView
。每次。 (或者,您可以在
View
中创建一个复杂的跟踪机制,检查
List
的更改并调用通知。但这很不方便。)

我想使用

RecyclerView.Adapter
中的标准通知方法。
LiveData
的主要问题是你无法订阅动态变化列表中的每个元素,否则LiveData将与列表元素一起不断创建和删除。但是如果我们要一次加载 10K 个项目,需要多少内存呢?

所以我决定将

List
放入包装类中,并能够分配侦听器以添加到
List
:

class ObservableArrayList<Type> : ArrayList<Type>() {

    // interface for the change listener
    abstract class OnListUpdateListener<Type>(val looper: Looper) {
        abstract fun onItemAdded(position: Int, element: Type)
        abstract fun onItemRemoved(position: Int)
        abstract fun onItemUpdated(position: Int, element: Type)
    }
    
    // listeners of changes in the List
    private val listeners = mutableListOf<OnListUpdateListener<Type>>()

    fun addOnListUpdateListener(listener: OnListUpdateListener<Type>) {
        listeners.add(listener)
    }

    fun removeOnListUpdateListener(listener: OnListUpdateListener<Type>) {
        listeners.remove(listener)
    }

    // data changing

    override fun add(element: Type): Boolean {
        val result = super.add(element)
        // notifying listeners
        if (result) listeners.forEach { it.onItemAdded(super.size, element) }
        return result
    }

    override fun add(index: Int, element: Type) {
        super.add(index, element)
        listeners.forEach { it.onItemAdded(super.size, element) }
    }

    override fun remove(element: Type): Boolean {
        val position = super.indexOf(element)
        val result = super.remove(element)
        if (result) listeners.forEach { it.onItemRemoved(position) }
        return result
    }

    override fun removeAt(index: Int): Type {
        val result = super.removeAt(index)
        listeners.forEach { it.onItemRemoved(index) }
        return result
    }

    override fun set(index: Int, element: Type): Type {
        val result = super.set(index, element)
        listeners.forEach { it.onItemUpdated(index, element) }
        return result
    }

    // can be called from a background thread
    // (for pure MVVM, it can only be called in ViewModel methods)

    fun postAdd(element: Type): Boolean {
        val result = super.add(element)
        // notifying listeners
        if (result) {
            listeners.forEach {
                Handler(it.looper).post {
                    it.onItemAdded(super.size, element)
                }
            }
        }
        return result
    }

    fun postRemoveAt(index: Int): Type {
        val result = super.removeAt(index)
        // notifying listeners
        listeners.forEach {
            Handler(it.looper).post {
                it.onItemRemoved(index)
            }
        }
        return result
    }

    fun postSet(index: Int, element: Type): Type {
        val result = super.set(index, element)
        // notifying listeners
        listeners.forEach {
            Handler(it.looper).post {
                it.onItemUpdated(index, element)
            }
        }
        return result
    }

    // ... You can override the rest of the methods ... 
}

这个类非常容易使用。 我在

LiveData
:
 中创建了一个 
ViewModel

变量
val logsList = MutableLiveData(ObservableArrayList<LogEntry>())

当我从

Model
收到信息时,我只是将其保存到
List

logsList.value!!.postAdd(LogEntry((/*...*/))

View
中,我订阅接收
List
,然后订阅
View
中的更改
List

viewModel.logsList.observe(viewLifecycleOwner) { list ->
    // assign a sheet to RecyclerView 1 time (on list init)
    recyclerView.adapter = LogsAdapter(list, resources)

    // subscribe to the List updates
    list.addOnListUpdateListener(object :
        ObservableArrayList.OnListUpdateListener<LogEntry>(Looper.getMainLooper()) {

        override fun onItemAdded(position: Int, element: LogEntry) {
            recyclerView.adapter!!.notifyItemInserted(position)
        }

        override fun onItemRemoved(position: Int) {
            recyclerView.adapter!!.notifyItemRemoved(position)
        }

        override fun onItemUpdated(position: Int, element: LogEntry) {
            recyclerView.adapter!!.notifyItemChanged(position)
        }
    })
}
© www.soinside.com 2019 - 2024. All rights reserved.