分组悬停列表-2:复杂的xml

Tags
#Android
这是分组悬停列表效果的第二发,在第一发里面,我们已经利用RecyclerView.ItemDecoration实现了分组悬停列表的简单Text。第二发里面,主要是实现分组悬停列表的复杂xml

一、效果图

notion image
notion image
之前我们使用onDraw(),onDrawOver(),都是用canvas的方法活生生的绘制出一个View,这对于很多人(包括我)来说都并不容易,xy坐标的确认,尺寸都较难把握,基本上调UI效果时间都很长。
尤其是canvas.drawText()方法的y坐标,特别是baseLine的位置,不了解的童鞋肯定要踩很多坑。
如果当我们想要绘制的分类title、悬停头部复杂一点时,我都不敢想象要调试多久了,这个时候我们还敢用ItemDecoration吗?
有没有一种方法,就像我们平时使用的那样,在Layout布局xml里画好View,然后inflate出来就可以了呢?
因为ItemDecoration并不是一个View,没法直接addView,那么怎么才能添加一个xml画好的View呢?
最后,看到了别人的解决方案:直接调用这个view的measure、layout、draw方法,将它绘制出来即可。

二、实现代码

  1. ItemDecoration会依次调用getItemOffsets、onDraw、onDrawOver
  1. 在第一次调用getItemOffsets时,加载xml,执行measure、layout,并创建viewHolder,避免重复的inflate和findViewById
  1. 在实际绘制时,加了一个dispatchDraw,根据ShowType,显示Text或XML
  1. 在显示XML时,通过接口回调,调用了tagDisplayer.showData(viewholder,item)
  1. 在决定view的显示位置时,普通的layout、offsetTopAndBottom等方式貌似无效,最后我只好通过canvas的位移来决定view的显示位置
/**
 * 分组悬停视图
 * <p>
 * 1、显示简单Text
 * 2、显示复杂的xml
 * <p>
 * 当设置了xml时,简单的Text会失效
 * 作者:余天然 on 2016/12/21 下午6:51
 */
public class PinnedDivider<T extends Pinnable> extends RecyclerView.ItemDecoration {

    private Paint paint;//画笔
    private Rect rect = new Rect();//用于存放测量文字Rect
    private Drawable divider;//分割线颜色

    private Builder<T> builder;
    private BaseViewHolder viewHolder;

    private ShowType type;//1-简单Text,2-复杂xml

    private PinnedDivider(Builder builder) {
        this.builder = builder;
        if (builder.displayer != null) {
            type = ShowType.XML;
        } else {
            type = ShowType.TEXT;
        }
        this.paint = new Paint();
        this.paint.setTextSize(builder.tagTextSize);
        this.paint.setAntiAlias(true);
        this.divider = new ColorDrawable(builder.dividerColor);
    }

    /**
     * 设置分组悬停视图的显示区域
     */
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        if (type == ShowType.XML && viewHolder == null) {
            viewHolder = createViewHolder(parent);
        }

        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        int position = params.getViewLayoutPosition() - builder.headerCount;

        //防止越界
        if (position > builder.data.size() - 1 || position < 0) {
            return;
        }
        //第1项肯定要有tag
        if (position == 0) {
            outRect.set(0, builder.tagHeight, 0, 0);
        }
        //其余项,不为空且跟前一个tag不一样了,说明是新的分类,也要tag
        else if (!builder.data.get(position).getPinnedTag().equals(builder.data.get(position - 1).getPinnedTag())) {
            outRect.set(0, builder.tagHeight, 0, 0);
        }
        //和下一项一样的,都需要分割线
        for (int i = 0; i < builder.data.size() - 1; i++) {
            String tag1 = builder.data.get(i).getPinnedTag();
            String tag2 = builder.data.get(i + 1).getPinnedTag();
            if (tag1.equals(tag2)) {
         

三、使用方式

  1. 调用pinnedDivider.tagDisplayer(),将上面的displayer传入即可。
public class MainActivity extends AppCompatActivity {

    private SingleAdapter<Bean> adapter;
    private RecyclerView rv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
    }

    private void initView() {
        rv = (RecyclerView) findViewById(R.id.rv);
        rv.setLayoutManager(new LinearLayoutManager(this));
        adapter = new SingleAdapter<Bean>(this, R.layout.item_tv) {
            @Override
            protected void bindData(BaseViewHolder holder, Bean item, int position) {
                TextView tv = holder.getView(R.id.tv);
                tv.setText(item.getCity());
            }
        };
        rv.setAdapter(adapter);
    }

    private void initData() {
        List<Bean> list = new ArrayList<>();
        list.add(new Bean("A", "安达"));
        list.add(new Bean("A", "安化"));
        list.add(new Bean("A", "安康"));
        list.add(new Bean("A", "安陆"));

        list.add(new Bean("B", "包头"));
        list.add(new Bean("B", "保山"));
        list.add(new Bean("B", "宝兴"));
        list.add(new Bean("B", "北京"));
        list.add(new Bean("B", "本溪"));
        list.add(new Bean("B", "宾阳"));

        list.add(new Bean("C", "茶陵"));
        list.add(new Bean("C", "朝阳"));
        list.add(new Bean("C", "昌黎"));
        list.add(new Bean("C", "常德"));
        list.add(new Bean("C", "常州"));
        list.add(new Bean("C", "郴州"));
        list.add(new Bean("C", "成都"));
        list.add(new Bean("C", "承德"));
        list.add(new Bean("C", "赤壁"));
        list.add(new Bean("C", "崇阳"));
        list.add(new Bean("C", "滁州"));
        list.add(new Bean("C", "长春"));
        list.add(new Bean("C", "长春"));
        list.add(new Bean("C", "长春"));
        list.add(new Bean("C", "长春"));
        list.add(new Bean("C", "长春"));
        list.add(new Bean("C", "长春"));

        PinnedDivider.TagDisplayer<Bean> displayer = 
最后奉上源码:Github

参考目录

  1. Android 仿微信通讯录 导航分组列表
  1. 深入理解 RecyclerView 系列之一:ItemDecoration
  1. 深入浅出 RecyclerView – 张涛

© fishyer 2022