自定义控件

Android - RecyclerView 解析

2017-04-27  本文已影响241人  东方未曦

一、RecyclerView 普通列表实现

RecyclerView 可以实现 ListView 的效果,它通过使用 LayoutManager 来确定每一个 item 的排列方式,同时为增加和删除项目提供默认的动画效果。

1. 列表显示

MainActivity:初始化数据并设置适配器

public class MainActivity extends AppCompatActivity {

    private RecyclerView main_recycler_view;
    private List<String> mData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ...
        initData();

        // 设置自定义适配器
        main_recycler_view.setAdapter(new RecyclerAdapter(this, mData));
        // 设置布局管理器
        main_recycler_view.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        // 设置动画
        main_recycler_view.setItemAnimator(new DefaultItemAnimator());
        // 设置分割线
        main_recycler_view.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
    }

    // 初始化数据
    private void initData() {
        mData = new ArrayList<>();
        for (int i = 'A'; i <= 'z'; i++) {
            mData.add("" + (char) i);
        }
    }
}

Item 布局文件

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

    <LinearLayout
        android:layout_width="360dp"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <TextView
            android:id="@+id/item_text_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="XXX"
            android:textSize="24sp"
            android:textColor="@android:color/black"
            android:padding="5dp"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingLeft="5dp"
            android:text="content"
            android:textSize="16sp"
            android:textColor="@android:color/black"/>
    </LinearLayout>

</LinearLayout>

RecyclerAdapter:自定义适配器

// 设置 RecyclerAdapter 继承自 RecyclerView.Adapter<RecyclerAdapter.ViewHolder>
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {

    private Context mContext;
    private List<String> mData;

    // 构造方法,传入上下文和列表数据
    public RecyclerAdapter(Context context, List<String> data) {
        this.mContext = context;
        this.mData = data;
    }

    // 加载 Item 的布局项
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = View.inflate(mContext, R.layout.item_recycler_view, null);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // 获得当前数据对象并设置显示
        String data = mData.get(position);
        holder.item_text_title.setText(data);
    }

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

    // ViewHolder 内部类
    class ViewHolder extends RecyclerView.ViewHolder {

        TextView item_text_title;

        public ViewHolder(View itemView) {
            super(itemView);
            item_text_title = (TextView) itemView.findViewById(R.id.item_text_title);
        }
    }
}

主页面布局以及内容项布局可以按自己的喜好设置。以下是 LayoutManager 进行各种不同设置时的运行结果。

RecyclerView 线性垂直布局.png RecyclerView 线性水平布局.png RecyclerView GridLayoutManager布局.png

2. Item 点击事件

有两种方法可以实现 Item 的点击事件,且都可以传入数据。

第一种,改写 ViewHolder 内部类,为其添加点击事件。

ViewHolder 类实现 OnclickListener 接口,itemView 设置该点击事件。

class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    // 在 bindData() 中传入数据(这里只是 String,一般为自定义数据对象)
    private String mData;
    // 视图对象
    private TextView item_text_title;

    public ViewHolder(View itemView) {
        super(itemView);
        // 为 itemView 设置点击事件
        itemView.setOnClickListener(this);
        item_text_title = (TextView) itemView.findViewById(R.id.item_text_title);
    }

    // 将点击的数据对象传入并设置视图对象的显示
    public void bindData(String data) {
        mData = data;
        item_text_title.setText(mData);
    }

    @Override
    public void onClick(View v) {
        Toast.makeText(mContext, "你点击了" + mData, Toast.LENGTH_SHORT).show();
    }
}

之后在 adapter 的 onBindViewHolder(...) 中调用新添加的 bindData() 方法。

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    holder.bindData(mData.get(position));
}

第二种,添加接口,使外部可以回调内部的点击事件。

首先在自定义的 RecyclerAdapter 中添加一个接口以及对应方法。

private OnMyItemClickListener listener;

interface OnMyItemClickListener {
    void onItemClick(int position);
}

// 用于外部设置 RecyclerAdapter 中的接口,重写其方法。
public void setOnMyItemClickListener(OnMyItemClickListener l) {
    this.listener = l;
}

改写 ViewHolder 内的构造方法。

public ViewHolder(View itemView) {
    super(itemView);
    item_text_title = (TextView) itemView.findViewById(R.id.item_text_title);

    itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (listener != null) {
                listener.onItemClick(getLayoutPosition());
            }
        }
    });
}

在主页面中为 RecyclerAdapter 设置接口,重写 onItemClick() 方法。

adapter.setOnMyItemClickListener(new RecyclerAdapter.OnMyItemClickListener() {
    @Override
    public void onItemClick(int position) {
        Toast.makeText(MainActivity.this, mData.get(position) + " Clicked!", Toast.LENGTH_SHORT).show();
    }
});

3. 添加和删除 Item

在 adapter 中加入两个函数:addItem(position) 以及 removeItem(position)
在函数中,先对数据集合进行操作,再通知适配器数据已经更改。
注:这里使用 notifyItemInserted(position) 与 notifyItemRemoved (position) 通知适配器,而非 ListView 中常用的 adapter.notifyDataSetChanged()

//  添加 Item 项
public void addItem(int position) {
    mData.add(position, "XXX");
    notifyItemInserted(position);
}

// 删除 Item 项
public void removeItem(int position) {
    mData.remove(position);
    notifyItemRemoved(position);
}

设置 ToolBar,加入“添加”以及“删除”两个按钮进行操作,效果如下:


加入ToolBar后的界面.png

因为我们已经在刚开始设置了默认的动画效果:

main_recycler_view.setItemAnimator(new DefaultItemAnimator());

所以添加或者删除 Item 项时会有很不错的效果~


添加 / 删除 Item 时的默认动画效果.gif

二、分类型 RecyclerView 的实现

分类型 RecyclerView 可以通过代码实现界面的多样化,如下图所示,一共三部分:最上方为轮播图片(Banner实现),中间是 GridView,下方是 ViewPager。
实现这样的效果,在总的布局文件中只需要定义一个 <RecyclerView> 即可。

分类型 RecyclerView 示例.png

总 RecyclerView 及其 adapter

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/home_recycle_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

Adapter 代码:

public class HomeFragmentAdapter extends RecyclerView.Adapter {
    // 定义 3 种类型的编码
    private static final int BANNER = 0;
    private static final int CHANNEL = 1;
    private static final int ACT = 2;

    // 当前类型
    private int currentType = BANNER;

    private Context context;
    private JsonResult.ResultBean result; // 联网获得的 json 数据
    private LayoutInflater layoutInflater;

    public HomeFragmentAdapter(Context context, JsonResult.ResultBean result) {
        this.context = context;
        this.result = result;
        layoutInflater = LayoutInflater.from(context);
    }

    //根据 RecyclerView 中的类型返回不同的 ViewHolder
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        if (viewType == BANNER) {
            return new BannerViewHolder(context, layoutInflater.inflate(R.layout.banner_view_pager, null));
        } else if (viewType == CHANNEL) {
            return new ChannelViewHolder(context, layoutInflater.inflate(R.layout.channel_item, null));
        } else if (viewType == ACT) {
            return new ActViewHolder(context, layoutInflater.inflate(R.layout.act_item, null));
        }
        return null;
    }

    // 根据 position 判断 RecyclerView 类型并绑定数据
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (getItemViewType(position) == BANNER) {
            BannerViewHolder bannerViewHolder = (BannerViewHolder) holder;
            bannerViewHolder.setData(result.getBanner_info());
        } else if (getItemViewType(position) == CHANNEL) {
            ChannelViewHolder channelViewHolder = (ChannelViewHolder) holder;
            channelViewHolder.setData(result.getChannel_info());
        } else if (getItemViewType(position) == ACT) {
            ActViewHolder actViewHolder = (ActViewHolder) holder;
            actViewHolder.setData(result.getAct_info());
        }
    }

    // 获取当前 RecyclerView 的类型
    @Override
    public int getItemViewType(int position) {
        switch (position) {
            case BANNER:
                currentType = BANNER;
                break;
            case CHANNEL:
                currentType = CHANNEL;
                break;
            case ACT:
                currentType = ACT;
                break;
        }
        return currentType;
    }

    // 一共有 3 种不同类型的 RecyclerView
    @Override
    public int getItemCount() {
        return 3;
    }
}

1. BannerViewHolder

Banner 是一个第三方库,用于实现 Android 的图片轮播,效果如下所示。
github 地址:Banner

Banner 的轮播和拖动.gif

布局文件:

<?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="match_parent">

    <com.youth.banner.Banner
        android:id="@+id/home_banner"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        app:indicator_height="5dp"
        app:indicator_width="5dp"
        app:is_auto_play="true">
    </com.youth.banner.Banner>

</LinearLayout>

代码:

public class BannerViewHolder extends RecyclerView.ViewHolder {

    private Context mContext;
    private Banner banner;

    public BannerViewHolder(Context context, View itemView) {
        super(itemView);
        this.mContext = context;
        this.banner = (Banner) itemView.findViewById(R.id.home_banner);
    }

    // 设置数据并开启轮播
    public void setData(List<JsonResult.ResultBean.BannerInfoBean> banner_info) {
        List<String> imageUrls = new ArrayList<>();
        for (int i = 0; i < banner_info.size(); i++) {
            imageUrls.add(banner_info.get(i).getImage());
        }
        banner.setImages(imageUrls); // 设置图片 URL 的集合
        banner.setImageLoader(new GlideImageLoader());
        banner.setBannerAnimation(Transformer.Accordion); // 设置动画效果
        banner.start();
    }

    // 图片加载类
    private class GlideImageLoader extends ImageLoader {

        @Override
        public void displayImage(Context context, Object path, ImageView imageView) {
            // 使用 Glide 加载图片
            Glide.with(context).load(path).into(imageView);
        }
    }

}

2. ChannelViewHolder

可以看出,整个频道模块就是一个 numColumns 为 5 的 GridView。
因此在 ChannelViewHolder 中初始化视图对象后要为此 GridView 设置适配器。

频道.png

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="170dp"
    android:background="#fff"
    android:orientation="vertical">

    <GridView
        android:id="@+id/home_gv_channel"
        android:layout_width="wrap_content"
        android:layout_height="150dp"
        android:numColumns="5">
    </GridView>
    
</LinearLayout>

ChannelViewHolder 代码:

public class ChannelViewHolder extends RecyclerView.ViewHolder {

    private Context mContext;
    private GridView home_gv_channel;

    public ChannelViewHolder(Context context, View itemView) {
        super(itemView);
        this.mContext = context;
        home_gv_channel = (GridView) itemView.findViewById(R.id.home_gv_channel);
    }

    public void setData(List<JsonResult.ResultBean.ChannelInfoBean> channel_info) {
        ChannelAdapter adapter = new ChannelAdapter(mContext, channel_info);
        home_gv_channel.setAdapter(adapter);
    }
}

Adapter 代码:

public class ChannelAdapter extends BaseAdapter {

    private Context mContext;
    private List<ChannelInfoBean> channel_info;

    public ChannelAdapter(Context context, List<ChannelInfoBean> channel_info) {
        this.mContext = context;
        this.channel_info = channel_info;
    }

    @Override
    public int getCount() { return channel_info.size(); }

    @Override
    public Object getItem(int position) { return channel_info.get(position); }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder viewHolder;

        if (convertView == null) {
            viewHolder = new ViewHolder();
            convertView = View.inflate(mContext, R.layout.item_channel, null);
            viewHolder.iv_channel = (ImageView) convertView.findViewById(R.id.iv_channel);
            viewHolder.tv_channel = (TextView) convertView.findViewById(R.id.tv_channel);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        ChannelInfoBean data = channel_info.get(position);
        Glide.with(mContext).load(data.getImage()).into(viewHolder.iv_channel);
        viewHolder.tv_channel.setText(data.getChannel_name());

        return convertView;
    }

    private static class ViewHolder {
        ImageView iv_channel;
        TextView tv_channel;
    }
}
上一篇 下一篇

猜你喜欢

热点阅读