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

Android使用ListView实现滚轮的动画效果实例

程序员文章站 2023-10-27 18:11:28
之前收到一个需求,需要把一个数据展示列表页面做成像滚轮那样的动画效果:中间最大然后向上下两端逐渐缩小。我想了想ios那边自带滚轮组件,安卓得自己去实现,目前网上仿ios的滚...

之前收到一个需求,需要把一个数据展示列表页面做成像滚轮那样的动画效果:中间最大然后向上下两端逐渐缩小。我想了想ios那边自带滚轮组件,安卓得自己去实现,目前网上仿ios的滚轮组件的也有一些,但是感觉不适合我,我的要求没那么复杂,于是决定自己动手去实现一下。

动手前先分析一下应该怎么做,归根到底只是要实现缩放效果,由中间向两边变小,当一个item越接近中间就放大,越远离中间就缩小。那么可以通过先获取listview的中点,然后获取当前可视的所有item跟listview的中点的垂直距离计算出一个比例,然后将item的大小根据这个比例进行缩放,各个item跟listview的中点的垂直距离不同,计算出来的比例也就不同,然后每次滚动的时候都计算比例然后进行缩放,这样应该就能实现我们想要的效果了。

因为一开始我的列表展示就是用listview做的,有其他效果在里面,也不方便换其他组件,所以依然还是用listview实现就好了。由于我们是每次一滚动都要进行缩放,listview有提供一个onscrolllistener,它的onscroll方法每次一开始滚动就会调用,所以我们选择重写它。当它被调用的时候,我们就开始获取listview中点,然后开始计算每个item的距离进行缩放.

/** 
  * 中点的y坐标 
  */ 
  private float centery = 0f; 
  @override 
  public void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount) { 
    //计算中点 
    centery = getheight()/2; 
    //判断中点的有效性 
    if(centery <= 0){ 
      return; 
    } 
    //开始对当前显示的view进行缩放 
    for(int i = 0; i < visibleitemcount; i++){ 
      //获取item 
      view temp_view = getchildat(i); 
      //计算item的中点y坐标 
      float itemy = temp_view.getbottom()-(temp_view.getheight()/2); 
      //计算离中点的距离 
      float distance = centery; 
      if(itemy > centery){ 
        distance = itemy - centery; 
      }else{ 
        distance = centery - itemy; 
      } 
      //根据距离进行缩放 
      temp_view.setscaley(1.1f - (distance / centery) < 0 ? 0 : 1.1f - (distance / centery)); 
      temp_view.setscalex(1.1f - (distance / centery) < 0 ? 0 : 1.1f - (distance / centery)); 
      //根据距离改变透明度 
      temp_view.setalpha(1.1f - (distance / centery) < 0 ? 0 : 1.1f - (distance / centery)); 
    } 
  } 

后面不想加demo了,所以直接上整个listview的代码吧

/** 
 * 模仿滚轮动画缩放的listview 
 * created by xu on 2017/3/3. 
 */ 
public class xulistview extends listview implements abslistview.onscrolllistener { 
  private static final string tag = "xulistview"; 
 
  /** 
   * 中点的y坐标 
   */ 
  private float centery = 0f; 
 
  public xulistview(context context, attributeset attrs) { 
    super(context, attrs); 
    //设置一个滚动监听 
    setonscrolllistener(this); 
  } 
 
  @override 
  public void onscrollstatechanged(abslistview view, int scrollstate) { 
 
  } 
 
  @override 
  public void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount) { 
    //计算中点 
    centery = getheight()/2; 
    //判断中点的有效性 
    if(centery <= 0){ 
      return; 
    } 
    //开始对当前显示的view进行缩放 
    for(int i = 0; i < visibleitemcount; i++){ 
      //获取item 
      view temp_view = getchildat(i); 
      //计算item的中点y坐标 
      float itemy = temp_view.getbottom()-(temp_view.getheight()/2); 
      //计算离中点的距离 
      float distance = centery; 
      if(itemy > centery){ 
        distance = itemy - centery; 
      }else{ 
        distance = centery - itemy; 
      } 
      //根据距离进行缩放 
      temp_view.setscaley(1.1f - (distance / centery) < 0 ? 0 : 1.1f - (distance / centery)); 
      temp_view.setscalex(1.1f - (distance / centery) < 0 ? 0 : 1.1f - (distance / centery)); 
      //根据距离改变透明度 
      temp_view.setalpha(1.1f - (distance / centery) < 0 ? 0 : 1.1f - (distance / centery)); 
    } 
  } 
} 

这样基本就实现了我们想要的效果了

Android使用ListView实现滚轮的动画效果实例

但是现在有一个问题,就是第一个item和最后一个item无法滚动到中间从而放大突出显示。对此我暂时想了两个方法去解决:1、在头尾各种添加一些空白的item,使我们需要显示的数据都可以滚动到最中间。(我现在就是这么做的);2、使整个列表实现循环滚动。

添加空白的item的话,我是通过修改adapter去实现的。数据源是一个数组,我在数组前面和后面各加一些特殊的数据,adapter实现getview的时候,如果发现当前item的数据是特殊数据,那么item就变透明,从而实现了我们原本要显示的数据都可以被滚动最中间;

先从数据源下手,从头尾填充特殊的数据

public class mainactivity extends appcompatactivity { 
  xulistview mlisetview; 
  myadapter adapter; 
  arraylist<string> nos = new arraylist<string>(); 
  @override 
  protected void oncreate(bundle savedinstancestate) { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.activity_main); 
     
    mlisetview = (xulistview) findviewbyid(r.id.list_test); 
 
    arraylist<string> temp = new arraylist<string>(); 
    for(int i = 0;i<10;i++){ 
      temp.add(i+""); 
    } 
    adapter = new myadapter(this,temp); 
    mlisetview.setadapter(adapter); 
    resetitem(mlisetview); 
  } 
 
  /** 
   * 在头尾填充透明的item数据 
   */ 
  private void resetitem(listview listview) { 
 
    if(listview == null){ 
      return; 
    } 
    //获取屏幕高度 
    windowmanager wm =getwindowmanager(); 
    int displayheight = wm.getdefaultdisplay().getheight(); 
    //计算一个item的高度 
    int itemhight = 0; 
    if(adapter!=null){ 
      view v=(view)adapter.getview(0, null, listview); 
      v.measure( 
          view.measurespec.makemeasurespec(0, view.measurespec.unspecified), 
          view.measurespec.makemeasurespec(0, view.measurespec.unspecified)); 
      itemhight=v.getmeasuredheight(); 
    } 
    //根据item的高度和屏幕的高度计算需要多少个item可以填满一半的屏幕 
    int newcount = ((displayheight/2)/itemhight); 
    //填充前面的空白item 
    for (int i = 1; i <= newcount; i++) { 
      nos.add("full"); 
    } 
    //添加我们需要显示的数据 
    for(int i = 0;i<10;i++){ 
      nos.add(i+""); 
    } 
    //填充后面的空白item 
    for (int i = 1; i <= newcount; i++) { 
      nos.add("full"); 
    } 
    //刷新数据 
    adapter.refreshdata(nos); 
  } 
} 

然后adapter里面对头尾的特殊数据进行识别,将item变为透明的。

public class myadapter extends baseadapter { 
 
  arraylist<string> nos = new arraylist<string>(); 
  private context context; 
 
  public myadapter(context context, arraylist<string> nos){ 
    this.context = context; 
    this.nos = nos; 
  } 
  public void refreshdata(arraylist<string> nos) { 
    this.nos = nos; 
    notifydatasetchanged(); 
  } 
  @override 
  public int getcount() { 
    return nos.size(); 
  } 
 
  @override 
  public object getitem(int position) { 
    return nos.get(position); 
  } 
 
  @override 
  public long getitemid(int position) { 
    return position; 
  } 
 
  @override 
  public view getview(int position, view convertview, viewgroup parent) { 
    viewholder holder = null; 
    if (convertview == null) { 
    // 如果是第一次显示该页面(要记得保存到viewholder*下次直接从缓存中调用) 
      holder = new viewholder(); 
      convertview = layoutinflater.from(context).inflate(r.layout.item_test, null); 
      holder.tv_no = (textview) convertview.findviewbyid(r.id.tv_no); 
      convertview.settag(holder); 
    } else { 
      holder = (viewholder) convertview.gettag(); 
    } 
    holder.tv_no.settext(nos.get(position)); 
    if(nos.get(position).equals("full")){ 
      convertview.setvisibility(view.invisible); 
    }else{ 
      convertview.setvisibility(view.visible); 
    } 
    return convertview; 
  } 
   
   
  private class viewholder { 
    textview tv_no; 
  } 
} 

 这样我们就实现可以第一种解决方法

Android使用ListView实现滚轮的动画效果实例

第二种方法,就是让整个listview实现循环滚动,实现的方式有很多,大家可以自行百度,我这里就通过修改adapter的getcount方法去实现,就是在getcount方法return一个很大的值,getview获取数据的时候要模原本的数组大小,不然数组就越界了。然后listview滚动到最中间,这样就实现伪循环滚动了

public class mainactivity extends appcompatactivity { 
  xulistview mlisetview; 
  myadapter adapter; 
  arraylist<string> nos = new arraylist<string>(); 
  @override 
  protected void oncreate(bundle savedinstancestate) { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.activity_main); 
 
    mlisetview = (xulistview) findviewbyid(r.id.list_test); 
 
    arraylist<string> temp = new arraylist<string>(); 
    for(int i = 0;i<10;i++){ 
      temp.add(i+""); 
    } 
    adapter = new myadapter(this,temp); 
    mlisetview.setadapter(adapter); 
    //滚动到中间 
    mlisetview.setselection(adapter.getcount()/2); 
  } 
 
} 
/** 
 * created by xu on 2017/6/27. 
 */ 
public class myadapter extends baseadapter { 
 
  arraylist<string> nos = new arraylist<string>(); 
  private context context; 
 
  public myadapter(context context, arraylist<string> nos){ 
    this.context = context; 
    this.nos = nos; 
  } 
  @override 
  public int getcount() { 
    return integer.max_value; 
  } 
 
  @override 
  public object getitem(int position) { 
    return nos.get(position); 
  } 
 
  @override 
  public long getitemid(int position) { 
    return position; 
  } 
 
  @override 
  public view getview(int position, view convertview, viewgroup parent) { 
    viewholder holder = null; 
    if (convertview == null) { 
    // 如果是第一次显示该页面(要记得保存到viewholder*下次直接从缓存中调用) 
      holder = new viewholder(); 
      convertview = layoutinflater.from(context).inflate(r.layout.item_test, null); 
      holder.tv_no = (textview) convertview.findviewbyid(r.id.tv_no); 
      convertview.settag(holder); 
    } else { 
      holder = (viewholder) convertview.gettag(); 
    } 
    holder.tv_no.settext(nos.get(position%nos.size())); 
    return convertview; 
  } 
 
 
  private class viewholder { 
    textview tv_no; 
  } 
} 

这样我们就实现了使列表进行循环滚动,从而达到每个item都可以滚动到中间突出显示的效果了

Android使用ListView实现滚轮的动画效果实例

既然我们把动画效果都做出来了,那么也可以直接做成一个滚轮选择器,只需要加多两步:1、把最靠近中间的item滚动到中间;2、把中间的item的序号通过一个接口返回出去。 我就直接贴代码吧,反正也不难。

把最靠近中间的item滚动到中间的实现我是这么做的:首先决定好整个listview可视的的item数量是多少,必须是奇数,这样才能只有一个item处于正中间,然后根据listview的高度计算出每个item符合要求的高度,然后更改现有的item的高度,同时对内容进行缩放(不缩放内容单纯高度变小的话布局就乱了)。最后我们利用smoothscrolltoposition方法回正可视item中的第一个item,就实现了将最中间的item回滚到最中间的效果了。因为可视的item我们是根据listview的高度去计算item的高度的,所有的可视item刚好铺满listview,所以只要顶部那个回正了,其他的item也会回正。所以我们可以重写一下onscrolllistener的onscrollstatechanged方法,每次滚动结束就执行一次回滚

/** 
 * 可视的item数 
 */ 
private int mvisibleitemcount = -1; 
/** 
 * 没调整之前每个item的高度 
 */ 
private float olditemheight = 0; 
/** 
 * 调整过后的每个item的高度 
 */ 
private float newitemheight = -1; 
 
/** 
 * 调整每个可视的item的高度 以及对内容进行缩放 
 */ 
public void resetitemheight() { 
  for (int i = 0; i < getchildcount(); i++) { 
    //获取item 
    view temp_view = getchildat(i); 
    //设置item的高度 
    viewgroup.layoutparams lp = temp_view.getlayoutparams(); 
    lp.height = (int) newitemheight; 
    temp_view.setlayoutparams(lp); 
 
    //缩放内容 我的item的内容用一个linearlayout包了起来 所以直接缩放linearlayout 
    linearlayout item_ll_value = (linearlayout) temp_view.findviewbyid(r.id.item_ll_value); 
    item_ll_value.setscaley((newitemheight / olditemheight) < 0 ? 0 : (newitemheight / olditemheight)); 
    item_ll_value.setscalex((newitemheight / olditemheight) < 0 ? 0 : (newitemheight / olditemheight)); 
  } 
} 
/** 
 * 计算在给定的可视item数目下 每个item应该设置的高度 
 * */ 
private void getnewitemheight() { 
  //先把旧的item存起来 
  olditemheight = getchildat(0).getheight(); 
  //计算新的高度 
  newitemheight = getheight() / mvisibleitemcount; 
  if ((getheight() / mvisibleitemcount) % newitemheight > 0) { 
    //除不尽的情况下把余数分给各个item,暂时发现分一次余数就够了,如果效果不理想就做个递归多分几次 
    float remainder = (getheight() / mvisibleitemcount) % newitemheight; 
    newitemheight = remainder / mvisibleitemcount; 
 
  } 
} 
 
@override 
public void onscrollstatechanged(abslistview view, int scrollstate) { 
 
  //滚动结束之后开始回滚item 
  if (scrollstate == abslistview.onscrolllistener.scroll_state_idle && mvisibleitemcount != -1) { 
    //使离中间最近的item回滚到中点位置 
    smoothscrolltoposition(getfirstvisibleposition()); 
  } 
 
} 

实现了把最靠近中间的item滚动到中间,那么选择的item就是滚动结束后处于最中间的item。对此我们需要再次重写一下onscrolllistener的onscrollstatechanged方法。每次滚动结束后,取可视item中的第一个item的序号加上我们之前设置的可视item数的一半(舍去小数部分)就是最中间的item的序号了,也是当前选择项的序号。

/** 
   * 当前选中项发生变化的监听者 
   */ 
  private onselectionchangelisenter selectionchangelisenter; 
 
   
  /** 
   * 设置选中项的监听者 
   */ 
  public void setselectionchangelisenter(onselectionchangelisenter selectionchangelisenter) { 
    this.selectionchangelisenter = selectionchangelisenter; 
  } 
   
  @override 
  public void onscrollstatechanged(abslistview view, int scrollstate) { 
 
     //滚动结束之后开始正常回滚item并记录最中间的item为选中项 (必须设置可视项,listview才会改为选择器模式) 
    if( scrollstate == abslistview.onscrolllistener.scroll_state_idle && mvisibleitemcount != -1){ 
      //使离中间最近的item回滚到中点位置 
      smoothscrolltoposition(getfirstvisibleposition()); 
      //计算当前选中项的序号 
      int nowposition = getfirstvisibleposition() + mvisibleitemcount/2; 
      //把当前选中项的序号存起来并通过listener回调出去 
      if(selectionchangelisenter != null && nowposition != curposition){ 
        curposition = nowposition; 
        selectionchangelisenter.onselectionchange(curposition); 
      } 
    } 
 
  } 

此处我是使用了一个接口去,用以实时把最新的数据返回出去

/** 
 * created by xu on 2017/3/3. 
 */ 
public interface onselectionchangelisenter { 
  void onselectionchange(int position); 
} 

使用这个滚轮选择器的方法也非常简单,除了跟正常的listview初始化和绑定adapter之外,只需要额外调用两个方法就行了

//设置listview的可视item数(必须是奇数) 
    mlisetview.setvisibleitemcount(3); 
//设置监听者监听选中项的变化 
    mlisetview.setselectionchangelisenter(new onselectionchangelisenter() { 
      @override 
      public void onselectionchange(final int position) { 
        mhandler.post(new runnable() { 
          @override 
          public void run() { 
            toast.maketext(mainactivity.this,"选择项发生变化 当前选中序号:"+(temp.get(position)),toast.length_short).show(); 
          } 
        }); 
      } 
    }); 

这样我们就实现滚轮数字选择器的效果了

Android使用ListView实现滚轮的动画效果实例

现在贴下整个滚轮选择器的完整代码

/** 
 * 模仿滚轮动画缩放的listview 
 * created by xu on 2017/3/3. 
 */ 
public class xulistview extends listview implements abslistview.onscrolllistener { 
  private static final string tag = "xulistview"; 
 
  /** 
   * 中点的y坐标 
   */ 
  private float centery = 0f; 
  /** 
   * 可视的item数 
   */ 
  private int mvisibleitemcount = -1; 
  /** 
   * 没调整之前每个item的高度 
   */ 
  private float olditemheight = 0; 
  /** 
   * 调整过后的每个item的高度 
   */ 
  private float newitemheight = -1; 
  /** 
   * 当前选中项发生变化的监听者 
   */ 
  private onselectionchangelisenter selectionchangelisenter; 
  /** 
   * 当前选中项的序号 
   */ 
  private int curposition = -1; 
 
  public xulistview(context context, attributeset attrs) { 
    super(context, attrs); 
    //设置一个滚动监听 
    setonscrolllistener(this); 
  } 
 
  /** 
   * 设置选中项的监听者 
   */ 
  public void setselectionchangelisenter(onselectionchangelisenter selectionchangelisenter) { 
    this.selectionchangelisenter = selectionchangelisenter; 
  } 
 
  /** 
   * 设置listview的显示item数 
   * @param count :必须是奇数  如果为-1 则表示只是使用动画效果的普通listview 
   */ 
  public boolean setvisibleitemcount(int count){ 
    if(count % 2 == 0){ 
      return false; 
    }else{ 
      mvisibleitemcount = count; 
      return true; 
    } 
 
  } 
 
  /** 
   * 在这里第一次调整item高度 
   */ 
  @override 
  public void onwindowfocuschanged(boolean haswindowfocus) { 
    super.onwindowfocuschanged(haswindowfocus); 
    if(mvisibleitemcount != -1){ 
      getnewitemheight(); 
      resetitemheight(); 
    } 
  } 
 
  /** 
   * 调整每个可视的item的高度 以及对内容进行缩放 
   */ 
  public void resetitemheight(){ 
    for(int i = 0; i < getchildcount(); i++){ 
      //获取item 
      view temp_view = getchildat(i); 
      //设置item的高度 
      viewgroup.layoutparams lp = temp_view.getlayoutparams(); 
      lp.height = (int)newitemheight; 
      temp_view.setlayoutparams(lp); 
 
      //缩放内容 我的item的内容用一个linearlayout包了起来 所以直接缩放linearlayout 
      linearlayout item_ll_value = (linearlayout)temp_view.findviewbyid(r.id.item_ll_value); 
      item_ll_value.setscaley((newitemheight / olditemheight) < 0 ? 0 : (newitemheight / olditemheight)); 
      item_ll_value.setscalex((newitemheight / olditemheight) < 0 ? 0 : (newitemheight / olditemheight)); 
    } 
  } 
 
 
  /** 
   * 计算在给定的可视item数目下 每个item应该设置的高度 
   */ 
  private void getnewitemheight(){ 
    //先把旧的item存起来 
    olditemheight = getchildat(0).getheight(); 
    //计算新的高度 
    newitemheight = getheight()/mvisibleitemcount; 
    if((getheight()/mvisibleitemcount) % newitemheight > 0){ 
      //除不尽的情况下把余数分给各个item,暂时发现分一次余数就够了,如果效果不理想就做个递归多分几次 
      float remainder = (getheight()/mvisibleitemcount) % newitemheight; 
      newitemheight = remainder/mvisibleitemcount; 
    } 
  } 
   
  @override 
  public void onscrollstatechanged(abslistview view, int scrollstate) { 
 
     //滚动结束之后开始正常回滚item并记录最中间的item为选中项 (必须设置可视项,listview才会改为选择器模式) 
    if( scrollstate == abslistview.onscrolllistener.scroll_state_idle && mvisibleitemcount != -1){ 
      //使离中间最近的item回滚到中点位置 
      smoothscrolltoposition(getfirstvisibleposition()); 
      //计算当前选中项的序号 
      int nowposition = getfirstvisibleposition() + mvisibleitemcount/2; 
      //把当前选中项的序号存起来并通过listener回调出去 
      if(selectionchangelisenter != null && nowposition != curposition){ 
        curposition = nowposition; 
        selectionchangelisenter.onselectionchange(curposition); 
      } 
    } 
     
  } 
 
  @override 
  public void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount) { 
    //计算中点 
    centery = getheight()/2; 
    //判断中点的有效性 
    if(centery <= 0){ 
      return; 
    } 
    //开始对当前显示的view进行缩放 
    for(int i = 0; i < visibleitemcount; i++){ 
      //获取item 
      view temp_view = getchildat(i); 
      //计算item的中点y坐标 
      float itemy = temp_view.getbottom()-(temp_view.getheight()/2); 
      //计算离中点的距离 
      float distance = centery; 
      if(itemy > centery){ 
        distance = itemy - centery; 
      }else{ 
        distance = centery - itemy; 
      } 
 
      //根据距离进行缩放 
      temp_view.setscaley(1.1f - (distance / centery) < 0 ? 0 : 1.1f - (distance / centery)); 
      temp_view.setscalex(1.1f - (distance / centery) < 0 ? 0 : 1.1f - (distance / centery)); 
      //根据距离改变透明度 
      temp_view.setalpha(1.1f - (distance / centery) < 0 ? 0 : 1.1f - (distance / centery)); 
    } 
  } 
 
} 

注释很详细 相信小白看了也没什么难度。

滚轮效果的实现方式有很多,解决头尾两个item无法滚动到中间的方法也很多,我说的方法仅供参考,没有最好的方法,只有最符合自己的需求的方法。

demo下载地址

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