使用 RecyclerView 实现通讯录效果

使用 RecyclerView 实现通讯录效果

先附上效果图:
在这里插入图片描述

如图效果所示,实现某个字母下面展示以该字母开头的产品。

看界面效果,基本可以肯定,主 View 应该是 RecyclerView 或者 ListView,需要显示出两种布局,字母的布局,产品的布局,我是使用 RecyclerView 实现的,简单记录一下我的思路。

其中一种看起来可以实现的方法是在 Adapter 中设置两个 ViewType,根据不同的 ViewType 加载不同的布局,这条路看起来是走的通的,而且我们很多开发场景加载多套布局都是在 Adapter 中设置和判断 ViewType 实现的。不过我没有这样做,我参考了网上的思路(稍后附上网络参考链接),觉得他们的方式也很不错,走了第二条路。

第二种实现思路是给 RecyclerView 添加了自定义的 RecyclerView.ItemDecoration,这个类很常见的一个用途是自定义 RecyclerView 的分割线,这个实现方式也是,将字母的布局那里当做了分割线来处理,ok,我们来看实现代码。

public class MoreItemDecoration extends RecyclerView.ItemDecoration{
    private Context context;
    private Paint backgroundPaint;// 画背景的笔
    private Paint textPaint;// 画字母的笔

    private int titleHeight;

    private int textHeight;// 文字高度
    private int textBaseLineOffset;// 文字底部位置
    private int textStartMargin;// 文字的起始 margin

    private ArrayList<IndexMenuChildrenBean> list;// 数据源。 bean 有自己的字母属性
    private String currentParentName = "";// 当前的 item 的产品的字母是什么

    public MoreItemDecoration(Context context, ArrayList<IndexMenuChildrenBean> list) {
        this.context = context;
        this.list = list;
        this.titleHeight = context.getResources().getDimensionPixelSize(R.dimen.item_decoration_title_height);

        backgroundPaint = new Paint();
        backgroundPaint.setColor(context.getResources().getColor(R.color.gray));

        textPaint = new TextPaint();
        textPaint.setColor(context.getResources().getColor(R.color.black));
        textPaint.setTextSize(context.getResources().getDimensionPixelSize(R.dimen.item_decoration_title_fontsize));

        Paint.FontMetrics fm = textPaint.getFontMetrics();
        textHeight = (int) (fm.bottom - fm.top);
        textBaseLineOffset = (int) fm.bottom;
        textStartMargin = context.getResources().getDimensionPixelOffset(R.dimen.item_decoration_title_start_margin);
    }
    // 先执行完全部的 getItemOffsets 方法,再去执行 onDraw 方法
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        // 该方法的目的是找到需要在该 position 的 item 的上方空间写字母的 position,然后修改偏移量
        // 获取 recyclerView 的 item 的 position
        int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition();
	if (position == 0) {
            outRect.set(0, titleHeight, 0, 0);
        } else {
            if (!list.get(position - 1).getParentName().equals(list.get(position).getParentName())) {
                outRect.set(0, titleHeight, 0, 0);
            } else {
                outRect.set(0, 0, 0, 0);
            }
        }
        
        // 判断当前字母是否和 bean 的字母相同 下面方法在数据较多复用 item 时存在 bug
        /*if (!currentParentName.equals(list.get(position).getParentName())) {
            // 不相同,则绘制分割线空隙为设定的高度
            outRect.set(0, titleHeight, 0, 0);
            // 将 bean 的字母值赋予给当前的字母值变量
            currentParentName = list.get(position).getParentName();
        } else {
            outRect.set(0, 0, 0, 0);
        }*/
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        // 该方法的目的是在需要写字母的 item 的上方写上字母
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            int position = params.getViewAdapterPosition();
            if (position == 0) {
                drawText(c, left, right, child, params, position);
            } else {
                if (!list.get(position - 1).getParentName().equals(list.get(position).getParentName())) {
                    // 绘制字母
                    drawText(c, left, right, child, params, position);
                }
            }
            
            // 如果当前的字母值和 bean 的字母值相同,则跳过,不再绘制,比如已经绘制了 A 字母,第二个 bean 的字母属性还是 A,则跳过,不再绘制 A。 下面方法在数据较多时存在问题
            /*if (currentParentName.equals(list.get(position).getParentName())) {
                continue;
            }
            // 绘制字母
            drawText(c, left, right, child, params, position);*/
        }
    }

    private void drawText(Canvas c, int left, int right, View child, RecyclerView.LayoutParams params, int position) {
        int rectBottom = child.getTop() - params.topMargin;
        c.drawRect(left, rectBottom - titleHeight, right, rectBottom, backgroundPaint);
        c.drawText(list.get(position).getParentName(), child.getPaddingLeft() + textStartMargin, rectBottom - (titleHeight - textHeight) / 2 - textBaseLineOffset, textPaint);
        // 之前写到,执行完全部的 getItemOffsets  方法后,才执行 onDraw 方法,所以,这里需要再绘制完字母后,修改当前字母变量
        //currentParentName = list.get(position).getParentName();
    }
}

在 Activity 中调用就十分简单了。

rvMore = (RecyclerView) findViewById(R.id.rv_moreActivity);
        rvMore.setLayoutManager(new LinearLayoutManager(MoreActivity.this));

        adapter = new MoreAdapter(MoreActivity.this);
        rvMore.setAdapter(adapter);
        
ArrayList<IndexMenuChildrenBean> beans = getBeans();
        rvMore.addItemDecoration(new MoreItemDecoration(MoreActivity.this, beans));
        adapter.setList(beans);
        adapter.notifyDataSetChanged();

参考文章链接:https://juejin.im/entry/586f0640128fe100580a9fd4

作者写的很好,他完整的实现了通讯录的一个功能,并且附有 github 地址,大家可以参考学习,不过他的判断 position 的方式,我一直没有看懂,不过这并不重要,根据自己的实际业务选择合适的方式去判断 position 就可以了。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值