在RecyclerView适配器中使用自定义视图?

28

我有一个基本的自定义View,长这样:

public class CustomView extends RelativeLayout {

    private User user;

    private ImageView profilePicture;

    public CustomView(Context context) {
        super(context);
        init();
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        inflate(getContext(), R.layout.custom_layout, this);

        profilePicture = (ImageView) findViewById(R.id.profilePicture);

        // ACCESS USER MODEL HERE
        // e.g. user.getUsername()
    }

}

正如您所看到的,我想在视图中访问用户数据(即:user.getUsername())。

我还需要能够在RecyclerView适配器中使用自定义视图。

这是我当前适配器的样子:

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

    private Context context;

    private List<User> userData;

    public MyAdapter(Context context, List<User> userData) {
        this.context = context;
        this.userData = userData;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(View v) {
            super(v);
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(context);

        // HOW TO INFLATE THE CUSTOM VIEW?

        // ViewHolder viewHolder = new ViewHolder(customView);

        return viewHolder;
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        // ANYTHING HERE?
    }

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

}

我如何在适配器中填充自定义视图?
此外,我应该在onBindViewHolder()中添加任何内容吗?

注意:我必须使用自定义视图,因为我在不同的适配器下使用此视图(即:不仅仅是这个RecyclerView适配器)。

3个回答

56
假设有一个类名为CustomView的类,它看起来像这样:
public class CustomView extends RelativeLayout {
    private User user;
    private ImageView profilePicture;

    // override all constructors to ensure custom logic runs in all cases
    public CustomView(Context context) {
        this(context, null);
    }
    public CustomView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }
    public CustomView(
            Context context,
            AttributeSet attrs,
            int defStyleAttr,
            int defStyleRes
    ) {
        super(context, attrs, defStyleAttr, defStyleRes);

        // put all custom logic in this constructor, which always runs
        inflate(getContext(), R.layout.custom_layout, this);
        profilePicture = (ImageView) findViewById(R.id.profilePicture);
    }

    public void setUser(User newUser) {
        user = newUser;
        // ACCESS USER MODEL HERE
        // e.g. user.getUsername()
    }
}

您的RecyclerView.AdapterRecyclerView.ViewHolder可能如下所示:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    // no Context reference needed—can get it from a ViewGroup parameter
    private List<User> userData;

    public MyAdapter(List<User> userData) {
        // make own copy of the list so it can't be edited externally
        this.userData = new ArrayList<User>(userData);
    }

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

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // no need for a LayoutInflater instance—
        // the custom view inflates itself
        CustomView itemView = new CustomView(parent.getContext());
        // manually set the CustomView's size
        itemView.setLayoutParams(new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
        ));
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        holder.getCustomView().setUser(userData.get(position));
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        private CustomView customView;

        public ViewHolder(View v) {
            super(v);
            customView = (CustomView) v;
        }

        public CustomView getCustomView() {
            return customView;
        }
    }
}
  • CustomView 管理自己的设置,这发生在它自己的构造函数中,并且在这种情况下使用 XML 文件的膨胀。 (或者,它可以通过编程方式设置其子视图。)
  • 因此,RecyclerView.Adapter 不需要执行任何膨胀操作 - 它只创建一个新的 CustomView 实例,并让 CustomView 自行处理其自身的设置。
  • CustomView 在其 setUser 方法被调用之前无法获取 User 实例,因此不能在构造函数中访问用户。 无论如何,在一个 CustomView 生命周期内,一个 RecyclerView 可以要求显示不同用户的信息。 因此,引入了一个 setUser 方法。
  • 由于 CustomView 是由代码实例化而不是由 XML 实例化的,因此无法在 XML 中定义大小属性。 因此,实例化后需要以编程方式进行大小调整。
  • onBindViewHolder 只需在 CustomView 上调用 setUser 来将 CustomView 与正确的 User 实例链接起来。
  • ViewHolder 类现在只是一个 RecyclerView 项和一个 CustomView 之间的链接。

RecyclerView中使用另一个类中的预构建自定义视图(即不在RecyclerView.Adapter内部膨胀XML)似乎从未被讨论。我认为这是一个很好的主意,即使自定义视图仅在RecyclerView中使用,因为它促进关注点分离遵循单一职责原则


3
这是一个很好的回答 - 加上最后一段,就成为了一个绝妙的回答。我已经开始在我所有的RecyclerView适配器中使用自定义视图,结果非常干净。 - Luke Needham
2
谢谢,Luke——我很感激你的赞美。我仍然惊讶于这种方法在我看到的项目中为什么没有被更多地采用。 - Alex Peters
@AlexPeters你说:“因为CustomView是通过代码实例化而不是通过XML实例化,所以无法在XML中定义大小属性。因此,在实例化后需要通过编程方式进行调整大小。”那么这种方式呢:https://dev59.com/oV4b5IYBdhLWcg3whB6V#38690715? - drmrbrewer
@drmrbrewer,抱歉,我不明白。您能否请重新表述问题? - Alex Peters
1
好的,我现在明白了。如果您的自定义组件从XML中膨胀,并且特别是如果该组件依赖于其最上层的特定大小约束,则听起来是一个完美的方法。我想我没有太多考虑一旦包装在ViewHolder中可能有哪些尺寸值是可能/可接受的。我猜我在上面的答案中放置的尺寸约束纯粹基于我认为可能是最合适的。例如,如果子项不如父项宽,我不知道事物会如何运作。 - Alex Peters
显示剩余2条评论

3
CustomView extends RelativeLayout {
你已经有了一个视图(好吧,一个ViewGroup)
如何填充自定义视图?
您不需要...自定义视图对象的重点是不需要XML,因此不需要填充。
您可以创建new CustomView(),但需要设置所有布局参数,在XML中更清晰易懂。
大多数RecyclerView教程都显示通过XML填充。
View customView = inflater.inflate(...);
ViewHolder viewHolder = new ViewHolder(customView);

这应该是可行的,因为在类链中你有 CustomView > RelativeLayout > ViewGroup > View

LayoutInflater inflater = LayoutInflater.from(context);

就像我之前所说,如果没有要填充的XML文件,您不需要使用此功能。

您也不需要使用context变量。

parent.getContext()是一个很好的解决方案。

// ANYTHING HERE?

是的,你需要将 ViewHolder 与应该包含的数据 "绑定" 起来。

再次强调,大多数教程都会涉及到这一点。


你提到大多数/所有教程都涵盖了使用外部视图类作为“ViewHolder”的视图。 您能否提供一两个链接? 我感觉我看过很多关于“RecyclerView”和“ViewHolder”的教程,但我从未见过这种特定的事情被提及过。 - Alex Peters
@Alex https://guides.codepath.com/android/Using-the-RecyclerView#creating-the-custom-row-layout 和 https://guides.codepath.com/android/Heterogenous-Layouts-inside-RecyclerView。 - OneCricketeer
好的。问题说明需要在RecyclerView内外都使用自定义视图,因此直接填充XML并不是一个真正的选择。尽管如此,你的答案在实例化自定义视图、传递上下文实例以及需要手动设置布局参数方面都非常到位。 - Alex Peters

-2

list_content.xml

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

    <TextView
        android:text="TextView"
        android:layout_width="@dimen/_160sdp"
        android:textSize="@dimen/_14sdp"
        android:layout_gravity="center|bottom"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:paddingTop="@dimen/_10sdp"
        android:id="@+id/name"
         />

    <ImageView
        android:layout_width="@dimen/_20sdp"
        android:layout_gravity="center|right"
        android:layout_height="@dimen/_20sdp"
        app:srcCompat="@drawable/close"
        android:id="@+id/close"
        android:layout_weight="1" />
</LinearLayout>

Adapter.java

public class yourAdapter extends RecyclerView.Adapter<yourAdapter .SimpleViewHolder> {


private Context mContext;
ArrayList<String> mylist;

public yourAdapter (Context context, ArrayList<String> checklist) {
    mContext = context;
    mylist = checklist;
}

@Override
public yourAdapter .SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_content, parent, false);
    return new yourAdapter .SimpleViewHolder(view);
}
@Override
public void onBindViewHolder(final yourAdapter .SimpleViewHolder holder, final int position) {
    holder.name.setText(adap.getName());


}

@Override
public long getItemId(int i) {
    return 0;
}

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

@Override
public int getItemViewType(int i) {
    return 0;
}

public static class SimpleViewHolder extends RecyclerView.ViewHolder {
    TextView name;
    Imageview content;


    public SimpleViewHolder(View itemView) {
        super(itemView);
        name= (TextView) itemView.findViewById(R.id.name);
        close= (Imageview) itemView.findViewById(R.id.close);

    }
}

}

然后在你想要的活动中调用适配器类

https://developer.sonymobile.com/2010/05/20/android-tutorial-making-your-own-3d-list-part-1/


1
注意:我必须使用自定义视图,因为我在不同的适配器下使用此视图(即不仅仅是这个RecyclerView适配器)。 - user7638732

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