当前位置: 移动技术网 > 移动技术>移动开发>Android > 手势滑动结束Activity基本功能的实现(一)

手势滑动结束Activity基本功能的实现(一)

2019年07月24日  | 移动技术网移动技术  | 我要评论

喜欢听音乐的朋友可能都看过天天动听这款 app, 这款 app 有一个亮点就是在切换页面(fragment)的时候可以通过手势滑动来结束当前页面,这里先说一下,我为什么会这么关心这个功能呢,因为前两天 pm说我们即将开始做的这款app 也要实现页面能通过手势滑动来结束的功能,所以我就拿着这款 app 滑了一上午;但是我要实现的跟天天动听这款 app又有点不同,细心观察的朋友可能会发现,天天动听是 fragment 之间的切换,而我这里要实现的是 activity 之间的切换,不过,不管是哪种,最终效果都是一样,就是页面能随着手势的滑动而滑动,最终达到某个特定条件,结束此页面。
要实现这个功能其实也不是特别难,这里我把这个功能的实现分为了以下两个步骤:

1、识别手势滑动自定义viewgroup 的实现
2、实现自定义 viewgroup 和 activity 绑定

根据以上两个步骤,我们发现,这其中涉及到的知识点有:android 事件处理机制、自定义 view(viewgroup)的实现,activity window的知识,在开发的过程中还涉及到activity 主题的配置。android 事件处理和自定义 view 都在我前面的 blog 中有讲到,如果不了解的朋友可以去看看。下面开始按步骤来实现功能

一、自定义 viewgroup

这个 viewgroup 的功能只要是对事件的拦截,能够实现手势滑动效果;显示 activity 的内容包括 actionbar 和内容区。

1、实现测量和布局

  @override
  protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
    /*获取默认的宽度*/
    int width = getdefaultsize(0, widthmeasurespec);
    /*获取默认的高度*/
    int height = getdefaultsize(0, heightmeasurespec);
    /*设置viewgroup 的宽高*/
    setmeasureddimension(width, height);
    /*获取子 view 的宽度*/
    final int contentwidth = getchildmeasurespec(widthmeasurespec, 0, width);
    /*获取子view 的高度*/
    final int contentheight = getchildmeasurespec(heightmeasurespec, 0, height);
    /*设置子view 的大小*/
    mcontent.measure(contentwidth, contentheight);
  }

  @override
  protected void onlayout(boolean changed, int l, int t, int r, int b) {
    final int width = r - l;
    final int height = b - t;
    mcontent.layout(0, 0, width, height);
  }

因为每个 activity 都只有一个 layout,所以这里只有一个子 view,布局和测量就显得非常简单。

2、事件拦截

  @override
  public boolean onintercepttouchevent(motionevent ev) {
    if (!isenable) {
      return false;
    }
    final int action = ev.getaction() & motioneventcompat.action_mask;

    if (action == motionevent.action_cancel || action == motionevent.action_up
        || action != motionevent.action_down && misunabletodrag) {
      /*结束手势的滑动,不拦截*/
      endtodrag();
      return false;
    }
    switch (action) {
      case motionevent.action_down:
        /*计算 x,y 的距离*/
        int index = motioneventcompat.getactionindex(ev);
        mactivepointerid = motioneventcompat.getpointerid(ev, index);
        if (mactivepointerid == invalid_pointer)
          break;
        mlastmotionx = minitialmotionx = motioneventcompat.getx(ev, index);
        mlastmotiony = motioneventcompat.gety(ev, index);
        /*这里判读,如果这个触摸区域是允许滑动拦截的,则拦截事件*/
        if (thistouchallowed(ev)) {
          misbeingdragged = false;
          misunabletodrag = false;
        } else {
          misunabletodrag = true;
        }
        break;
      case motionevent.action_move:
        /*继续判断是否需要拦截*/
        determinedrag(ev);
        break;
      case motionevent.action_up:
        break;
      case motionevent.action_pointer_up:
        /*这里做了对多点触摸的处理,当有多个手指触摸的时候依然能正确的滑动*/
        onsecondarypointerup(ev);
        break;

    }
    if (!misbeingdragged) {
      if (mvelocitytracker == null) {
        mvelocitytracker = velocitytracker.obtain();
      }
      mvelocitytracker.addmovement(ev);
    }
    return misbeingdragged;
  }

事件拦截,是拦截而是其不会向子 view 分发,直接执行本级 view的 ontouchevent方法;

3、事件处理

  @override
  public boolean ontouchevent(motionevent event) {
    if (!isenable) {
      return false;
    }
    if (!misbeingdragged && !thistouchallowed(event))
      return false;
    final int action = event.getaction();

    if (mvelocitytracker == null) {
      mvelocitytracker = velocitytracker.obtain();
    }
    mvelocitytracker.addmovement(event);

    switch (action & motioneventcompat.action_mask) {
      case motionevent.action_down:
        /*按下则结束滚动*/
        completescroll();
        int index = motioneventcompat.getactionindex(event);
        mactivepointerid = motioneventcompat.getpointerid(event, index);
        mlastmotionx = minitialmotionx = event.getx();
        break;
      case motioneventcompat.action_pointer_down: {
        /*有多个点按下的时候,取最后一个按下的点为有效点*/
        final int indexx = motioneventcompat.getactionindex(event);
        mlastmotionx = motioneventcompat.getx(event, indexx);
        mactivepointerid = motioneventcompat.getpointerid(event, indexx);
        break;

      }
      case motionevent.action_move:
        if (!misbeingdragged) {
          determinedrag(event);
          if (misunabletodrag)
            return false;
        }
        /*如果已经是滑动状态,则根据手势滑动,而改变view 的位置*/
        if (misbeingdragged) {
          // 以下代码用来判断和执行view 的滑动
          final int activepointerindex = getpointerindex(event, mactivepointerid);
          if (mactivepointerid == invalid_pointer)
            break;
          final float x = motioneventcompat.getx(event, activepointerindex);
          final float deltax = mlastmotionx - x;
          mlastmotionx = x;
          float oldscrollx = getscrollx();
          float scrollx = oldscrollx + deltax;
          final float leftbound = getleftbound();
          final float rightbound = getrightbound();
          if (scrollx < leftbound) {
            scrollx = leftbound;
          } else if (scrollx > rightbound) {
            scrollx = rightbound;
          }

          mlastmotionx += scrollx - (int) scrollx;
          scrollto((int) scrollx, getscrolly());

        }
        break;
      case motionevent.action_up:
        /*如果已经是滑动状态,抬起手指,需要判断滚动的位置*/
        if (misbeingdragged) {
          final velocitytracker velocitytracker = mvelocitytracker;
          velocitytracker.computecurrentvelocity(1000, mmaxmunvelocity);
          int initialvelocity = (int) velocitytrackercompat.getxvelocity(
              velocitytracker, mactivepointerid);
          final int scrollx = getscrollx();
          final float pageoffset = (float) (-scrollx) / getcontentwidth();
          final int activepointerindex = getpointerindex(event, mactivepointerid);
          if (mactivepointerid != invalid_pointer) {
            final float x = motioneventcompat.getx(event, activepointerindex);
            final int totaldelta = (int) (x - minitialmotionx);
            /*这里判断是否滚动到下一页,还是滚回原位置*/
            int nextpage = determinetargetpage(pageoffset, initialvelocity, totaldelta);
            setcurrentiteminternal(nextpage, true, initialvelocity);
          } else {
            setcurrentiteminternal(mcuritem, true, initialvelocity);
          }
          mactivepointerid = invalid_pointer;
          endtodrag();
        } else {
//          setcurrentiteminternal(0, true, 0);
          endtodrag();
        }
        break;
      case motioneventcompat.action_pointer_up:
        /*这里有事多点处理*/
        onsecondarypointerup(event);
        int pointerindex = getpointerindex(event, mactivepointerid);
        if (mactivepointerid == invalid_pointer)
          break;
        mlastmotionx = motioneventcompat.getx(event, pointerindex);
        break;
    }

    return true;
  }

因为这里加入了多点控制,所以代码看起来有点复杂,其实原理很简单,就是不断的判断是否符合滑动的条件。其他就不细讲了,来看看这个自定义 viewgroup 的效果


可以看到,这里我们已经实现了手势识别的 viewgroup,其实这个viewgroup如果发挥想象,它能实现很多效果,不单单是我今天要讲的效果,还可以用作侧拉菜单,或者是做 qq5.0版本侧滑效果都可以实现的。

二、侧滑 view绑定 activity

这里为了代码的简洁,还是通过一个 viewgroup 来封装了一层。

/**
 * created by moon.zhong on 2015/3/13.
 */
public class slidinglayout extends framelayout {
  /*侧滑view*/
  private slidingview mslidingview ;
  /*需要侧滑结束的activity*/
  private activity mactivity ;

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

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

  public slidinglayout(context context, attributeset attrs, int defstyleattr) {
    super(context, attrs, defstyleattr);
    mslidingview = new slidingview(context) ;
    addview(mslidingview);
    mslidingview.setonpagechangelistener(new slidingview.onpagechangelistener() {
      @override
      public void onpagescrolled(int position, float positionoffset, int positionoffsetpixels) {
        if (position == 1){          log.v("zgy","========position=========") ;
          mactivity.finish();
        }
      }
      @override
      public void onpageselected(int position) {
      }
    });
    mactivity = (activity) context;
    bindactivity(mactivity) ;
  }

  /**
   * 侧滑view 和activity 绑定
   * @param activity
   */
  private void bindactivity(activity activity){
    /*获取activity 的最顶级viewgroup*/
    viewgroup root = (viewgroup) activity.getwindow().getdecorview();
    /*获取activity 显示内容区域的viewgroup,包行actionbar*/
    viewgroup child = (viewgroup) root.getchildat(0);
    root.removeview(child);
    mslidingview.setcontent(child);
    root.addview(this);
  }
}

测试 activity 这事就变的非常简单了

public class secondactivity extends actionbaractivity {

  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.activity_second);
    /*绑定activity*/
    new slidinglayout(this) ;
  }

}

来看看效果怎么样:

咦!能滑动结束页面,但为什么边滑走的同时看不到第一个 acitivity,而是要等结束了才能看到呢?我们猜测,应该是滑动的时候,这个 activity 还有哪里把第一个 activity 覆盖了,每个 activity 都是附在一个 window 上面,所以这里就涉及到一个 activity 的 window背景颜色问题, ok,把第二个 activity 的 window 背景设为透明

<style name="translucenttheme" parent="apptheme">
 <item name="android:windowistranslucent">true</item>
 <item name="android:windowbackground">@android:color/transparent</item>
 <item name="android:windowcontentoverlay">@null</item>
</style>

<activity android:name=".secondactivity"
  android:label="secondactivity"
  android:screenorientation="portrait"
  android:theme="@style/translucenttheme"
 />

再来看看效果,效果图:


完美实现!

好了,今天就到这里,下期文章就是对这个功能的进一步优化和改善,如果感兴趣,可以继续关注我!

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网