手势滑动结束Activity基本功能的实现(一)
发布时间 - 2026-01-11 01:42:43 点击率:次喜欢听音乐的朋友可能都看过天天动听这款 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" />
再来看看效果,效果图:
完美实现!
好了,今天就到这里,下期文章就是对这个功能的进一步优化和改善,如果感兴趣,可以继续关注我!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# 手势
# 滑动
# Activity
# Android通过滑动实现Activity跳转(手势识别器应用)
# Android实现手势滑动多点触摸放大缩小图片效果
# Android GestureDetector手势滑动使用实例讲解
# Android手势滑动实现ImageView缩放图片大小
# Android实现手势滑动多点触摸缩放平移图片效果
# Android实现图片自动轮播并且支持手势左右无限滑动
# Android中Activity滑动关闭的效果
# Android仿微信activity滑动关闭效果
# Android仿微信滑动退出Activity
# android中使用Activity实现监听手指上下左右滑动
# 自定义
# 这款
# 多点
# 绑定
# 都是
# 按下
# 涉及到
# 第一个
# 多个
# 来看看
# 有一个
# 的是
# 判断是否
# 我就
# 我要
# 在我
# 好了
# 还可以
# 下一页
# 也要
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
图片制作网站免费软件,有没有免费的网站或软件可以将图片批量转为A4大小的pdf?
公司门户网站制作流程,华为官网怎么做?
微信h5制作网站有哪些,免费微信H5页面制作工具?
Laravel Artisan命令怎么自定义_创建自己的Laravel命令行工具完全指南
bootstrap日历插件datetimepicker使用方法
PHP 500报错的快速解决方法
Laravel如何实现数据导出到PDF_Laravel使用snappy生成网页快照PDF【方案】
如何用搬瓦工VPS快速搭建个人网站?
微信小程序 HTTPS报错整理常见问题及解决方案
Laravel如何使用Contracts(契约)进行编程_Laravel契约接口与依赖反转
如何在 Telegram Web View(iOS)中防止键盘遮挡底部输入框
nginx修改上传文件大小限制的方法
如何用AWS免费套餐快速搭建高效网站?
网站制作软件有哪些,制图软件有哪些?
网站制作报价单模板图片,小松挖机官方网站报价?
如何在云主机上快速搭建网站?
Linux系统命令中screen命令详解
Angular 表单中正确绑定输入值以确保提交与验证正常工作
免费视频制作网站,更新又快又好的免费电影网站?
公司门户网站制作公司有哪些,怎样使用wordpress制作一个企业网站?
Laravel如何自定义错误页面(404, 500)?(代码示例)
Laravel中的Facade(门面)到底是什么原理
Laravel如何发送邮件_Laravel Mailables构建与发送邮件的简明教程
微信小程序 五星评分(包括半颗星评分)实例代码
佐糖AI抠图怎样调整抠图精度_佐糖AI精度调整与放大细化操作【攻略】
如何在Windows服务器上快速搭建网站?
Swift中循环语句中的转移语句 break 和 continue
如何制作新型网站程序文件,新型止水鱼鳞网要拆除吗?
香港服务器网站生成指南:免费资源整合与高速稳定配置方案
QQ浏览器网页版登录入口 个人中心在线进入
制作旅游网站html,怎样注册旅游网站?
教你用AI将一段旋律扩展成一首完整的曲子
中国移动官方网站首页入口 中国移动官网网页登录
Laravel怎么使用Blade模板引擎_Laravel模板继承与Component组件复用【手册】
作用域操作符会触发自动加载吗_php类自动加载机制与::调用【教程】
html5audio标签播放结束怎么触发事件_onended回调方法【教程】
Laravel如何使用Seeder填充数据_Laravel模型工厂Factory批量生成测试数据【方法】
如何在建站之星网店版论坛获取技术支持?
JavaScript 输出显示内容(document.write、alert、innerHTML、console.log)
Laravel Eloquent模型如何创建_Laravel ORM基础之Model创建与使用教程
Windows10如何删除恢复分区_Win10 Diskpart命令强制删除分区
JS中对数组元素进行增删改移的方法总结
北京的网站制作公司有哪些,哪个视频网站最好?
Laravel如何构建RESTful API_Laravel标准化API接口开发指南
如何自定义建站之星网站的导航菜单样式?
百度输入法ai组件怎么删除 百度输入法ai组件移除工具
黑客如何利用漏洞与弱口令入侵网站服务器?
bing浏览器学术搜索入口_bing学术文献检索地址
电商网站制作价格怎么算,网上拍卖流程以及规则?
Laravel如何部署到服务器_线上部署Laravel项目的完整流程与步骤

