用recyclerView实现多级分类悬浮标题滑动顶出效果
2019-01-18 本文已影响67人
鹈鹕醍醐
效果图镇楼,请忽略这么丑的样式,我只是个小demo
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。用于在滑动时处理悬浮控件,
- 1 :列表数据使用
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,通过
totalDy
和positions
的比对,找到悬浮letter控件应当显示什么 - 3,通过
totalDy
和当前letter对应小分类的临界点处理悬浮letter控件顶出逻辑
- 1,此时列表正常显示数据,通过
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