使用MVP模式实现onItemClickListener

11

我正在学习MVP,并且在实现onClickListener时感到困惑,不知道应该在哪里以及如何实现,同时又不会破坏MVP的概念。

我遵循了这篇指南:https://android.jlelse.eu/recyclerview-in-mvp-passive-views-approach-8dd74633158

这是我的实现:

适配器:

public class RepositoriesRecyclerAdapter extends RecyclerView.Adapter<RepositoriesRecyclerAdapter.RepoViewHolder> {


private final RepositoriesListPresenter presenter;

public RepositoriesRecyclerAdapter(RepositoriesListPresenter repositoriesPresenter) {
    this.presenter = repositoriesPresenter;
}

@Override
public RepositoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return new RepositoryViewHolder(LayoutInflater.from(parent.getContext())
                                            .inflate(R.layout.cell_repo_view, parent, false));
}

@Override
public void onBindViewHolder(RepositoryViewHolder holder, int position) {
    presenter.onBindRepositoryRowViewAtPosition(position, holder);

}

@Override
public int getItemCount() {
    return presenter.getRepositoriesRowsCount();
}

}

仓库视图持有者

public class RepositoryViewHolder extends RecyclerView.ViewHolder implements RepositoryRowView {

    TextView titleTextView;
    TextView starsCountTextView;

    public RepositoryViewHolder(View itemView) {
        super(itemView);
        titleTextView = itemView.findViewById(R.id.repoTitleText);
        starsCountTextView = itemView.findViewById(R.id.repoStarsCountText);
    }

    @Override
    public void setTitle(String title) {
        titleTextView.setText(title);
    }

    @Override
    public void setStarCount(int starCount) {
        starsCountTextView.setText(String.format("%s ★", starCount));
    }
}

RepositoryRowView

interface RepositoryRowView {

    void setTitle(String title);

    void setStarCount(int starCount);
}

我看到的所有指南都是关于在Adapter中创建onClickListener对象,然后在ViewHolder中使用它,但在这个实现中,我在我的Presenter中重写了所有的adapter函数,并且传递onClickListener(与Android相关的东西)会违反MVP模式。在这种情况下该怎么办?也许有人可以编写一个解决方案 - 真的很困惑。

我的主要目标是单击RecyclerView项并获取项名称(通过toast)。

2个回答

14

OnClickListener是Android SDK中的一个接口。您的Presenter不应该了解Andriod SDK的任何内容。它应该是纯Java,这样就可以仅使用JVM上的单元测试进行测试。它不应该了解视图、RecyclerView、Adapter或ViewHolder。

您的onBindViewHolder不违反此原则,因为它通过抽象接口RepositoryRowView进行了分离。

您应该在适配器/视图持有者中实现OnClickListener,并从那里调用您的Presenter。

public class RepositoryViewHolder extends RecyclerView.ViewHolder implements RepositoryRowView, View.OnClickListener {

    TextView titleTextView;
    TextView starsCountTextView;
    RepositoriesListPresenter presenter;

    public RepositoryViewHolder(View itemView, RepositoriesListPresenter presetner) {
        super(itemView);
        titleTextView = itemView.findViewById(R.id.repoTitleText);
        starsCountTextView = itemView.findViewById(R.id.repoStarsCountText);
        this.presenter = presenter;
        itemView.setOnClickListener(this);
    }

    @Override
    public void setTitle(String title) {
        titleTextView.setText(title);
    }

    @Override
    public void setStarCount(int starCount) {
        starsCountTextView.setText(String.format("%s ★", starCount));
    }

    @Override
    public void onClick(View view) {
        if (presenter != null) {
            presenter.onItemInteraction(getAdapterPosition());
        }
    }
}

如果我想改变所选项目的颜色,该怎么办呢?我不能将项目传递回递交者(如你之前所述),但我可以传递位置(如你所示),那么VIEW(片段)会如何使用该位置改变颜色或其他内容呢? - JoshuaMad
1
@JoshuaMad 你可以使用已经存在的抽象化。你可以将位置和RepositoryRowView(由你的ViewHolder实现,所以你可以将"this"作为参数添加进去)传递给Presenter。然后你可以在RepositoryRowView类中添加一个方法,比如"markRowAs...",并实现颜色变化的功能。 - Josef Adamcik

2

不要在适配器内部调用Presenter,我宁愿制作一个接口来处理点击事件,因为你会在视图中实例化这个适配器,所以最好将MVP模式与视图内元素的单击事件保持一致,而不是在适配器本身中处理。

以下示例使用Kotlin编写,但我相信您可以理解它。

首先,只需创建一个简单的接口,以便在用户单击列表中的任何项目时调用单击事件。

Original Answer翻译成“最初的回答”

class EquipmentAdapter(private val context: Context,private var equipmentList:ArrayList<Equipment>,itemListener:RecyclerViewClickListener): RecyclerView.Adapter<EquipmentAdapter.EquipmentViewHolder>() {

    interface RecyclerViewClickListener {
        fun recyclerViewListClicked(v: View?, position: Int)
    }

    companion object{
        var itemClickListener: RecyclerViewClickListener? = null
        var equipmentSearchList:ArrayList<Equipment>? = null
    }

    init {
        equipmentSearchList = equipmentList
        itemClickListener = itemListener
    }

然后,在您的ViewHolder中,您应该调用此接口来处理单击事件。最初的回答中提到了这一点。
inner class EquipmentViewHolder(itemView: View): RecyclerView.ViewHolder(itemView), View.OnClickListener {
    val equipmentName:TextView = itemView.txt_equipmentname

    init {
        itemView.setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        itemClickListener?.recyclerViewListClicked(v, adapterPosition)
    }
}

最后,在您调用适配器的视图中实现点击接口,然后只需在那里管理Presenter交互,而不是在适配器内部管理。
class EquipmentActivity : BaseActivity(), EquipmentContract.EquipmentView, EquipmentAdapter.RecyclerViewClickListener ...

最初的回答
并实现点击方法
override fun recyclerViewListClicked(v: View?, position: Int) {
    presenter.onItemInteraction(position)
}

这样做可以确保列表中元素的点击是来自于视图本身而不是适配器,因此您可以像往常一样与Presenter交互,并且还可以做更多的事情来保持您的项目整洁。

原始答案:最初的回答


这个答案应该被标记为正确的。 - Funrab

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接