喜欢听音乐的朋友可能都看过天天动听这款 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,布局和測量就显得非常easy。

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;
}

由于这里增加了多点控制,所以代码看起来有点复杂。事实上原理非常easy。就是不断的推断是否符合滑动的条件。其它就不细讲了。来看看这个自己定义 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 这事就变的非常easy了

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"
/>

再来看看效果,效果图:

完美实现!

好了,今天就到这里,下期文章就是对这个功能的进一步优化和改善,假设感兴趣,能够继续关注我。

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

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

    近期更新了微信版本号到6.2.发现里面有个很好的体验,就是在第二个页面Activity能手势向右滑动返回,在手势滑动的过程中能看到第一个页面,这样的体验很赞,这里高仿了一下. 这里使用的是v4包里面的 ...

  2. css 的通用样式 设置 和倒计时功能 移动轮播图的手势滑动的功能

    body{ line-height:1.4; color:#333; font-family:arial; font-size: 12px; background:white; } input,tex ...

  3. 再谈iOS 7的手势滑动返回功能

    本文转载至 http://blog.csdn.net/jasonblog/article/details/28282147  之前随手写过一篇<使用UIScreenEdgePanGestureR ...

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

    iOS中如果不自定义UINavigationBar,通过手势向右滑是可以实现返回的,这时左边的标题文字提示的是上一个ViewController的标题,如果需要把文字改为简约风格,例如弄过箭头返回啥的 ...

  5. 禁用ios7 手势滑动返回功能

    禁用ios7 手势滑动返回功能 版权声明:本文为博主原创文章,未经博主允许不得转载. 在有的时候,我们不需要手势返回功能,那么可以在页面中添加以下代码: - (void)viewDidAppear:( ...

  6. iOS之手势滑动返回功能

    iOS中如果不自定义UINavigationBar,通过手势向右滑是可以实现返回的,这时左边的标题文字提示的是上一个ViewController的标题,如果需要把文字改为简约风格,例如弄过箭头返回啥的 ...

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

    转载请标明出处: http://blog.csdn.net/hanhailong726188/article/details/46453627 本文出自:[海龙的博客] 一.概述 在上一篇博文中,博文 ...

  8. iOS 7的手势滑动返回

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

  9. 滑动关闭activity

    // 手指上下滑动时的最小速度 private static final int YSPEED_MIN = 1000; // 手指向右滑动时的最小距离 private static final int ...

随机推荐

  1. BZOJ 1005 明明的烦恼 (组合数学)

    题解:n为树的节点数,d[ ]为各节点的度数,m为无限制度数的节点数. 则               所以要求在n-2大小的数组中插入tot各序号,共有种插法: 在tot各序号排列中,插第一个节点的 ...

  2. js中this的深入研究

    this对象是函数在运行时由调用函数的对象决定的: 1.在全局对象中运行函数时,this等于window 2.当函数被作为某个对象的方法调用时, this等于那个对象. 需要注意的几点: 声明函数里的 ...

  3. Maven导入时,Cannot change version of project facet Dynamic Web Module to 3.0.

    今天手贱,在eclipse里面把项目删掉了,重新maven导入时,报出Cannot change version of project facet Dynamic Web Module to 3.0. ...

  4. IntelliJ IDEA 14 注册码生成器

    IntelliJ IDEA 14 注册码生成器 文件为Java代码 自己编译运行里面的程序输入名称然后就生成注册码了工具:http://yun.baidu.com/s/1cZKsA部分工具生成的注册码 ...

  5. 快速构建ASP.NET MVC Admin主页

    前言 后台开发人员一般不喜欢调样式,搞半天样式出不来,还要考虑各种浏览器兼容,费心费力不讨好,还好互联网时代有大量的资源共享,避免我们从零开始,现在就来看怎么快速构建一个ASP.NET MVC后台管理 ...

  6. Thread.Sleep(0)的意义 操作系统中CPU的竞争策略

    在线程的学习中遇到的 不太明白就搜了一下  有一篇觉得写得很好的分享一下 转载:http://www.360doc.com/content/12/1220/07/1054746_255212714.s ...

  7. JavaSE学习总结第06天_Java语言基础2 & 面向对象1

      06.01 二维数组概述和格式1的讲解 二维数组概述:二维数组其实就是一个元素为一维数组的数组 格式1:数据类型[][] 变量名 = new 数据类型[m][n]; m表示这个二维数组有多少个一维 ...

  8. LNMP下基于端口的虚拟主机配置

    1.在/usr/local/nginx/conf/nginx.conf文件的的最后一个"}"前加上 include vhost/*.conf; 2.在/usr/local/ngin ...

  9. Iframe知识点

    var oIframe=document.getElementById('iframe1'); 获取iframe对象:   oIframe.contentWindow.//  iframe 里的win ...

  10. zepto API参考(~~比较全面)

    Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jquery有着类似的api. 如果你会用jquery,那么你也会用zepto. 设计的目的是提供jquery的类似的APIs ...