欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android实现Recycleview悬浮粘性头部外加右侧字母导航

程序员文章站 2023-11-30 10:48:28
公司项目要实现这个效果:android实现recycleview悬浮粘性头部外加右侧字母导航 图一是开始的画面,图二是滑动的画面,点击右侧字母需要滑动左侧到指定位...

公司项目要实现这个效果:android实现recycleview悬浮粘性头部外加右侧字母导航

Android实现Recycleview悬浮粘性头部外加右侧字母导航

Android实现Recycleview悬浮粘性头部外加右侧字母导航

图一是开始的画面,图二是滑动的画面,点击右侧字母需要滑动左侧到指定位置,然后左侧的顶部字母a,b等需要悬浮。

实现思路:

右侧的联动可以用recycyeview中adapter的scrolltopositionwithoffset方法实现。左侧就是recycleview,后台返回的城市数据是这种类型的:

复制代码 代码如下:
{"returncode":1,"returnmsg":"操作成功","data":[{"startword":"a","traincitylist":[{"cityid":531,"cityname":"昂昂溪","code":null},{"cityid":2137,

我进行了一层封装

1.建立实体类用来封装下标和城市名字:

public class contactmodel {
 private string index;
 private string name;

 public contactmodel(string name){
  this.index = newfirstletterutil.getfirstletter(name);
  this.name = name;
 }

 public string getindex() {
  return index;
 }

 public string getname() {
  return name;
 }
 
}

2.讲服务器返回的数据进行封装:

 list<contactmodel> contacts = new arraylist<>();
   for (int i=0;i<mtraincitylist.size();i++){
   // contactmodel contactmodel = new contactmodel(mtraincitylist.get(i).getcityname());
   contacts.add(new contactmodel(mtraincitylist.get(i).getcityname()));
   collections.sort(contacts, new lettercomparator());
     }
    mcontactmodels.addall(contacts);
    mshowmodels.addall(mcontactmodels);

3.设置适配器

private void setnewadapter() {

   contactsadapter madapter = new contactsadapter(mshowmodels);
   mmainrecycleview.setlayoutmanager(new linearlayoutmanager(this));
   final stickyrecyclerheadersdecoration headersdecor = new stickyrecyclerheadersdecoration(madapter);
   mmainrecycleview.additemdecoration(headersdecor);

   madapter.setonitemclicklisttener(new contactsadapter.onitemclicklisttener() {
    @override
    public void onitemclick(int pos) {
     // toast.maketext(trainnewstartactivity.this,"惦记的pos:"+pos+"数据:"+mshowmodels.get(pos).getname(),toast.length_short).show();
     intent intent = new intent();
     intent.putextra("data", mshowmodels.get(pos).getname());
     log.d("lwp","data:"+mshowmodels.get(pos).getname());
     setresult(result_ok, intent);
     finish();
    }
   });
   mmainrecycleview.setadapter(madapter);
   mmain_side_bar.setlazyrespond(false);
   // 侧边设置相关
   mmain_side_bar.setonselectindexitemlistener(new wavesidebarview.onselectindexitemlistener() {
    @override
    public void onselectindexitem(string letter) {
     for (int i = 0; i< mcontactmodels.size(); i++) {
      if (mcontactmodels.get(i).getindex().equals(letter)) {
       ((linearlayoutmanager) mmainrecycleview.getlayoutmanager()).scrolltopositionwithoffset(i, 0);
       return;
      }
     }
    }
   });
}

4.适配器代码:

public class contactsadapter extends recyclerview.adapter<contactsadapter.contactsviewholder> implements stickyrecyclerheadersadapter {

 private list<contactmodel> contacts;
 private static final string tag = "contactsadapter";
 private contactmodel contact;

 public contactsadapter(list<contactmodel> contacts) {
  this.contacts = contacts;
 }

 @override
 public contactsviewholder oncreateviewholder(viewgroup parent, int viewtype) {
  layoutinflater inflater = layoutinflater.from(parent.getcontext());
  view view = inflater.inflate(r.layout.layaout_item_contacts, null);
  return new contactsviewholder(view);
 }

 @override
 public void onbindviewholder(contactsviewholder holder, final int position) {
  contact = contacts.get(position);
  log.e(tag, "onbindviewholder: index:" + contact.getindex());
  if (position == 0 || !contacts.get(position-1).getindex().equals(contact.getindex())) {
   holder.tvindex.setvisibility(view.gone);
   holder.tvindex.settext(contact.getindex());
  } else {
   holder.tvindex.setvisibility(view.gone);
  }
  holder.tvname.settext(contact.getname());
  holder.tvname.setonclicklistener(new view.onclicklistener() {
   @override
   public void onclick(view v) {
    log.d("lwp","惦记的pos:"+position);
    onitemclicklisttener.onitemclick(position);
   }
  });
 }

 public interface onitemclicklisttener{
  void onitemclick(int pos);
 }

 public onitemclicklisttener onitemclicklisttener;

 public void setonitemclicklisttener(onitemclicklisttener onitemclicklisttener) {
  this.onitemclicklisttener = onitemclicklisttener;
 }

 @override
 public long getheaderid(int position) {
  if (contacts.get(position).getindex().equals("a")){
   return 0;
  }else if (contacts.get(position).getindex().equals("b")){
   return 1;
  }else if (contacts.get(position).getindex().equals("c")){
   return 2;
  }else if (contacts.get(position).getindex().equals("d")){
   return 3;
  }else if (contacts.get(position).getindex().equals("e")){
   return 4;
  }else if (contacts.get(position).getindex().equals("f")){
   return 5;
  }else if (contacts.get(position).getindex().equals("g")){
   return 6;
  }else if (contacts.get(position).getindex().equals("h")){
   return 7;
  }else if (contacts.get(position).getindex().equals("i")){
   return 8;
  }else if (contacts.get(position).getindex().equals("j")){
   return 9;
  }else if (contacts.get(position).getindex().equals("k")){
   return 10;
  }else if (contacts.get(position).getindex().equals("l")){
   return 11;
  }else if (contacts.get(position).getindex().equals("m")){
   return 12;
  }else if (contacts.get(position).getindex().equals("n")){
   return 13;
  }else if (contacts.get(position).getindex().equals("o")){
   return 14;
  }else if (contacts.get(position).getindex().equals("p")){
   return 15;
  }else if (contacts.get(position).getindex().equals("q")){
   return 16;
  }else if (contacts.get(position).getindex().equals("r")){
   return 17;
  }else if (contacts.get(position).getindex().equals("s")){
   return 18;
  }else if (contacts.get(position).getindex().equals("t")){
   return 19;
  }else if (contacts.get(position).getindex().equals("u")){
   return 20;
  }else if (contacts.get(position).getindex().equals("v")){
   return 21;
  }else if (contacts.get(position).getindex().equals("y")){
   return 22;
  }else if (contacts.get(position).getindex().equals("x")){
   return 23;
  }else if (contacts.get(position).getindex().equals("y")){
   return 24;
  }else if (contacts.get(position).getindex().equals("z")){
   return 25;
  }else {
   return -1;
  }

 }

 @override
 public recyclerview.viewholder oncreateheaderviewholder(viewgroup parent) {
  view view = layoutinflater.from(parent.getcontext())
    .inflate(r.layout.view_header, parent, false);
  return new recyclerview.viewholder(view) {
  };
 }

 @override
 public void onbindheaderviewholder(recyclerview.viewholder holder, int position) {
  textview textview = (textview) holder.itemview;
  textview.settext(string.valueof(contacts.get(position).getindex()));
 }

 @override
 public int getitemcount() {
  return contacts.size();
 }

 class contactsviewholder extends recyclerview.viewholder {
  textview tvindex;
  imageview ivavatar;
  textview tvname;

  contactsviewholder(view itemview) {
   super(itemview);
   tvindex = (textview) itemview.findviewbyid(r.id.tv_index);
   ivavatar = (imageview) itemview.findviewbyid(r.id.iv_avatar);
   tvname = (textview) itemview.findviewbyid(r.id.tv_name);
  }
 }
}

5.两个布局文件:

layaout_item_contacts.xml:

<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width="match_parent"
 android:layout_height="match_parent">

 <textview
  android:id="@+id/tv_index"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:paddingleft="12dp"
  android:text="a"
  android:textsize="14sp"
  android:background="#e0e0e0"/>

 <relativelayout
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:background="?android:attr/selectableitembackground">
  <imageview
   android:id="@+id/iv_avatar"
   android:layout_width="40dp"
   android:layout_height="40dp"
   android:visibility="gone"
   android:layout_margin="10dp"
   />

  <textview
   android:id="@+id/tv_name"
   android:layout_marginleft="12dp"
   android:layout_width="wrap_content"
   android:layout_height="34dp"
   android:layout_torightof="@+id/iv_avatar"
   android:text="南尘"
   android:gravity="center_vertical"
   android:textcolor="#424242"
   android:textsize="16sp"
   android:layout_centervertical="true" />
 </relativelayout>

 <view
  android:layout_width="match_parent"
  android:layout_height="1dp"
  android:layout_marginleft="15dp"
  android:background="#e8e8e8" />
</linearlayout>

view_header.xml:

<?xml version="1.0" encoding="utf-8"?>

<textview
 xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:orientation="horizontal"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:paddingleft="12dp"
 android:textsize="14sp"
 android:textstyle="bold"
 android:background="#e0e0e0"
 tools:text="animals starting with a"
 />

采用的第三方:

compile 'com.github.nanchen2251:wavesidebar:1.0.6'
compile 'com.timehop.stickyheadersrecyclerview:library:0.4.3@aar'

右侧字母用的是wavesidebar,但是由于不太符合设计图,所有我没有用他的,而是自己拿过来重新定义了(该类没提供修改,建议完善),如下:

public class slfwaveslidebarview extends view {
 private final static int default_text_size = 14; // sp
 private final static int default_max_offset = 80; //dp

 private final static string[] default_index_items = {"a", "b", "c", "d", "e", "f", "g", "h", "i",
   "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"};

 private string[] mindexitems;

 /**
  * the index in {@link #mindexitems} of the current selected index item,
  * it's reset to -1 when the finger up
  */
 private int mcurrentindex = -1;

 /**
  * y coordinate of the point where finger is touching,
  * the baseline is top of {@link #mstarttouchingarea}
  * it's reset to -1 when the finger up
  */
 private float mcurrenty = -1;

 private paint mpaint;
 private int mtextcolor;
 private float mtextsize;

 /**
  * the height of each index item
  */
 private float mindexitemheight;

 /**
  * offset of the current selected index item
  */
 private float mmaxoffset;

 /**
  * {@link #mstarttouching} will be set to true when {@link motionevent#action_down}
  * happens in this area, and the side bar should start working.
  */
 private rectf mstarttouchingarea = new rectf();

 /**
  * height and width of {@link #mstarttouchingarea}
  */
 private float mbarheight;
 private float mbarwidth;

 /**
  * flag that the finger is starting touching.
  * if true, it means the {@link motionevent#action_down} happened but
  * {@link motionevent#action_up} not yet.
  */
 private boolean mstarttouching = false;

 /**
  * if true, the {@link wavesidebarview.onselectindexitemlistener#onselectindexitem(string)}
  * will not be called until the finger up.
  * if false, it will be called when the finger down, up and move.
  */
 private boolean mlazyrespond = false;

 /**
  * the position of the side bar, default is {@link #position_right}.
  * you can set it to {@link #position_left} for people who use phone with left hand.
  */
 private int msidebarposition;
 public static final int position_right = 0;
 public static final int position_left = 1;

 /**
  * the alignment of items, default is {@link #text_align_center}.
  */
 private int mtextalignment;
 public static final int text_align_center = 0;
 public static final int text_align_left = 1;
 public static final int text_align_right = 2;


 /**
  * observe the current selected index item
  */
 private wavesidebarview.onselectindexitemlistener onselectindexitemlistener;

 /**
  * the baseline of the first index item text to draw
  */
 private float mfirstitembaseliney;

 /**
  * for {@link #dp2px(int)} and {@link #sp2px(int)}
  */
 private displaymetrics mdisplaymetrics;


 public slfwaveslidebarview(context context) {
  this(context, null);
 }

 public slfwaveslidebarview(context context, attributeset attrs) {
  this(context, attrs, 0);
 }

 public slfwaveslidebarview(context context, attributeset attrs, int defstyleattr) {
  super(context, attrs, defstyleattr);
  mdisplaymetrics = context.getresources().getdisplaymetrics();

  typedarray typedarray = context.obtainstyledattributes(attrs, com.nanchen.wavesidebar.r.styleable.wavesidebarview);
  mlazyrespond = typedarray.getboolean(com.nanchen.wavesidebar.r.styleable.wavesidebarview_sidebar_lazy_respond, false);
  mtextcolor = typedarray.getcolor(com.nanchen.wavesidebar.r.styleable.wavesidebarview_sidebar_text_color, color.gray);
  mmaxoffset = typedarray.getdimension(com.nanchen.wavesidebar.r.styleable.wavesidebarview_sidebar_max_offset, dp2px(default_max_offset));
  msidebarposition = typedarray.getint(com.nanchen.wavesidebar.r.styleable.wavesidebarview_sidebar_position, position_right);
  mtextalignment = typedarray.getint(com.nanchen.wavesidebar.r.styleable.wavesidebarview_sidebar_text_alignment, text_align_center);
  typedarray.recycle();

  mtextsize = sp2px(default_text_size);

  mindexitems = default_index_items;

  initpaint();
 }

 private void initpaint() {
  mpaint = new paint();
  mpaint.setantialias(true);
  mpaint.setcolor(mtextcolor);
  mpaint.settextsize(mtextsize);
  switch (mtextalignment) {
   case text_align_center: mpaint.settextalign(paint.align.center); break;
   case text_align_left: mpaint.settextalign(paint.align.left); break;
   case text_align_right: mpaint.settextalign(paint.align.right); break;
  }
 }

 @override
 protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
  super.onmeasure(widthmeasurespec, heightmeasurespec);

  int height = measurespec.getsize(heightmeasurespec);
  int width = measurespec.getsize(widthmeasurespec);

  paint.fontmetrics fontmetrics = mpaint.getfontmetrics();
  mindexitemheight = fontmetrics.bottom - fontmetrics.top;
  mbarheight = mindexitems.length * mindexitemheight;

  // calculate the width of the longest text as the width of side bar
  for (string indexitem : mindexitems) {
   mbarwidth = math.max(mbarwidth, mpaint.measuretext(indexitem));
  }

  float arealeft = (msidebarposition == position_left) ? 0 : (width - mbarwidth - getpaddingright());
  float arearight = (msidebarposition == position_left) ? (getpaddingleft() + arealeft + mbarwidth) : width;
  float areatop = height/2 - mbarheight/2;
  float areabottom = areatop + mbarheight;
  mstarttouchingarea.set(
    arealeft,
    areatop,
    arearight,
    areabottom);

  // the baseline y of the first item' text to draw
  mfirstitembaseliney = (height/2 - mindexitems.length*mindexitemheight/2)
    + (mindexitemheight/2 - (fontmetrics.descent-fontmetrics.ascent)/2)
    - fontmetrics.ascent;
 }

 @override
 protected void ondraw(canvas canvas) {
  super.ondraw(canvas);

  // draw each item
  for (int i = 0, mindexitemslength = mindexitems.length; i < mindexitemslength; i++) {
   float baseliney = mfirstitembaseliney + mindexitemheight*i;

   // calculate the scale factor of the item to draw
   float scale = getitemscale(i);

   int alphascale = (i == mcurrentindex) ? (255) : (int) (255 * (1-scale));
   mpaint.setalpha(alphascale);

   mpaint.settextsize(mtextsize + mtextsize*scale);

   float baselinex = 0f;
   if (msidebarposition == position_left) {
    switch (mtextalignment) {
     case text_align_center:
      baselinex = getpaddingleft() + mbarwidth/2 + mmaxoffset*scale;
      break;
     case text_align_left:
      baselinex = getpaddingleft() + mmaxoffset*scale;
      break;
     case text_align_right:
      baselinex = getpaddingleft() + mbarwidth + mmaxoffset*scale;
      break;
    }
   } else {
    switch (mtextalignment) {
     case text_align_center:
      baselinex = getwidth() - getpaddingright() - mbarwidth/2 - mmaxoffset*scale;
      break;
     case text_align_right:
      baselinex = getwidth() - getpaddingright() - mmaxoffset*scale;
      break;
     case text_align_left:
      baselinex = getwidth() - getpaddingright() - mbarwidth - mmaxoffset*scale;
      break;
    }
   }

   // draw
   canvas.drawtext(
     mindexitems[i], //item text to draw
     baselinex, //baseline x
     baseliney, // baseline y
     mpaint);
  }

  // reset paint
  mpaint.setalpha(255);
  mpaint.settextsize(mtextsize);
 }

 /**
  * calculate the scale factor of the item to draw
  *
  * @param index the index of the item in array {@link #mindexitems}
  * @return the scale factor of the item to draw
  */
 private float getitemscale(int index) {
  float scale = 0;
  if (mcurrentindex != -1) {
   float distance = math.abs(mcurrenty - (mindexitemheight*index+mindexitemheight/2)) / mindexitemheight;
   scale = 1 - distance*distance/16;
   scale = math.max(scale, 0);
  }
  return scale;
 }

 @override
 public boolean ontouchevent(motionevent event) {
  if (mindexitems.length == 0) {
   return super.ontouchevent(event);
  }

  float eventy = event.gety();
  float eventx = event.getx();
  mcurrentindex = getselectedindex(eventy);

  switch (event.getaction()) {
   case motionevent.action_down:
    if (mstarttouchingarea.contains(eventx, eventy)) {
     mstarttouching = true;
     if (!mlazyrespond && onselectindexitemlistener != null) {
      onselectindexitemlistener.onselectindexitem(mindexitems[mcurrentindex]);
     }
    //  invalidate();
     return true;
    } else {
     mcurrentindex = -1;
     return false;
    }

   case motionevent.action_move:
    if (mstarttouching && !mlazyrespond && onselectindexitemlistener != null) {
     onselectindexitemlistener.onselectindexitem(mindexitems[mcurrentindex]);
    }
   //  invalidate();
    return true;

   case motionevent.action_up:
   case motionevent.action_cancel:
    if (mlazyrespond && onselectindexitemlistener != null) {
     onselectindexitemlistener.onselectindexitem(mindexitems[mcurrentindex]);
    }
    mcurrentindex = -1;
    mstarttouching = false;
   //  invalidate();
    return true;
  }

  return super.ontouchevent(event);
 }

 private int getselectedindex(float eventy) {
  mcurrenty = eventy - (getheight()/2 - mbarheight /2);
  if (mcurrenty <= 0) {
   return 0;
  }

  int index = (int) (mcurrenty / this.mindexitemheight);
  if (index >= this.mindexitems.length) {
   index = this.mindexitems.length - 1;
  }
  return index;
 }

 private float dp2px(int dp) {
  return typedvalue.applydimension(typedvalue.complex_unit_dip, dp, this.mdisplaymetrics);
 }

 private float sp2px(int sp) {
  return typedvalue.applydimension(typedvalue.complex_unit_sp, sp, this.mdisplaymetrics);
 }

 public void setindexitems(string... indexitems) {
  mindexitems = arrays.copyof(indexitems, indexitems.length);
  requestlayout();
 }

 public void settextcolor(int color) {
  mtextcolor = color;
  mpaint.setcolor(color);
  invalidate();
 }

 public void setposition(int position) {
  if (position != position_right && position != position_left) {
   throw new illegalargumentexception("the position must be position_right or position_left");
  }

  msidebarposition = position;
  requestlayout();
 }

 public void setmaxoffset(int offset) {
  mmaxoffset = offset;
  invalidate();
 }

 public void setlazyrespond(boolean lazyrespond) {
  mlazyrespond = lazyrespond;
 }

 public void settextalign(int align) {
  if (mtextalignment == align) {
   return;
  }
  switch (align) {
   case text_align_center: mpaint.settextalign(paint.align.center); break;
   case text_align_left: mpaint.settextalign(paint.align.left); break;
   case text_align_right: mpaint.settextalign(paint.align.right); break;
   default:
    throw new illegalargumentexception(
      "the alignment must be text_align_center, text_align_left or text_align_right");
  }
  mtextalignment = align;
  invalidate();
 }

 public void setonselectindexitemlistener(wavesidebarview.onselectindexitemlistener onselectindexitemlistener) {
  this.onselectindexitemlistener = onselectindexitemlistener;
 }

 public interface onselectindexitemlistener {
  void onselectindexitem(string letter);
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。