手势滑动结束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 】
相关推荐:
Laravel Vite是做什么的_Laravel前端资源打包工具Vite配置与使用
JavaScript如何实现倒计时_时间函数如何精确控制
javascript中数组(Array)对象和字符串(String)对象的常用方法总结
android nfc常用标签读取总结
Laravel Seeder怎么填充数据_Laravel数据库填充器的使用方法与技巧
如何实现javascript表单验证_正则表达式有哪些实用技巧
中山网站制作网页,中山新生登记系统登记流程?
如何在建站之星绑定自定义域名?
如何用y主机助手快速搭建网站?
Laravel如何使用Eloquent ORM进行数据库操作?(CRUD示例)
如何在香港免费服务器上快速搭建网站?
佛山网站制作系统,佛山企业变更地址网上办理步骤?
PHP 500报错的快速解决方法
Android实现代码画虚线边框背景效果
高配服务器限时抢购:企业级配置与回收服务一站式优惠方案
iOS验证手机号的正则表达式
香港服务器部署网站为何提示未备案?
如何在云主机上快速搭建多站点网站?
广州网站制作公司哪家好一点,广州欧莱雅百库网络科技有限公司官网?
潮流网站制作头像软件下载,适合母子的网名有哪些?
Android okhttputils现在进度显示实例代码
专业企业网站设计制作公司,如何理解商贸企业的统一配送和分销网络建设?
如何快速搭建安全的FTP站点?
Laravel如何处理文件上传_Laravel Storage门面实现文件存储与管理
canvas 画布在主流浏览器中的尺寸限制详细介绍
如何快速搭建二级域名独立网站?
Java垃圾回收器的方法和原理总结
Laravel怎么进行数据库回滚_Laravel Migration数据库版本控制与回滚操作
如何正确选择百度移动适配建站域名?
如何用景安虚拟主机手机版绑定域名建站?
Laravel Eloquent访问器与修改器是什么_Laravel Accessors & Mutators数据处理技巧
jQuery 常见小例汇总
电商网站制作多少钱一个,电子商务公司的网站制作费用计入什么科目?
Laravel Seeder填充数据教程_Laravel模型工厂Factory使用
惠州网站建设制作推广,惠州市华视达文化传媒有限公司怎么样?
Laravel如何实现用户注册和登录?(Auth脚手架指南)
Laravel Eloquent关联是什么_Laravel模型一对一与一对多关系精讲
Laravel怎么配置.env环境变量_Laravel生产环境敏感数据保护与读取【方法】
Python结构化数据采集_字段抽取解析【教程】
HTML透明颜色代码怎么让图片透明_给img元素加透明色的技巧【方法】
如何自定义safari浏览器工具栏?个性化设置safari浏览器界面教程【技巧】
javascript和jQuery中的AJAX技术详解【包含AJAX各种跨域技术】
悟空识字怎么关闭自动续费_悟空识字取消会员自动扣费步骤
安克发布新款氮化镓充电宝:体积缩小 30%,支持 200W 输出
如何制作新型网站程序文件,新型止水鱼鳞网要拆除吗?
重庆市网站制作公司,重庆招聘网站哪个好?
微信推文制作网站有哪些,怎么做微信推文,急?
Bootstrap CSS布局之列表
UC浏览器如何设置启动页 UC浏览器启动页设置方法
高防网站服务器:DDoS防御与BGP线路的AI智能防护方案

