SwipeBackLayout

其实github上已经有这个开源库了,我是个菜鸟,我喜欢用开源库,同时也非常好奇它的实现原理。很多大神写的代码注释都特别少,可能是他们觉得很简单就懒得写了,这点对新手来讲就有点坑爹了;所以我只是借助大神的代码向大家讲解下这个的实现原理。介绍原理之前我先说下原创的问题,老实讲我博客上讲的东西以前绝对有人写过,很多别人写的很好,而我只是站在他们的肩膀上帮助下新手。我真的不喜欢那些很绕的代码,我喜欢来的直一点的,写出让超新手都能看的懂的代码,因为其中注释代码的程度到了令人发指的地步(大神可以无视)。

代码

关键之处还是在于自定义控件SwipeBackLayout这里。

public class SwipeBackLayout extends FrameLayout {/*** SwipeBackLayout的主布局*/private View mContentView;/*** 是一个距离,表示滑动的时候手的移动要大于这个距离才开始移动    控件。如果小于这个距离就不触发移动控件,* 如 viewpager就是用这个距离来判断用户是否翻页,这个距离打印出来是16px*/private int mTouchSlop;/*** 手指点击屏幕时的Y坐标*/private int downY;/*** 手指点击屏幕时的X坐标*/private int downX;/*** 手指点击屏幕时,临时的X坐标*/private int tempX;/*** Android里 Scroller类是为了实现View平滑滚动的一个Helper类*/private Scroller mScroller;/*** 手机屏幕的宽度*/private int viewWidth;/*** 表示屏幕是否正在滑动的标记*/private boolean isSilding;/*** 表示是否finish掉当前的activity*/private boolean isFinish;/*** 获取系统资源的 drawable文件,带有阴影的*/private Drawable mShadowDrawable;/*** SwipeBackLayout依附的activity*/private Activity mActivity;/*** 当前activity里所存在的 viewpager的集合*/private List<ViewPager> mViewPagers = new LinkedList<ViewPager>();// 创建一个空的 viewpager的集合/*** 是否对手势进行拦截的设置,默认为true。若为false,则SwipeBackLayout这个 viewgroup对手势不拦截不消费*/private boolean mEnable = true;public SwipeBackLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}public SwipeBackLayout(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); // 是一个距离,表示滑动的时候手的移动要大于这个距离才开始移动控件。如果小于这个距离就不触发移动控件,如 viewpager就是用这个距离来判断用户是否翻页Log. d("xiao" , "mTouchSlop:" + mTouchSlop );mScroller = new Scroller(context); // Android里Scroller 类是为了实现View平滑滚动的一个Helper类mShadowDrawable = getResources().getDrawable(R.drawable.shadow_left );// 获取系统资源的 drawable文件}/*** 把swipeBackLayout附加到指定的activity中,放到 decor顶层窗口下,decorChild上* 这样做的作用就是,让SwipeBackLayout附加到任何activity时,就立于此activity主视图之上** @param activity*/public void attachToActivity(Activity activity) {mActivity = activity; // 传进来的activityint[] attrs = new int[] { android.R.attr.windowBackground };// 返回一个与主题Theme定义的 attrs数组对应的typedArray类型数组TypedArray a = activity.getTheme().obtainStyledAttributes(attrs);// 获取typedArray数组中指定位置的资源id值int background = a.getResourceId(0, 0);// 回收TypedArray类型数组a.recycle();// 返回顶层窗口装饰视图ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();// 返回装饰视图的指定位置的view,就是 decor的child,很形象ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);// 给顶层窗口装饰视图的第一个子视图设置背景资源,// background就是上面获得的android.R.attr.windowBackgrounddecorChild.setBackgroundResource(background);// decor顶层窗口装饰视图移除decorChild,杀了他的儿子decor.removeView(decorChild);// 这个应该是给SwipeBackLayout添加子view,因为SwipeBackLayout继承自FrameLayout// 这时,SwipeBackLayout就是decorChild的父布局了this.addView(decorChild);// 把decorChild当成SwipeBackLayout的contentView进行设置this.setContentView(decorChild);// 然后给decor添加一个子view,这个this就是SwipeBackLayoutdecor.addView( this);}/*** 设置主布局视图** @param decorChild*/private void setContentView(View decorChild) {mContentView = (View) decorChild.getParent(); // 返回decorChild的父布局,这个时候父布局就是SwipeBackLayout}/*** 设置手势** @param enable*/public void setEnableGesture( boolean enable) {mEnable = enable;}/*** 事件拦截操作*/@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {if (! mEnable) {// false表示不拦截return false;}// 处理ViewPager冲突问题ViewPager mViewPager = getTouchViewPager( mViewPagers, ev);// 获取到触摸地方的 viewpagerif (mViewPager != null && mViewPager.getCurrentItem() != 0) {// 当viewpager 不为空且viewpager不处在第一个item时,swipeBackLayout就不拦截return super.onInterceptTouchEvent(ev); // 默认返回false,表示不拦截}switch (ev.getAction()) {case MotionEvent. ACTION_DOWN:downX = tempX = ( int) ev.getRawX(); // 当点击时候的X坐标downY = ( int) ev.getRawY(); // 当点击时候的Y坐标break;case MotionEvent. ACTION_MOVE:int moveX = ( int) ev.getRawX();// 满足此条件屏蔽SildingFinishLayout里面子类的touch事件if (moveX - downX > mTouchSlop&& Math. abs((int) ev.getRawY() - downY) < mTouchSlop) {// 当水平移动的距离大于16px,且竖直方向的移动距离小于16px时,SwipeBackLayout拦截此次触摸事件// 就是说当手指move的时候,满足上述条件时,触摸事件就会被SwipeBackLayout的onInterceptTouchEvent方法拦截// 继而传递给SwipeBackLayout的onTouchEvent方法return true;}break;}return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {if (! mEnable) {return false;}switch (event.getAction()) {case MotionEvent. ACTION_MOVE:int moveX = ( int) event.getRawX(); // 移动后的X坐标int deltaX = tempX - moveX; // 这个是点击时和移动后,X坐标差值Log. d("xiao" , "deltaX:" + deltaX);// 右滑为负值tempX = moveX; // 给tempX重新赋值if (moveX - downX > mTouchSlop&& Math. abs((int) event.getRawY() - downY) < mTouchSlop) {// 满足上述条件时,触摸事件由onInterceptTouchEvent传递至onTouchEvent方法中isSilding = true; // 标记为正在滑动}if (moveX - downX >= 0 && isSilding) {// 当右滑且处于正在滑动的时候,主布局通过scrollBy整体移动,且通过打印deltaX为负值mContentView.scrollBy(deltaX, 0);}break;case MotionEvent. ACTION_UP:isSilding = false; // 讲滑动标记设置为falseLog. d("xiaok" , "mContentView:" + mContentView.getScrollX());Log. d("xiaok" , "viewWidth:" + viewWidth );if ( mContentView.getScrollX() <= - viewWidth / 2) {// 当右滑距离超过屏幕宽度的一半时,标记isFinish为true表示滚动出界面,然后滚动出界面isFinish = true;scrollRight();} else {// 否则界面回滚至原点,标记isFinish为falsescrollOrigin();isFinish = false;}break;}return true;}/*** 获取SwipeBackLayout里面的ViewPager的集合,这里用到的好像是递归思想** @param mViewPagers* @param parent*/private void getAlLViewPager(List<ViewPager> mViewPagers, ViewGroup parent) {int childCount = parent.getChildCount();for ( int i = 0; i < childCount; i++) {View child = parent.getChildAt(i);if (child instanceof ViewPager) {mViewPagers.add((ViewPager) child);} else if (child instanceof ViewGroup) {getAlLViewPager(mViewPagers, (ViewGroup) child);}}}/*** 返回我们touch的ViewPager** @param mViewPagers* @param ev* @return*/private ViewPager getTouchViewPager(List<ViewPager> mViewPagers,MotionEvent ev) {if (mViewPagers == null || mViewPagers.size() == 0) {// 如果mViewPagers集合为空,或者mViewPagers的size=0,那么直接返回空return null;}Rect mRect = new Rect(); // 创建一个新的空矩形。所有坐标被初始化为0。for (ViewPager v : mViewPagers) { // 遍历mViewPagers集合,判断我现在触摸的地方是不是在某一个 viewpager范围里// 如果在,那么就返回这个 viewpagerv.getHitRect(mRect); // 获取每一个viewpager的矩形的坐标值,并赋值给mRect矩形if (mRect.contains(( int) ev.getX(), ( int) ev.getY())) {// 返回true,如果(x,y)坐标在mRect矩形的范围内return v; // 返回viewpager}}return null; // 否则返回空}@Overrideprotected void onLayout( boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);// 不明白为什么onLayout方法执行了两次Log. d("xiao" , "changed:" + changed);if (changed) {viewWidth = this.getWidth();Log. d("xiao" , "viewWidth:" + viewWidth );getAlLViewPager( mViewPagers, this); // 获得SwipeBackLayout中的ViewPager的集合}}@Overrideprotected void dispatchDraw(Canvas canvas) { // 当需要绘制子view的时候,才会调用此方法super.dispatchDraw(canvas);/*** 这个方法是用来绘制右滑退出时,SwipeBackLayout左侧的那个阴影效果*/if ( mShadowDrawable != null && mContentView != null) {int left = mContentView.getLeft()- mShadowDrawable.getIntrinsicWidth();int right = left + mShadowDrawable.getIntrinsicWidth();int top = mContentView.getTop();int bottom = mContentView.getBottom();mShadowDrawable.setBounds(left, top, right, bottom);mShadowDrawable.draw(canvas);}}/*** 滚动出界面*/private void scrollRight() {/*** 这里解释下getScrollX的意思:返回视图左边缘的X坐标,但是是反向的X轴,就是值是相反的*/final int delta = ( viewWidth + mContentView.getScrollX());/*** 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item* 当 dx的值为正数时view向左滑动*/Log. d("xiao" , "delta:" + delta);mScroller.startScroll( mContentView.getScrollX(), 0, -delta + 1, 0,Math. abs(delta));postInvalidate();}/*** 滚动到起始位置*/private void scrollOrigin() {int delta = mContentView.getScrollX();Log. d("xiao" , "1delta:" + delta);mScroller.startScroll( mContentView.getScrollX(), 0, -delta, 0,Math. abs(delta));postInvalidate();}@Overridepublic void computeScroll() {/*** 当我们执行 ontouch或invalidate()或postInvalidate()都会导致这个copmuteScroll方法的执行* 所以底下加一个判断,computeSrcollOffset是在startScroll方法启动时就会返回true*/// 调用startScroll的时候scroller.computeScrollOffset()返回true,从文字上理解是计算偏移量if ( mScroller.computeScrollOffset()) {mContentView.scrollTo( mScroller.getCurrX(), mScroller.getCurrY());postInvalidate();if ( mScroller.isFinished() && isFinish) { //// 当滑动结束,且当前view滑动出界面时,执行activity,finish的命令mActivity.finish();}}}
}

说实话,稍微有点基础的,认真看下代码和注释就已经看的懂了。为了帮助新手能看懂我再进行三点讲解:
一、首先这个SwipeBackLayout继承FrameLayout,注意attachToActivity方法。这个方法就是让SwipeBackLayout包裹住我们XML里写的contentView这个步骤方便我们后面实现滑动finish的功能。图示如下:



二、重写SwipeBackLayout中的onInterceptTouchEvent和onTouchEvent方法,对手势进行监听。手势监听问题又涉及到了事件分发问题,这里只是简单说下。onInterceptTouchEvent方法表示父布局是否拦截当前事件,true表示拦截,false表示不拦截,且默认为不拦截。当手指向右滑动超过16px且上下滑动距离小于16px时让onIntecpetTouchEvent方法返回true,表示拦截。拦截的意思是,SwipeBackLayout自身处理这个滑动事件,就不会传递给子view了。

然后再onTouchEvent方法的手指触摸移动方法ACTION_MOVE中,根据不断算出move移动的距离来对我们的SwipeBackLayout进行scrollBy方法。

在手指抬起ACTION_UP中,判断是否右滑距离超过屏幕的一半,如果超过一半,则将SwipeBackLayout滚动出界面,finish掉当前activity,反之,则将SwipeBackLayout滚动回原点。
最后贴下scrollTo和scrollBy的区别,直接看源码!

三、注意横向滑动的事件冲突,如viewpager的右滑。当viewpager的currentItem不等于0的时候,右滑事件应该是让viewpager触发的,这个时候SwipeBackLayout是不应该对滑动事件进行拦截的。也就是onInterceptTouchEvent方法这个时候返回false,表示不拦截,把事件交给子view中的viewpager进行处理。

if (mViewPager != null && mViewPager.getCurrentItem() != 0) {// 当viewpager 不为空且viewpager不处在第一//个item时,swipeBackLayout就不拦截return super.onInterceptTouchEvent(ev); // 默认返回false,表示不拦截}

参考博文(里面可以下载demo源码大神写的)

http://blog.csdn.net/xiechengfa/article/details/45317503
最关键和难的部分我已经讲解完了,其他部分看看就好了。我注释的代码是在这里下载的。
其实原不原创对我来说无所谓,只要能帮助到大家就好了,我也不求你点赞,也不会像很多人那样直接一个字不改就复制粘贴,然后放到github上还骗人star。就这样吧。

自定义SwipeBackLayout控件实现右滑退出activity功能相关推荐

  1. 自定义treeview控件,实现右键菜单编辑功能

    试过用复合控件包含treeview控件,但是失败,这次在右键菜单调用tree的回发js成功可以在页面后台绑定被编辑的节点,后台3个函数都可以编辑用c#传的参数e.mynode就是右键菜单的选中的节点 ...

  2. WPF自定义仪表盘控件

    WPF自定义仪表盘控件 一.前言 二.功能实现 一.前言 在学习和工作中使用WPF时,都离不开自定义控件的使用,今天分享一个自己在学习过程中使用到的一个自定义仪表盘控件,感觉挺不错的,在这里分享给大家 ...

  3. android滑动背景变透明,Android右滑退出+沉浸式(透明)状态栏

    背景 上篇文章一个千万量级的APP使用的一些第三方库中,在说到一个使用很广泛的滑动退出库SwipeBackLayout时有提过有时间会分享自己在项目中引入这个库的时候填过的一些坑.前段时间项目加入沉浸 ...

  4. 安卓中自定义view控件代替radiogroup实现颜色渐变效果的写法

    利用自定义控件代替radiogroup,同时实现在使用viewpager进行翻页的时候,实现颜色渐变的效果. 一: 首先创建一个自定义view类继承自View类,所有的控件均用canvas绘制出来(包 ...

  5. WPF 滚动条控件ScrollViewer的使用及自定义滚动条控件(一)

    WPF 滚动条控件ScrollViewer的使用及自定义滚动条控件(一) 首先看一下两种空间的运行效果: 左边是自定义滑条控件,右边是自带的滑条控件: **滑条使用方法:**我们在ScrollView ...

  6. Android 手机卫士--自定义组合控件构件布局结构

    由于设置中心条目中的布局都很类似,所以可以考虑使用自定义组合控件来简化实现 本文地址:http://www.cnblogs.com/wuyudong/p/5909043.html,转载请注明源地址. ...

  7. Android View体系(十)自定义组合控件

    相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源 ...

  8. iOS自定义View 控件自动计算size能力

    iOS自定义View 控件自动计算size能力 背景 在使用 UILabel 和 UIImage 的时候,不用指定宽高约束,控件也不会报约束缺失,还可以根据内容自己确定适合的宽高,特别适合 Xib 和 ...

  9. VS2010 自定义用户控件未出现在工具箱的解决方案

    VS2010 自定义用户控件未出现在工具箱的解决方案 参考文章: (1)VS2010 自定义用户控件未出现在工具箱的解决方案 (2)https://www.cnblogs.com/lyout/arch ...

  10. [置顶] 分步实现具有分页功能的自定义DataList控件【附源代码】

    一.控件也是类 [效果] [操作步骤] 1.  新建网站Web 2.  添加类CustomDataList.cs(系统会提示你把类建在App_Code文件夹中),代码如下: using System; ...

最新文章

  1. office2007/2010/2013/2016安装出现错误:无法安装64位版本的office,因为在您的PC上......
  2. 正文处理命令及tar命令
  3. 图像分段线性变化_暗光也清晰的图像增强算法
  4. JVM监控及诊断工具-命令行篇一
  5. 使用alembic进行openstack数据库版本管理
  6. 手游频繁崩溃”闪退”? 从程序上找原因
  7. ML.NET 推荐引擎中一类矩阵因子分解的缺陷
  8. 【渝粤教育】国家开放大学2018年秋季 0233-21T学前儿童语言教育 参考试题
  9. Linux内核编译与安装[转]
  10. 呼吸灯 裸机 S3C2416
  11. 54.Linux/Unix 系统编程手册(下) -- POSIX 共享内存
  12. 数学之美:余弦定理和新闻分类
  13. Mbps、MB、Mb和Mb/s的含义与区别
  14. NovelAi + Webui + Stable-diffusion本地配置
  15. 在谷歌和ie上加入mp4格式的视频
  16. TDSQL“相似查询工具MSQL+”入选VLDB论文 1
  17. Echarts地图自定义图标Symbol同时动态更改图标进行切换显示
  18. Women in Tech | 关于职业与成长,她们给人奋进的启发和动力
  19. arp命令(windows ),nmap查看局域网内所有主机IP和MAC
  20. 秋招面试问题总结-视觉算法

热门文章

  1. 计算器c语言源代码全,C语言的计算器源代码
  2. xftp连接不上虚拟机linux,XFTP如何连接LINUX虚拟机
  3. 找了好久的数据库mysql中文乱码问题终于解决
  4. perl语言入门(小骆驼)学习(一)
  5. 一本书学会可视化设计 pdf_【推荐给设计师看的11本书】电子版PDF
  6. excel mysql乱码_excel打开是乱码的解法方法
  7. 低代码,想说爱你不容易
  8. php 翻译接口,翻译接口整理
  9. 【转】java对音频文件的频谱分析
  10. java环境变量配置不成功,已经解决