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

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

再来看看效果,效果图:

完美实现!

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

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

(0)

相关推荐

  • Android通过滑动实现Activity跳转(手势识别器应用)

    通过手势识别器实现界面的转跳,具体内容如下 1.创建 GestureDetector对象 2.创建新类继承SimpleOnGestureListener类(创建 GestureDetecto需要的参数) 3.重写SimpleOnGestureListener中的OnFling()方法.(滑动手势监听) 4.重写界面的OntouchEvent方法 5.通过 GestureDetector对象的onTouchEvent()添加事件 代码如下: public abstract class BaseAc

  • Android手势滑动实现ImageView缩放图片大小

    本文推出了两种Android手势实现ImageView缩放图片大小的方法,分享给大家供大家参考,具体内容如下 方法一: 将以下代码写到MulitPointTouchListener.java中,然后对你相应的图片进行OnTouchListener. 例如:imageView.setOnTouchListener(new MulitPointTouchListener ()); 在xml中要将ImageView的缩放格式改成Matrix 例如:android:scaleType="matrix&q

  • Android GestureDetector手势滑动使用实例讲解

    Gesture在 ViewGroup中使用 GestureDetector类可以让我们快速的处理手势事件,如点击,滑动等. 使用GestureDetector分三步: 1. 定义GestureDetector类 2. 初始化手势类,同时设置手势监听 3. 将touch事件交给gesture处理 先来了解一下如何使用,后面会有示例: package com.example.y2222.myview; import android.content.Context; import android.ut

  • Android仿微信activity滑动关闭效果

    Android仿微信activity滑动关闭功能 1.利用具体利用v4包下的slidingPaneLayout实现透明的activity,代码如下: BaseActivity: public class BaseSlideCloseActivity extends AppCompatActivity implements SlidingPaneLayout.PanelSlideListener { @Override protected void onCreate(Bundle savedIns

  • Android仿微信滑动退出Activity

    效果图: 原理: 原理一句话就能描述清楚.重写Activity的dispatchTouchEvent,滑动的时候拿到Activity栈中栈顶Activity的上一个Acticity的ContentView添加到栈顶Activity的DecorView中,滑动的过程中做视图平移,滑动结束之后把前面拿过来用的ContentView归还给上一个Activity,然后finish当前Activity. ActivityStack: 实现 Application.ActivityLifecycleCall

  • Android中Activity滑动关闭的效果

    最近感觉有一个Activity关闭的效果挺不错的,就是手势滑动就可以关闭当前Activity,于是就想写一篇博客和大家一起分享下!废话不多说,老规矩,还先上效果图,更直观! 项目地址:https://github.com/xinyitiandi/SlidingFinishDemo 上代码: 1.第一个Activity: package com.ekeguan.slidingfinishdemo; import android.content.Intent; import android.os.B

  • android中使用Activity实现监听手指上下左右滑动

    用Activity的onTouchEvent方法实现监听手指上下左右滑动 应用了Activity的ontouchEvent方法监听手指点击事件,手指滑动的时候会先按下,滑倒另一个地方再抬起,我们就可以根据按下的坐标和抬起的坐标算出用户是往哪一个方向滑动了. package com.example.testtt; import android.app.Activity; import android.os.Bundle; import android.view.MotionEvent; impor

  • Android实现图片自动轮播并且支持手势左右无限滑动

    废话不多说了,先给大家上左右无限滑动的代码了. 1.左右无限滑动 public class MainActivity extends AppCompatActivity { private static ViewPager viewPager; private RadioGroup group; //图片资源,实际项目需要从网络获取 private int[] imageIds = {R.drawable.ym1, R.drawable.ym2, R.drawable.ym3, R.drawab

  • Android实现手势滑动多点触摸缩放平移图片效果

    现在app中,图片预览功能肯定是少不了的,用户基本已经形成条件反射,看到小图,点击看大图,看到大图两个手指开始进行放大,放大后,开始移动到指定部位. 一.概述 想要做到图片支持多点触控,自由的进行缩放.平移,需要了解几个知识点:Matrix , GestureDetector , ScaleGestureDetector 以及事件分发机制,ps:不会咋办,不会你懂的. 1.Matrix 矩阵,看深入了都是3维矩阵的乘啊什么的,怪麻烦的~~ 其实这么了解下就行了: Matrix 数据结构:3维矩阵

  • Android实现手势滑动多点触摸放大缩小图片效果

    网上文章虽多,但是这种效果少之又少,我真诚的献上以供大家参考 实现原理:自定义ImageView对此控件进行相应的layout(动态布局). 这里你要明白几个方法执行的流程: 首先ImageView是继承自View的子类. onLayout方法:是一个回调方法.该方法会在在View中的layout方法中执行,在执行layout方法前面会首先执行setFrame方法. setFrame方法:判断我们的View是否发生变化,如果发生变化,那么将最新的l,t,r,b传递给View,然后刷新进行动态更新

随机推荐