RecyclerView.Adapter用于多种数据类型

时间:2018-07-21 21:28:05

标签: java android android-recyclerview recycler-adapter

我正在尝试在我的应用程序中实现搜索功能,用户可以在其中搜索不同类型的数据:曲目,专辑或艺术家。用户在活动A中选择要搜索的数据类型,然后在活动B中执行实际搜索。

活动A内:

    Intent intent = new Intent(this, PostSearch.class);
    intent.putExtra(PostSearch.EXTRA_POST_TYPE, postType);

    startActivityForResult(intent, RC_NEW_POST);

EXTRA_POST_TYPE指定将执行的搜索类型。然后在活动B中提取了这些额外资源。我已经为每种数据类型定义了类:TrackAlbumArtist。在活动B中,我有一个由适配器支持的RecyclerView。我希望为每种数据类型使用相同的适配器定义。但是,我不确定是否可行。

我对适配器的定义如下(为简便起见,简称:)

public class PostSearchAdapter extends RecyclerView.Adapter<PostSearchAdapter.PostSearchViewHolder> {

    private Context mContext;
    private int mSearchType;
    private List<?> mSearchResults;

    public PostSearchAdapter(Context context, int searchType) {
        this.mContext = context;
        mSearchType = searchType;

        switch (mSearchType) {
            case 0:
                mSearchResults = new ArrayList<Track>();
                break;
            case 1:
                mSearchResults = new ArrayList<Album>();
                break;
            case 2:
                mSearchResults = new ArrayList<Album>();
                break;
            default:
                Log.i(TAG, "Invalid search type for adapter. Uh oh...");
                break;
        }
    }

    @Override
    public int getItemCount() {
        if (mSearchResults == null) {
            return 0;
        } else {
            return mSearchResults.size();
        }
    }

    @NonNull
    @Override
    public PostSearchViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View searchResult = LayoutInflater
                                .from(parent.getContext())
                                .inflate(R.layout.list_item_search_result, parent, false);

        return new PostSearchViewHolder(searchResult);
    }

    @Override
    public void onBindViewHolder(PostSearchViewHolder viewHolder, int position) {
        switch (mSearchType) {
            case 0:
                Track track = (Track) mSearchResults.get(position);

                viewHolder.mTitle.setText(track.getTitle());
                viewHolder.mArtist.setText(track.getArtistNames());
                if (!TextUtils.isEmpty(track.getAlbum().getLargeAlbumCover())) {
                    Picasso.get()
                            .load(track.getAlbum().getLargeAlbumCover())
                            .error(R.drawable.ic_no_cover)
                            .into(viewHolder.mCover);
                }
                break;

            case 1:
                // Handle album results here
                break;

            case 2:
                // Handle artist results here
                break;

            default:
                break;
        }

    }

    public void addData(List<?> newData) {
        mSearchResults.add(newData);
        notifyDataSetChanged();
    }

    public void clearData(){
        mSearchResults.clear();
    }    
}

我以这种方式定义了适配器,因为当我在活动B中实例化适配器时,我将从EXTRA_POST_TYPE中知道要处理的数据类型,并且可以执行以下操作:

mAdapter = new PostSearchAdapter(this, mSearchType);

...其中mSearchType具有EXTRA_POST_TYPE存储的值。但是,我的问题现在必须在适配器中处理mSearchResults。我已经使用Java ?通配符类型定义了这个成员变量:

private List<?> mSearchResults;

我这样做是因为在实例化适配器之前,我不知道列表将包含哪种类型的数据。然后,我在适配器的重写构造函数中实例化mSearchResults,因为我现在基于mSearchType的值知道要放入数组的数据类型:

public PostSearchAdapter(Context context, int searchType) {
    this.mContext = context;
    mSearchType = searchType;

    switch (mSearchType) {
        //Instantiate mSearchResults here
    }
}

我面临着两个大问题。首先是我仍然必须为onBindViewHolder()中的列表中存储的对象强制转换:

Track track = (Track) mSearchResults.get(position);

我知道这是一个问题,因为使用参数化数据类型的主要优点之一是,我们不再需要像上面那样进行转换。其次,我仍然在addData()中遇到编译时错误。 Android Studio抱怨捕获和通配符有一些问题:

enter image description here

我不完全理解错误,这使我陷入了停顿。

我应该提一下,我对Java中的泛型没有最全面的了解,但是我觉得我试图解决的问题可以通过使用它们来解决。任何对此的见解将不胜感激!

2 个答案:

答案 0 :(得分:2)

您没有正确使用泛型。

您的TrackAlbum共享超类或任何接口吗?

如果不创建接口并将其添加到两个类中,否则请继续使用您的超类:

public interface ISearchResult{ }

然后将您的适配器更改为abstract类,并为ViewHolder添加适当的类型声明和模板:

public abstract class PostSearchAdapter<T extends ISearchResult> extends RecyclerView.Adapter<PostSearchAdapter.PostSearchViewHolder> {
    // note change from private to protected
    protected final Context mContext;
    protected final ArrayList<T> mSearchResults = new ArrayList<T>();

    // constructor no longer handles weird type variable
    public PostSearchAdapter(Context context) {
        this.mContext = context;
    }

    // oncreate viewholder is removed on purpose

    @Override
    public void onBindViewHolder(PostSearchAdapter.PostSearchViewHolder viewHolder, int position) {
        viewHolder.bind(mSearchResults.get(position));
    }

    public void addData(List<T> newData) {
        mSearchResults.addAll(newData);
        notifyDataSetChanged();
    }

    public void clearData(){
        mSearchResults.clear();
    }

    @Override
    public int getItemCount() {
        return mSearchResults.size();  // this is never null
    }

    // ViewHolder template
    protected abstract class PostSearchViewHolder extends RecyclerView.ViewHolder{
        protected PostSearchViewHolder(View itemView) {
            super(itemView);
            // this can have some or all views that are shared between search results
        }

        protected abstract void bind(T data);
    }
}

这涉及基本方法。现在,您需要为每种搜索结果类型创建此适配器的子类(请注意,extends中使用了type参数):

public class TrackAdapter extends PostSearchAdapter<Track> {
    public TrackAdapter(Context context) {
        super(context);
    }

    @NonNull
    @Override
    public PostSearchViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // if all types share layout You can create helper method in superclass to inflate it
        View searchResult = LayoutInflater
            .from(parent.getContext())
            .inflate(R.layout.list_item_search_result, parent, false);

        return new TrackViewHolder(searchResult);
    }

    protected class TrackViewHolder extends PostSearchViewHolder{
        protected TrackViewHolder(View itemView) {
            super(itemView);
            // store views not included in superclass in fields
        }

        @Override
        protected void bind(Track data) {
            // bind views to Track data...
        }
    }
}

现在只剩下创造力了。可以使用简单的静态工厂方法代替它,例如,将其添加到PostSearchAdapter类中:

public static PostSearchAdapter create(Context context, int resultType){
    switch (resultType){
        case 0:
            return new TrackAdapter(context);
        case 1:
            return new AlbumAdapter(context);
        default:
            throw new IllegalArgumentException("Invalid resultType:"+resultType);
    }
}

这使您可以通过简单的调用获得正确类型的适配器:

mAdapter = PostSearchAdapter.create(this, mSearchType);

答案 1 :(得分:0)

编辑

甚至不能将元素1加1也行。 我附加的2个链接中已详细说明了原因,请查看。这也不起作用:

List<?> list = new ArrayList<>();
List<?> list2 = new ArrayList<>();
list.add(list2.get(0)); // I thought for-each loop would work, this proves that it wouldn't

因为类型可能不同。甚至无法从同一列表中获取元素:

List<?> list = new ArrayList<>();
list.add(list.get(0)); // Same compile error

我认为使用addAll方法是可行的。但是它显示了相同的错误。

这可以编译,但是如果可以正常工作,那就不知道了:

List list = new ArrayList<>();
List<?> list2 = new ArrayList<>();
list.addAll(list2);

深入研究后,我发现thisthis可以帮助我们更好地理解。