用recyclerView实现多级分类悬浮标题滑动顶出效果

效果图镇楼,请忽略这么丑的样式,我只是个小demo

1339969-7473fc3b5e7eef0d.gif
demo.gif

在ios上经常看到这样的效果,标签悬浮,到下一个的时候会缓慢把上一个标签顶出去,然后自己悬浮在页面顶部。抽空自己写了个demo。

思路:

  • 1,大列表用recyclerView做,定义两种type的item,一种带letter,一般用在小分类的第一个item;另一种是普通item,只展示具体内容。上代码
private static class MyAdapter extends RecyclerView.Adapter<MyHolder> {
    private final ArrayList<Pair<Character, String>> strings;
    private final int HEADER = 1,NORMAL = 0;

    public MyAdapter(ArrayList<Pair<Character, String>> strings) {this.strings = strings;}

    @Override
    public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == HEADER){
            LinearLayout linearLayout = new LinearLayout(parent.getContext());
            linearLayout.setOrientation(LinearLayout.VERTICAL);
            linearLayout.setLayoutParams(new ViewGroup.LayoutParams(-1,-2));
            TextView letter = new TextView(parent.getContext());
            letter.setBackgroundColor(0xffff3434);
            letter.setTextColor(0xff333333);
            letter.setTextSize(16);
            letter.setGravity(Gravity.CENTER_VERTICAL);
            letter.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
            letter.setPadding(dp2px(15), 0, 0, 0);
            linearLayout.addView(letter,new LinearLayout.LayoutParams(-1, dp2px(35)));

            TextView textView = new TextView(parent.getContext());
            textView.setGravity(Gravity.CENTER_VERTICAL);
            textView.setBackgroundColor(Color.WHITE);
            textView.setTextColor(0xff333333);
            textView.setTextSize(15);
            textView.setPadding(dp2px(15), 0, 0, 0);
            linearLayout.addView(textView,new ViewGroup.LayoutParams(-1, dp2px(50)));
            return new MyHolder(linearLayout);
        }else {
            TextView textView = new TextView(parent.getContext());
            textView.setGravity(Gravity.CENTER_VERTICAL);
            textView.setBackgroundColor(Color.WHITE);
            textView.setPadding(dp2px(15), 0, 0, 0);
            textView.setTextColor(0xff333333));
            textView.setTextSize(15);
            textView.setLayoutParams(new ViewGroup.LayoutParams(-1, dp2px(50)));
            return new MyHolder(textView);
        }
    }

    @Override
    public void onBindViewHolder(MyHolder holder, int position) {
        int itemViewType = getItemViewType(position);
        Pair<Character,String> text = strings.get(position);
        if(itemViewType == HEADER){
            holder.letter.setText(String.valueOf(text.first));
        }else {
            if(position %2 == 0){
                holder.textView.setBackgroundColor(0xf4f4f4);
            }else {
                holder.textView.setBackgroundColor(Color.WHITE);
            }
        }
        holder.textView.setText(text.second);
    }

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

    @Override
    public int getItemViewType(int position) {
        if (position == 0) {
            return HEADER;
        }else{
            char charAt = strings.get(position).first;
            char charBefore = strings.get(position-1).first;
            if(charAt == charBefore){
                return NORMAL;
            }else {
                return HEADER;
            }
        }
    }
}
  • 2,数据容器的选择
    • 1 :列表数据使用ArrayList<Pair<Character, String>>,简单定义了两个属性,一个Character代表item的小分类,用letter表示,一个String代表具体的数据
    • 2 :定义了一个ArrayList<Pair<Character,Integer>> positions,用于记录每个letter分类下数据能达到的最大ScrollY。用于在滑动时处理悬浮控件,
ArrayList<Pair<Character,String>> strings = new ArrayList<>();
int maxScrollY = 0;
final ArrayList<Pair<Character,Integer>> positions = new ArrayList<>();
final Random random = new Random();
final int scrollRange = dp2px(35);
for (int index = 'A';index <= 'Z';index ++){
    int size = random.nextInt(50)+10;
    for (int i = 0; i < size; i++){
        strings.add(new Pair<>((char)index,(char)index+" __ "+i));
    }
    if(positions.size() == 0){
        positions.add(new Pair<>((char)index,maxScrollY = scrollRange +dp2px(50*size)));
    }else {
        positions.add(new Pair<>((char)index,maxScrollY += scrollRange +dp2px(50*size)));
    }
}
  • 3,悬浮letter的联动
    • 1,此时列表正常显示数据,通过RecyclerView.OnScrollListener可以获取RecyclerView在垂直方向上的滑动总距离totalDy
    • 2,通过totalDypositions的比对,找到悬浮letter控件应当显示什么
    • 3,通过totalDy和当前letter对应小分类的临界点处理悬浮letter控件顶出逻辑
final TextView tv_overlay = findViewById(R.id.tv_overlay);
rv_data.setAdapter(new MyAdapter(strings));
rv_data.addOnScrollListener(new RecyclerView.OnScrollListener() {
    int totalDy;
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        totalDy += dy;
        if(!recyclerView.canScrollVertically(-1)){
            totalDy = 0;
        }
        Logs.e("onScrolled: ", "totalDy   "+totalDy);
        int rangeIndex = getRange(positions, totalDy);
        Pair<Character, Integer> range = positions.get(rangeIndex);
        tv_overlay.setText(String.valueOf(range.first));
        //根据拖动范围做位移动画
        if(rangeIndex == positions.size() -1){
            return;//最后一个  不考虑下一个顶位的
        }
        if(totalDy + scrollRange > range.second){
            tv_overlay.setTranslationY(range.second - totalDy - scrollRange);
        }else {
            tv_overlay.setTranslationY(0);
        }
    }
});

里面用的getRange()方法如下

private int getRange(ArrayList<Pair<Character,Integer>> positions,int totalDy){
    for (int index = 0; index <positions.size(); index ++){
        Pair<Character,Integer> pair = positions.get(index);
        if(totalDy < pair.second){
            return index;
        }
    }
    return 0;
}

PS:写到这里基本可以结束了,可以看到源码其实挺简单的。就是调用了View#setTranslationY(int)就可以实现。如果要兼容android2.X,建议集成NineOldAnimation

猜你喜欢

转载自blog.csdn.net/weixin_34081595/article/details/87415545