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

来源:互联网 发布:淘宝衣服手机拍照技巧 编辑:程序博客网 时间:2024/05/16 05:21

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

再来看看效果,效果图:
这里写图片描述

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

8 1
原创粉丝点击