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

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

程序员文章站 2023-12-11 21:07:34
喜欢听音乐的朋友可能都看过天天动听这款 app, 这款 app 有一个亮点就是在切换页面(fragment)的时候可以通过手势滑动来结束当前页面,这里先说一下,我为什么会这...

喜欢听音乐的朋友可能都看过天天动听这款 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 的效果

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

可以看到,这里我们已经实现了手势识别的 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) ;
  }

}

来看看效果怎么样:

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

咦!能滑动结束页面,但为什么边滑走的同时看不到第一个 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"
 />

再来看看效果,效果图:

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

完美实现!

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

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

上一篇:

下一篇: