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

Android用Scroller实现一个可向上滑动的底部导航栏

程序员文章站 2022-05-28 10:21:08
静静等了5分钟竟不知道如何写我这第一篇文章。每次都想好好的学习学习,有时间多敲敲代码,写几篇自己的文章。今天终于开始实行了,还是有点小激动的。哈哈! 好了废话就不多收...

静静等了5分钟竟不知道如何写我这第一篇文章。每次都想好好的学习学习,有时间多敲敲代码,写几篇自己的文章。今天终于开始实行了,还是有点小激动的。哈哈!

好了废话就不多收了。我今天想实现的一个功能就是一个可以上滑底部菜单栏。为什么我会想搞这么个东西呢, 还是源于一年前,我们app 有这么个需求,当时百度也好谷歌也好,都没有找到想要的效果,其实很简单的一个效果。但是当时我也是真的太菜了,所有有关自定义的控件真的是不会,看别人的代码还好,真要是自己写,一点头绪都没有。因为我试着写了,真的不行啊。当时觉得自己好废啊 ,所有效果都是别人的,自己都不会写。也下定决定要好好搞自定义控件,一拖再拖就到了现在。在了解了一段时间的自定义控件后又想起来这个效果,然后给他搞了出来。

看下效果:

Android用Scroller实现一个可向上滑动的底部导航栏

首先这个控件是滑动的肯定就会用到scrollto()或者scrollby(),和scroller类。那么先简单介绍一点这三个东西。

scrollto(int, int)与scrollby(int, int)

  1. scrollto是让view的content滚动到相对view初始位置的(x, y)处。
  2. scrollby是让view的content滚动到相对于view当前位置的(x, y)处。

scroller类

scroller是手指滑动中比较重要的一个辅助类,它可以帮助开发者完成一个顺滑的滚动。其主要包括:

  1. startscroll(int startx, int starty, int dx, int dy)startscroll(int startx, int - starty, int dx, int dy, int duration)
  2. startx,x方向从哪里开始移动。
  3. starty,y方向从哪里开始移动。
  4. dx,x方向移动多远。
  5. dy,y方向移动多远。
  6. duration,这个移动操作需要多少时间执行完,默认是250毫秒。

如果真正的想使用这个类,还需要配合computescroll()方法。重写此方法

@override
public void computescroll() {
  if (mscroller.computescrolloffset()) { // 计算新位置,并判断上一个滚动是否完成。
     scrollto(mscroller.getcurrx(), mscroller.getcurry());
     invalidate();// 再次调用computescroll。
  }
}

computescrolloffset()

这个方法用来计算当前你想知道的一个新位置,scroller会自动根据标记时的坐标、时间、当前位置计算出一个新位置,记录到内部,我们可以通过scroller#getcurrx()和scroller#getcurry()获取的新的位置。

要知道的是,它计算出的新位置是一个闭区间[x, y],而且会在你调用startscroll传入的时间内渐渐从你指定的int startx和int starty移动int dx和int dy的距离,所以我们每次调用scroller#computescrolloffset()后再调用view的scrollto(int, int)然后传入scroller#getcurrx()和scroller#getcurry()就可以得到一个渐渐移动的效果。

同时这个方法有一个返回值是boolean类型的,内部是用一个boolean来记录是否完成的,在调用scroller#startscroll)时会把这个boolean参数置为false。内部逻辑是先判断startscroll()动画是否还在继续,如果没有完成则计算最新位置,计算最新位置前会对duration做判断,第一如果时间没到,则真正的计算位置,并且返回true,第二如果时间到了,把记录是否继续的boolean成员变量标记完成,并直接赋值最新位置为最终目的位置,并且返回true;如果startscroll()已经完成则直接返回false。我们判断scroller#computescrolloffset()是true时说明还没完成,此时拿到scroller#getcurrx()和scroller#getcurry()做一个滚动。

scroller#getcurrx()

scroller#getcurry()

这两个方法就是拿到通过scroller#computescrolloffset()计算出的新的位置,上面也解释过了。

scroller.isfinished()上次的动画是否完成。

scroller.abortanimation()取消上次的动画。

好了,了解了这些下面开始实现这个效果。

首先先搞一个布局,包括底部导航栏的头和导航栏的内容体。

<com.study.androidtest.bottombar
    android:orientation="vertical"
    android:layout_alignparentbottom="true"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <linearlayout
      android:background="@color/coloraccent"
      android:layout_width="match_parent"
      android:layout_height="50dp">

    </linearlayout>

    <linearlayout
      android:background="@color/colorprimarydark"
      android:layout_width="match_parent"
      android:layout_height="200dp">

    </linearlayout>
  </com.study.androidtest.bottombar>

配上效果图。

Android用Scroller实现一个可向上滑动的底部导航栏

很简单的一个效果(这里只看效果,不看ui啦),蓝色的就是内容,红色的就是头部。
那么我想实现一个什么效果呢,就是开始的时候是看不到蓝色部分的,点击或者滑动红色部分可以显示蓝色部分,一个上拉和下拉的效果。那么现在肯定要实现一个自定的viewgroup去实现这个布局。

首先我去建一个类bottombar.class, 为了简单我直接用它去继承linearlayout。重写它的onlayout()方法。因为我要去把蓝色部分隐去,只留红色部分。怎么做呢 ,代码如下:

 @override
  protected void onlayout(boolean changed, int l, int t, int r, int b) {
    super.onlayout(changed, l, t, r, b);
    bottombar.layout(0, getmeasuredheight() - bottombar.getmeasuredheight(), getmeasuredwidth(), getmeasuredheight());
    bottomcontent.layout(0, getmeasuredheight(), getmeasuredwidth(), bottombar.getbottom() + bottomcontent.getmeasuredheight());
  }

通过onlayout()方法改变其位置让其吧蓝色部分隐藏。

接下来就是处理滑动事件了。我要按住红色部分上下滑动去显示和隐藏蓝色部分,那么肯定是要有手势识别,重写ontouchevent(),再配合view的scrollto()方法就可以实现这个简单的效果。

@override
  public boolean ontouchevent(motionevent event) {
    super.ontouchevent(event);
    switch (event.getaction()){
      case motionevent.action_down:
        log.i("", "--------->x="+event.getx() + ", y="+event.gety());
        downx = (int) event.getx();
        downy = (int) event.gety();
        break;
      case motionevent.action_move:
        int endy = (int) event.gety();
        int dy = (int) (endy - downy);
        int toscroll = getscrolly() - dy;
        if(toscroll < 0){
          toscroll = 0;
        } else if(toscroll > bottomcontent.getmeasuredheight()){
          toscroll = bottomcontent.getmeasuredheight();
        }
        scrollto(0, toscroll);
        downy = (int) event.gety();
        break;
      case motionevent.action_up:
        scrolloffset = getscrolly();
        if(scrolloffset > bottomcontent.getmeasuredheight() / 2){
          shownavigation();
        } else {
          closenavigation();
        }
        break;
    }

    return true;
  }

代码就懒了没有注释,但是我会在下面解释一下,都是一些简单的逻辑,首先action_down里面的代码,只是记录了按下的坐标,没什么的。然后是action_move的代码。首先应该了解getscrolly(),它是控件滑动的距离,初始值为0。可以看到我调用scrollto(0, toscroll),而toscroll = getscrolly() - dy;,dy是手指滑动的一个偏移量。通过了这些计算你会发现toscroll就是蓝色部分的高度。那么效果就已经实现了,很简单吧。看了之后你们会不会有这样一个疑问哈,也是我当时的一个疑问,那就是为什么我们不直接用dy也就是手指滑动的一个距离来当作toscroll 的值呢(不考虑下面对downy的赋值,单纯是手指滑动的距离)。其实是可以的,控件会随着手指滑动的。但是,当手指离开屏幕再次点击的时候,菜单又会回到原来的状态再进行滑动。那么为什么会造成这样的效果呢,仔细看过你会发现,每次点击的时候dy都是0,所以每次调用scrollto(x, y)的时候x, y都是0,自然菜单就会回到初始位置。所以getscrolly() - dy其实就是再记录上一次的位置,使在下一次点击的时候'y'的值不是0。前提是需要每次对downy重新进行赋值。好了有了这些上拉下拉的效果就有了。但是只有这样还是不行,我们要做到它自动的弹出和收回。接下来就是action_up时做处理,我调用了shownavigation();和colsenavigation();,代码如下都是简单的逻辑不做解释。

  private void shownavigation(){
    int dy = bottomcontent.getmeasuredheight() - scrolloffset;
    mscroller.startscroll(getscrollx(), getscrolly(), 0, dy, 500);
    invalidate();
  }

  private void closenavigation(){
    int dy = 0 - scrolloffset;
    mscroller.startscroll(getscrollx(), getscrolly(), 0, dy, 500);
    invalidate();
  }

效果实现啦,哈哈。真的很简单。希望对大家的学习有所帮助,也希望大家多多支持。