乐趣区

关于android:Android开发实现滑动退出-Fragment-Activity-二合一

前言

是否在不蕴含侧滑菜单的时候,增加一个侧滑返回,边缘 finish 以后 Fragment?

明天把这项工作实现了,做成了独自的 SwipeBackFragment 库以及 Fragmentation-SwipeBack 拓展库

个性:
1、SwipeBackFragment , SwipeBackActivity 二合一:当 Activity 内的 Fragment 数大于 1 时,滑动 finish 的是 Fragment,如果小于等于 1 时,finish 的是 Activity。

2、反对左、右、左 & 右滑动(将来可能会减少更多滑动区域)

3、反对 Scroll 中的滑动监听

4、帮你解决了 app 被零碎强杀后引起的 Fragment 重叠的状况

成果

效果图

谈谈实现

拖拽局部大部分是靠 ViewDragHelper 来实现的,ViewDragHelper 帮咱们解决了大量 Touch 相干事件,以及对速度、开释后的一些逻辑监控,大大简化了咱们对触摸事件的解决。(本篇不对 ViewDragHelper 做具体介绍,有不相熟的小伙伴能够自行查阅相干文档)

对 Fragment 以及 Activiy 的滑动退出,原理是一样的,都是在 Activity/Fragment 的视图上,增加一个父 View:SwipeBackLayout,该 Layout 里创立 ViewDragHelper,管制 Activity/Fragment 视图的拖拽。

1、Activity 的实现

对于 Activity 的 SwipeBack 实现,网上有大量剖析,这里我简要介绍下原理,如下图:

咱们只有保障 SwipeBackLayout、DecorView 和 Window 的背景是通明的,这样拖拽 Activity 的 xml 布局时,能够看到上个 Activity 的界面,把布局滑走时,再 finish 掉该 Activity 即可。

public void attachToActivity(FragmentActivity activity) {
    ...
    ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
    ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
    decorChild.setBackgroundResource(background);
    decor.removeView(decorChild);  // 移除 decorChild
    addView(decorChild);        // 增加 decorChild 到 SwipeBackLayout(FrameLayout)
    setContentView(decorChild);
    decor.addView(this);}        // 把 SwipeBackLayout 增加到 DecorView 下

2、Fragment 的实现

重点来了,Fragment 的实现!
在实现前,我先阐明 Fragment 的几个相干知识点:

1、Fragment 的视图局部其实就是在 onCreateView 返回的 View;

**2、同一个 Activity 里的多个通过 add 装载的 Fragment,他们在视图层是叠加下来的:
hide()并不销毁视图,仅仅让视图不可见,即 View.setVisibility(GONE);
show() 让视图变为可见,即View.setVisibility(VISIBLE);;**

add+show/hide 的状况

3、通过 replace 装载的 Fragment,他们在视图层是替换的,replace()会销毁以后的 Fragment 视图,即回调 onDestoryView,返回时,从新创立视图,即回调 onCreateView;

replace 的状况

4、不论 add 还是 replace,Fragment 对象都会被 FragmentManager 保留在内存中,即便 app 在后盾因系统资源有余被强杀,FragmentManager 也会为你保留 Fragment,当重启 app 时,咱们能够从 FragmentManager 中获取这些 Fragment。

剖析:

Fragment 之间的启动无非下图中的 2 种:

而这个库我并没有思考 replace 的状况,因为咱们的 SwipeBackFragment 应该是在 ” 流式 ” 应用的场景(FragmentA -> FragmentB ->….),而这种场景下联合下面的 2、3、4 条,add+show(),hide()无疑更优于 replace,性能更佳、响应更快、咱们 app 的代码逻辑更简略。

add+hide 的形式的实现

从第 1 条,咱们能够晓得 onCreateView 的 View 就是须要放入 SwipeBackLayout 的子 View,咱们给该子 View 一个背景色,而后 SwipeBackLayout 通明,这样在拖拽时,即可看到 ” 上个 Fragment”。

当咱们拖拽时,上个 Fragment A 的 View 是 GONE 状态,所以咱们要做的就是当判断拖拽产生时,Fragment A 的 View 设置为 VISIBLE 状态,这样拖拽的时候,上个 Fragment A 就被完整的显示进去了。

外围代码:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View view = inflater.inflate(...);
    return attachToSwipeBack(view);
}

protected View attachToSwipeBack(View view) {mSwipeBackLayout.addView(view);
    mSwipeBackLayout.setFragment(this, view);
    return mSwipeBackLayout;
}

然而相比 Activity,上个 Activity 的视图状态是 VISIBLE 的,而咱们的上个 Fragment 的视图状态是 GONE 的,所以咱们须要FragmentA.getView().setVisibility(VISIBLE),然而机会是什么时候呢?

最好的计划是开始拖拽前的那一刻,我是在 ViewDragHelper 里的 tryCaptureView 办法解决的:

@Override
public boolean tryCaptureView(View child, int pointerId) {boolean dragEnable = mHelper.isEdgeTouched(ViewDragHelper.EDGE_LEFT);
    if (mPreFragment == null) {if (dragEnable && mFragment != null) {
            ... 省略获取上一个 Fragment 代码
            mPreFragment = fragment;
            mPreFragment.getView().setVisibility(VISIBLE);
            break;
        }
    } else {View preView = mPreFragment.getView();
       if (preView != null && preView.getVisibility() != VISIBLE) {preView.setVisibility(VISIBLE);
       }
    }
    return dragEnable;
}

通过下面代码,咱们拖拽以后 Fragment 前的一瞬间,PreFragment 的视图会被 VISIBLE,同时齐全不会影响 onHiddenChanged 办法,完满。(到这之前可能有小伙伴想到,只通过 add 不 hide 上个 Fragment 的思路怎么样?很显著是不行的,因为这样的话 onHiddenChanged 办法不会被回调,而咱们应用 add 的形式,次要通过 onHiddenChanged 来作为“生命周期”来实现咱们的逻辑的)

还一种状况须要留神,当我曾经开始拖拽 FragmentB 打算 pop 时,拖拽到一半我放弃了,这时 FragmentA 的视图曾经是 VISIBLE 状态,我又从 B 进入到 Fragment C,这是咱们应该把 A 的视图 GONE 掉:

SwipeBackFragment 里:@Override
public void onHiddenChanged(boolean hidden) {super.onHiddenChanged(hidden);
    if (hidden && mSwipeBackLayout != null) {mSwipeBackLayout.hiddenFragment();
    }
}

SwipeBackLayout 里:
public void hiddenFragment() {if (mPreFragment != null && mPreFragment.getView() != null) {mPreFragment.getView().setVisibility(GONE);
    }
}

坑点

1、触摸事件抵触

当咱们所拖拽的边缘区域中的子 View,有其余 Touch 事件,比方 Click 事件,这时咱们会发现咱们的拖拽生效了,这是因为,如果子 View 不耗费事件,那么整个 Touch 流程间接走 onTouchEvent,在 onTouchEvent 的 DOWN 的时候就确定了 CaptureView。如果子 View 耗费事件,那么就会先走 onInterceptTouchEvent 办法,判断是否能够捕捉,而在这过程中会去判断另外两个回调的办法:getViewHorizontalDragRange 和 getViewVerticalDragRange,只有这两个办法返回大于 0 的值能力失常的捕捉;

并且你须要思考以后拖拽的页面下是有 2 个 SwipeBackLayout:以后 Fragment 的和 Activity 的,最初代码如下:

@Override
public int getViewHorizontalDragRange(View child) {if (mFragment != null) {return 1;} else {if (mActivity != null && mActivity.getSupportFragmentManager().getBackStackEntryCount() == 1) {return 1;}
    }
    return 0;
}

这样的话,一方面解决了事件抵触,一方面实现了 Activity 内 Fragment 数量大于 1 时,拖拽的是 Fragment,等于 1 时拖拽的是 Activity。

2、动画

咱们须要在拖拽实现时,将 Fragment/Activity 移出屏幕,紧接着敞开,最重要的是要保障以后 Fragment/Actiivty 敞开和上一个 Fragment/Activity 进入时是无动画的!

对于 Activity 这项工作很简略:Activity.overridePendingTransition(0, 0)即可。

对于 Fragment,如果自身在 Fragment 跳转时,就不为其设置转场动画,那就能够间接应用了;
如果你应用了 setCustomAnimations(enter,exit) 或者setCustomAnimations(enter,exit,popenter,popexit),你能够这样解决:

SwipeBackLayout 里:{
    mPreFragment.mLocking = true;
    mFragment.mLocking =true;
    mFragment.getFragmentManager().popBackStackImmediate();
    mFragment.mLocking = false;
    mPreFragment.mLocking = false;
}

SwipeBackFragment 里:@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {if(mLocking){return mNoAnim;}
    return super.onCreateAnimation(transit, enter, nextAnim);
}

3、启动新 Fragment 时,不要调用 show()

getSupportFragmentManager().beginTransaction()
             .setCustomAnimations(xxx)
             .add(xx, B)
//             .show(B)
             .hide(A)
             .commit();

请不要调用上述代码里的 show(B)
一方面是新 add 的 B 自身就是可见状态,不论你是 show 还是不调用 show,都不会回调 B 的 onHiddenChanged 办法;
另一方面,如果你调用了 show,滑动返回会后出现异常行为,回到 PreFragment 时,PreFragment 的视图会是 GONE 状态;如果你非要调用 show 的话,请按上面的形式解决:(没必要的话,还是不要调用 show 了,上面的代码可能会产生闪动)

@Overridepublic void onHiddenChanged(boolean hidden) {super.onHiddenChanged(hidden);
    if (!hidden && getView().getVisibility() != View.VISIBLE) {getView().post(new Runnable() {
            @Override
            public void run() {getView().setVisibility(View.VISIBLE);
            }
        });
    }
}

最初

我为什么把这个库做成 2 个,一个独自应用的 SwipeBackFragment 和一个 Fragmentation-SwipeBack 拓展库呢?

起因在于:
SwipeBackFragment 库是一个仅实现 Fragment&Activity 拖拽返回的根底库,适宜轻度应用 Fragment 的小伙伴(我的项目属于多 Activity+ 多 Fragment,Fragment 之间没有简单的逻辑),当然你也能够随便拓展。

Fragmentation 次要是在我的项目构造为 单 Activity+ 多 Fragment,或者重度应用 Fragment 的多 Activity+ 多 Fragment 构造时的一个 Fragment 帮忙库,Fragment-SwipeBack 是在其根底上拓展的一个库,用于实现滑动返回性能,能够用于各种我的项目构造。

退出移动版