Android Recyclerview多布局使用技巧

本文详细介绍了在Android中使用RecyclerView实现多布局的方法,包括继承RecyclerView.Adapter、使用BRVAH Adapter辅助类,以及通过基本的隐藏显示来创建仿微信朋友列表。文中还提到了SparseArray的优化作用,.9图的制作教程,以及Grid复杂布局的实现。并提供了相关开源库和项目的参考链接。

该文章主要记录,Rv中的多布局使用。

目录

1. 继承自RecyclerView.Adapter实现聊天多布局

 SparseArray的学习及使用

2. 使用BRVAH Adapter帮助类实现多布局 

1. 继承自RecyclerView.Adapter实现聊天多布局

实现该类多布局,主要是重写了getItemViewType,然后才是根据不同的数据类型去返回各自相对应的Item子布局。

而在onCreateViewHolder()中才是加载对应的itemType转换为对应的view即可。

 

/**
 * @author crazyZhangxl on 2019/1/15.
 * Describe: 多布局实现聊天通信 纯UI
 */
public class SessionAdapter extends RecyclerView.Adapter<OneViewHolder>{
    private Context mContext;
    private List<Message> mMessagesList;
    private static final int SEND_TEXT = R.layout.item_text_send;
    private static final int RECEIVE_TEXT = R.layout.item_text_receive;
    private static final int UNDEFINE_MSG = R.layout.item_no_support_msg_type;

    /**
     * 构造函数----
     * @param context
     * @param messagesList
     */
    public SessionAdapter(Context context, List<Message> messagesList) {
        mContext = context;
        mMessagesList = messagesList;
    }

    /**
     * 创建ViewHolder
     * @param viewGroup 父亲组件
     * @param viewType  文本布局资源文件[对应getItemViewType返回的类型]
     * @return
     */
    @NonNull
    @Override
    public OneViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
        return new OneViewHolder(LayoutInflater.from(mContext).inflate(viewType, viewGroup,false), mContext);
    }

    /**
     * 绑定ViewHolder设置初始化
     * @param viewHolder
     * @param position
     */
    @Override
    public void onBindViewHolder(@NonNull OneViewHolder viewHolder, int position) {
        // 设置时间
        setTime(viewHolder,position);
        setName(viewHolder,position);
        setContent(viewHolder,position);
    }

    private void setName(OneViewHolder viewHolder, int position) {
        TextView tvName =  viewHolder.mConvertView.findViewById(R.id.tvName);
        tvName.setText(mMessagesList.get(position).getSendName());
    }

    private void setContent(OneViewHolder viewHolder, int position) {
        Message message = mMessagesList.get(position);
        if (message instanceof TextMessage){
            TextView tvMessage =  viewHolder.mConvertView.findViewById(R.id.tvText);
            TextMessage textMessage = (TextMessage) mMessagesList.get(position);
            tvMessage.setText(textMessage.getTextInfo());
        }
    }

    private void setTime(OneViewHolder viewHolder, int position) {
        TextView tvTime = viewHolder.mConvertView.findViewById(R.id.tvTime);
        Message message = mMessagesList.get(position);
        long sendTime = message.getSendTime();
        if (position > 0){
            long preTime = mMessagesList.get(position - 1).getSendTime();
            if (sendTime - preTime > 5*60*1000){
                tvTime.setVisibility(View.VISIBLE);
                tvTime.setText(TimeUtils.getInstance().longToTime(sendTime));
            }else {
                tvTime.setVisibility(View.GONE);
            }
        }else {
            tvTime.setVisibility(View.VISIBLE);
            tvTime.setText(TimeUtils.getInstance().longToTime(sendTime));
        }
    }

    /**
     * 对应各种消息类型[多布局的核心]
     * @param position 对应的下标position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        int viewType = UNDEFINE_MSG;
        Message message = mMessagesList.get(position);
        boolean isSend = message.getDirection() == MessageDirection.SEND.integerValue;
        switch (message.getMessageType()){
            case TEXT:
                viewType = isSend?SEND_TEXT:RECEIVE_TEXT;
                break;
            case IMAGE:
                break;
                default:
                    break;
        }
        return viewType;
    }

    @Override
    public int getItemCount() {
        if (mMessagesList == null){
            return 0;
        }
        return mMessagesList.size();
    }
}
/**
 * @author crazyZhangxl on 2019/1/15.
 * Describe: 基本的ViewHolder
 */
public class OneViewHolder extends RecyclerView.ViewHolder{
    protected View mConvertView;
    private Context mContext;

    public OneViewHolder(@NonNull View itemView) {
        super(itemView);
    }

    public OneViewHolder(@NonNull View itemView, Context context) {
        super(itemView);
        mConvertView = itemView;
        mContext = context;
    }

    public View getConvertView() {
        return mConvertView;
    }

    public void setConvertView(View convertView) {
        mConvertView = convertView;
    }

    public Context getContext() {
        return mContext;
    }

    public void setContext(Context context) {
        mContext = context;
    }
}

拓展内容

可以学习下优秀的人封装的ViewHolder,特点是对View的操作更加简洁了,而且封装了item的各种操作事件处理。还有一个知识点是 SparseArray<Views>的学习以及使用,优化的缓存方案。

public class LQRViewHolder extends RecyclerView.ViewHolder {

    protected Context mContext;
    protected View mConvertView;
    protected SparseArray<View> mViews;
    protected int mMyPosition;//LQRViewHolderForAbsListView专用(另一个自带getPosition方法)

    protected OnItemClickListener mOnItemClickListener;
    protected OnItemLongClickListener mOnItemLongClickListener;
    protected OnItemTouchListener mOnItemTouchListener;

    public LQRViewHolder(View itemView) {
        super(itemView);
    }

    /**
     * 根据id得到布局中的View(使用SparseArray保管,提高效率)
     */
    public <T extends View> T getView(int viewId) {
        View view = mViews.get(viewId);
        if (view == null) {
            view = mConvertView.findViewById(viewId);
            mViews.put(viewId, view);
        }
        return (T) view;
    }


    /**
     * 得到当前item对应的View
     */
    public View getConvertView() {
        return mConvertView;
    }

    public int getMyPosition() {
        return mMyPosition;
    }

    public void setMyPosition(int myPosition) {
        mMyPosition = myPosition;
    }

    public OnItemClickListener getOnItemClickListener() {
        return mOnItemClickListener;
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
    }

    public OnItemLongClickListener getOnItemLongClickListener() {
        return mOnItemLongClickListener;
    }

    public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
        mOnItemLongClickListener = onItemLongClickListener;
    }

    public OnItemTouchListener getOnItemTouchListener() {
        return mOnItemTouchListener;
    }

    public void setOnItemTouchListener(OnItemTouchListener onItemTouchListener) {
        mOnItemTouchListener = onItemTouchListener;
    }

    /*================== 一切有可能的操作控件的方法 begin ==================*/

    /**
     * 设置TextView文字,并返回this
     */
    public LQRViewHolder setText(int viewId, String text) {
        TextView tv = getView(viewId);
        tv.setText(text);
        return this;
    }

    /**
     * 设置TextView的文字颜色,并返回this
     */
    public LQRViewHolder setTextColor(int viewId, int colorId) {
        TextView tv = getView(viewId);
        tv.setTextColor(mContext.getResources().getColor(colorId));
        return this;
    }

    /**
     * 设置ImageView的图片,并返回this
     */
    public LQRViewHolder setImageResource(int viewId, int resId) {
        ImageView iv = getView(viewId);
        iv.setImageResource(resId);
        return this;
    }

    /**
     * 设置ImageView的图片,并返回this
     */
    public LQRViewHolder setImageBitmap(int viewId, Bitmap bitmap) {
        ImageView iv = getView(viewId);
        iv.setImageBitmap(bitmap);
        return this;
    }

    /**
     * 设置ImageView的图片,并返回this
     */
    public LQRViewHolder setImageFileResource(int viewId, String path) {
        ImageView iv = getView(viewId);
        Bitmap bitmap = BitmapFactory.decodeFile(path);
        iv.setImageBitmap(bitmap);
        return this;
    }

    /**
     * 设置背景颜色,并返回this
     */
    public LQRViewHolder setBackgroundColor(int viewId, int colorId) {
        View view = getView(viewId);
        view.setBackgroundColor(mContext.getResources().getColor(colorId));
        return this;
    }


    /**
     * 设置背景资源,并返回this
     */
    public LQRViewHolder setBackgrounResource(int viewId, int resId) {
        View view = getView(viewId);
        view.setBackgroundResource(resId);
        return this;
    }

    /**
     * 设置显隐,并返回this
     */
    public LQRViewHolder setViewVisibility(int viewId, int visibility) {
        View view = getView(viewId);
        view.setVisibility(visibility);
        return this;
    }

    /**
     * 设置是否可用,并返回this
     */
    public LQRViewHolder setEnabled(int viewId, boolean enabled) {
        View view = getView(viewId);
        view.setEnabled(enabled);
        return this;
    }

    /**
     * 设置是否可获取焦点,并返回this
     */
    public LQRViewHolder setFocusable(int viewId, boolean focusable) {
        View view = getView(viewId);
        view.setFocusable(focusable);
        return this;
    }

    /*================== 一切有可能操作控件的方法 end ==================*/

}

public class LQRViewHolderForRecyclerView extends LQRViewHolder {

    public LQRViewHolderForRecyclerView(Context context, View itemView) {
        super(itemView);
        mContext = context;
        mConvertView = itemView;
        mViews = new SparseArray<>();

        mConvertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnItemClickListener != null) {
                    mOnItemClickListener.onItemClick(LQRViewHolderForRecyclerView.this, null, v, getPosition());
                }
            }
        });

        mConvertView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if (mOnItemLongClickListener != null) {
                    return mOnItemLongClickListener.onItemLongClick(LQRViewHolderForRecyclerView.this, null, v, getPosition());
                }
                return false;
            }
        });

        mConvertView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (mOnItemTouchListener != null) {
                    return mOnItemTouchListener.onItemTouch(LQRViewHolderForRecyclerView.this, v, event, getPosition());
                }
                return false;
            }
        });

    }
}

 SparseArray的学习及使用

 用途:当数据结构中key的类型是int时可用此结构来替代hashmap来进行优化

 特点:查找操作时使用的是二分法查找,时间复杂度时O(N);存储采用的时数组,占用的内存空间小

 HashMap是数组和链表的结合体,被称为链表散列.

 SparseArray是单纯数组的结合.被称为稀疏数组,对数据保存的时候,不会有额外的开销

 获取以及设置的使用方式

    /**
     * 根据id得到布局中的View(使用SparseArray保管,提高效率)
     */
    public <T extends View> T getView(int viewId) {
        View view = mViews.get(viewId);
        if (view == null) {
            view = mConvertView.findViewById(viewId);
            mViews.put(viewId, view);
        }
        return (T) view;
    }

Android学习笔记之性能优化SparseArray

Android常见优化方式-SparseArray

Android studio中.9图片的含义及制作教程

.9图的简单制作,右边3图效果依次为: 上下拉伸/左右拉伸/上下左右都拉伸的效果展示

2. 使用BRVAH Adapter帮助类实现多布局 

    private void initData() {
        mMulCityBeanList.add(new MulCityBean("101","南京","12","晴"));
        mMulCityBeanList.add(new MulCityBean("101","北京","15","阴"));
        mMulCityBeanList.add(new MulCityBean("101","天津","18","小雨"));
        mMulCityBeanList.add(new MulCityBean());
    }

    private void initAdapter() {
        mCityRecyclerView.setLayoutManager(new GridLayoutManager(this,3));
        mMulCityAdapter = new MulCityAdapter(mMulCityBeanList,this);
        // 设置adapter
        mCityRecyclerView.setAdapter(mMulCityAdapter);
    }

核心数据bean,必须得实现MultiItemEntity

public class MulCityBean implements MultiItemEntity{
    /**
     * 具体城市信息
     */
    public static final int CITY = 1;
    /**
     * 添加的按钮
     */
    public static final int ADD_SYMBOL = 2;

    /**
     * 用于返回给itemType的数值
     * 在构造方法中可进行初始化
     */
    private int itemType;

    // 对应的每个Item的具体数值
    public String cityId;
    // 就是江宁
    public String cityName;
    public String temp;
    public String weatherStatus;
    /**
     * 是否显示删除的按钮
     */
    private boolean isShowDelete;
    /**
     * 是否显示本地的城市
     */
    private boolean isNowCity;


    private boolean isChecked;

    public boolean isChecked() {
        return isChecked;
    }

    public void setChecked(boolean checked) {
        isChecked = checked;
    }

    public boolean isShowDelete() {
        return isShowDelete;
    }

    public void setShowDelete(boolean showDelete) {
        isShowDelete = showDelete;
    }

    public boolean isNowCity() {
        return isNowCity;
    }

    public void setNowCity(boolean nowCity) {
        isNowCity = nowCity;
    }

    /**
     * 无参数构造方法那么默认该bean就是添加按钮
     */
    public MulCityBean() {
        itemType = ADD_SYMBOL;
    }

    /**
     * 多惨构造方法即是城市的信息
     * @param cityId
     * @param cityName
     * @param temp
     * @param weatherStatus
     */
    public MulCityBean(String cityId, String cityName, String temp, String weatherStatus) {
        this.cityId = cityId;
        this.cityName = cityName;
        this.temp = temp;
        this.weatherStatus = weatherStatus;
        itemType = CITY;
    }

    @Override
    public int getItemType() {
        return itemType;
    }
}

adapter实现BaseMultiItemQuickAdapter 

public class MulCityAdapter extends BaseMultiItemQuickAdapter<MulCityBean,BaseViewHolder>{
    private Context context;
    public static final int[] BLUR_IMAGE = {
            R.mipmap.ic_city_blur0,
            R.mipmap.ic_city_blur1,
            R.mipmap.ic_city_blur2,
            R.mipmap.ic_city_blur3,
            R.mipmap.ic_city_blur4,
            R.mipmap.ic_city_blur5};
    private Drawable mDrawableLocation;
    private ImageView mImageView;
    private TextView mTv_status;
    private ImageView mView_del;
    private View mView_hover;
    private TextView mText_city_name;
    private ImageView mMChecked;

    /**
     * Same as QuickAdapter#QuickAdapter(Context,int) but with
     * some initialization data.
     *
     * @param data A new list is created out of this one to avoid mutable list
     */
    public MulCityAdapter(List<MulCityBean> data,Context context) {
        super(data);
        // 对应类型绑定不同的布局
        addItemType(MulCityBean.CITY, R.layout.item_city_followed_city);
        addItemType(MulCityBean.ADD_SYMBOL, R.layout.item_city_add_city);
        this.context = context;
        initDrawable();
    }

    /**
     * 初始化Drawable-----
     */
    private void initDrawable() {
        mDrawableLocation = ContextCompat.getDrawable(context,R.mipmap.ic_location);
        mDrawableLocation.setBounds(0,0,dpToPx(context,14),dpToPx(context,14));
    }

    @Override
    protected void convert(BaseViewHolder helper, MulCityBean item) {
        switch (helper.getItemViewType()){
            case MulCityBean.CITY:
                if (item == null){
                    return;
                }
                mImageView = helper.getView(R.id.image);
                mImageView.setImageResource(BLUR_IMAGE[helper.getAdapterPosition()% BLUR_IMAGE.length]);
                helper.setText(R.id.city_temp,item.temp);
                mText_city_name = helper.getView(R.id.city_name);
                mText_city_name.setText(item.cityName);

                mTv_status = helper.getView(R.id.city_status);
                mTv_status.setText(item.weatherStatus);
                mView_del = helper.getView(R.id.delete);
                helper.addOnClickListener(R.id.delete);
                // 设置删除按钮的显示
                mView_del.setVisibility(item.isShowDelete()?View.VISIBLE:View.GONE);

                // 设备阴影的显示
                mView_hover = helper.getView(R.id.hover);
                mView_hover.setVisibility(item.isShowDelete()?View.VISIBLE : View.GONE);

                // 是否是当前
                mMChecked = helper.getView(R.id.checked);
                mMChecked.setVisibility(item.isChecked()?View.VISIBLE:View.GONE);
                break;
                case MulCityBean.ADD_SYMBOL:
                    break;
                    default:
                        break;
        }
    }

    private int dpToPx(Context context,int dp){
        if (context == null){
            return  0;
        }

        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        return (int) (displayMetrics.density*(dp+0.5f));
    }
}

3. 通过基本的隐藏显示实现仿微信朋友列表

不需要多布局,设置要素隐藏显示即可实现特定的效果。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:background="#fff"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/tvIndex"
        android:layout_width="match_parent"
        android:layout_height="23dp"
        android:background="#E5E5E5"
        android:gravity="center_vertical"
        android:paddingLeft="12.5dp"
        android:text="A"
        android:textColor="#989898"
        android:textSize="12.5dp"
        android:visibility="gone"/>
    <LinearLayout
        android:paddingLeft="12.5dp"
        android:paddingRight="12.5dp"
        android:paddingTop="7.5dp"
        android:paddingBottom="7.5dp"
        android:layout_width="match_parent"
        android:layout_height="50dp">
        <ImageView
            android:src="@mipmap/ic_launcher"
            android:layout_width="30dp"
            android:layout_height="30dp" />
        <RelativeLayout
            android:layout_marginLeft="15dp"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:id="@+id/tvUserName"
                android:layout_centerVertical="true"
                android:text="我是谁"
                android:textColor="#000"
                android:textSize="14sp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
            <View
                android:id="@+id/line"
                android:background="#D2D2D2"
                android:layout_alignParentBottom="true"
                android:layout_width="match_parent"
                android:layout_height="0.5dp"/>
        </RelativeLayout>
    </LinearLayout>
</LinearLayout>

    @Override
    protected void convert(BaseViewHolder helper, FriendBean item) {
        helper.setText(R.id.tvUserName,item.getName());
        int position = helper.getAdapterPosition();
        String nowSpelling = item.getNameSpelling();
        TextView tvIndex = helper.getView(R.id.tvIndex);
        View viewLine = helper.getView(R.id.line);
        String index = "";
        if (position == 0){
            index = nowSpelling;
        }else {
            String beforeSpelling = mFriendBeanList.get(position - 1).getNameSpelling();
            if (!beforeSpelling.equals(nowSpelling)){
                index = nowSpelling;
            }
        }

        int nextIndex = position + 1;
        if (nextIndex < mFriendBeanList.size() - 1) {
            //得到下一个字母
            String nextLetter = mFriendBeanList.get(nextIndex).getNameSpelling();
            //如果和下一个字母的首字母不同则隐藏下划线
            if (!nextLetter.equalsIgnoreCase(nowSpelling)) {
               viewLine.setVisibility(View.INVISIBLE);
            } else {
                viewLine.setVisibility(View.VISIBLE);

            }
        } else {
            viewLine.setVisibility(View.VISIBLE);
        }
        if (position == mFriendBeanList.size() - 1) {
            viewLine.setVisibility(View.INVISIBLE);
        }


        if (TextUtils.isEmpty(index)){
            tvIndex.setVisibility(View.GONE);
        }else {
            tvIndex.setText(index);
            tvIndex.setVisibility(View.VISIBLE);
        }
    }

4. Grid复杂布局实现

      如上三点我们针对的都是单条目LinearLayoutManager,这一条我们针对的是GridLayoutManager中分3行 1行 2行显示的情况。主要实现的是 onAttachedToRecyclerView 里面有设置一个item占几行的属性。

    // size / x = 目标
    // 假如目标 = 3 / 那么 x = 2
    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager){
            GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int i) {
                    int itemViewType = getItemViewType(i);
                    if (LEFT == itemViewType){
                        return 2;
                    }else if (CENTER == itemViewType){
                        return 6;
                    }else if (RIGHT == itemViewType){
                        return 3;
                    }
                    return 6;
                }
            });
        }
    }

拓展效果:

参考资料:

RecyclerView 实现复杂布局 

RecyclerView的那些开源炫酷的LayoutManager

那些酷炫的 RecyclerView 开源库整理

项目地址

RecyclerView案例整理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值