手势滑动结束 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"
            />

再来看看效果,效果图:

完美实现!

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

时间: 2024-10-22 22:50:44

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

滑动结束 Activity(二)阴影效果和动画的实现

上一篇文章中讲了如何实现手势滑动来销毁页面,再来回顾一下实现的效果 具体实现请看上一篇文章 手势滑动结束 Activity(一)基本功能的实现,不过这只是实现了最基本的功能,还有很多地方需要优化和完善的,这篇文章主要是在原来实现的基础上做优化和特效: 先来看效果: 1.效果图1:侧滑显示阴影 2.效果图2:改变滑动动画效果 先来看看效果图1的实现方式: 根据效果图,我们应该能想到他的实现原理,就是在手势不断的滑动的同时绘制已经滑走部分的颜色,并且不断改变透明度. 所以我们需要重写 ViewGro

Android-通过SlidingPaneLayout高仿微信6.2最新版手势滑动返回(一)

最近更新了微信版本到6.2,发现里面有个非常好的体验,就是在第二个页面Activity能手势向右滑动返回,在手势滑动的过程中能看到第一个页面,这种体验非常赞,这里高仿了一下.这里使用的是v4包里面的SlidingPaneLayout来手势滑动,在下一篇博文中将采用SlidingMenu来高仿,下面是SlidingPaneLayout高仿后的效果,效果还是蛮不错的. 最重要的是,每一个页面都是Activity,而非Fragment哦,使用Activity和正常的Activity一样 这里给出dem

iOS 7的手势滑动返回功能

现在使用默认模板创建的iOS App都支持手势返回功能,如果导航栏的返回按钮是自定义的那么则会失效,也可以参考这里手动设置无效. if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.navigationController.interactivePopGestureRecognizer.enabled = NO; } 如果是因为自定义导航按钮而导

iOS之手势滑动返回功能-b

iOS中如果不自定义UINavigationBar,通过手势向右滑是可以实现返回的,这时左边的标题文字提示的是上一个ViewController的标题,如果需要把文字改为简约风格,例如弄过箭头返回啥的,那么你需要自定义UINavigationBar,但当你自定义navigationBar后,这个功能就会自动失效. 屏蔽右滑返回功能代码:   if ([self.navigationController respondsToSelector:@selector(interactivePopGest

Android 随手势滑动销毁(finish)Activity

今天给大家带来一个向右滑动销毁Activity的效果,Activtiy随着手指的移动而销毁,滑动销毁Activity主要使用GestureDetector来实现这个效果,当手势在屏幕上面滑动的时候 ,会掉用onFling方法,所以,在这个方法里面做判断和操作即可实现我们想要的效果. 首先看实现的最终效果: 好了直接上代码,相信你会看懂的 1.主页面Activity package com.sunny.slidingfinish; import android.os.Bundle; import 

Android-通过SlidingMenu高仿微信6.2最新版手势滑动返回(二)

转载请标明出处: http://blog.csdn.net/hanhailong726188/article/details/46453627 本文出自:[海龙的博客] 一.概述 在上一篇博文中,博文地址Android-通过SlidingPaneLayout高仿微信6.2最新版手势滑动返回(一),我们是通过官方自带的SlidingPaneLayout来实现的手势滑动返回.在这篇博文中,我们将採用SlidingMenu来高仿. 事实上实现的原理都一样.仅仅只是是把SlidingPaneLayout

iOS 7的手势滑动返回

如今使用默认模板创建的iOS App都支持手势返回功能,假设导航栏的返回button是自己定义的那么则会失效,也能够參考这里手动设置无效. [cpp] view plaincopy if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.navigationController.interactivePopGestureRecognizer.enab

Android 手势滑动的识别

提要: 对于Android中的手势识别可以从以下三个Listener入手--OnTouchListener.OnGestureListener.OnDoubleTapListener.这三个监听器分别是触摸监听.手势滑动监听和屏幕双击操作监听.很多的时候我们需要这些手势识别的操作,例如我们自定义控件的时候就经常会用到.下面就对这三个监听器分别进行介绍. 触摸监听器OnTouchListener 让我们的Activity去现实此接口,并重写onTouch方法.重写OnTouchListener的o

手势滑动和自定意控件

如图当我点击下一个时会跳转到别个界面,当我用手势向右滑动的时候也调转到下一页 其中点击最上面的RelativeLayout则CheckBox会被点击,并用SharedPreferences来记录是否被点击了 package com.org.demo.wangfeng.demo; import com.org.wangfeng.R; import android.app.Activity; import android.app.AlertDialog; import android.app.Ale