前言

是否在不蕴含侧滑菜单的时候,增加一个侧滑返回,边缘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就被完整的显示进去了。

外围代码:

@Overridepublic 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办法解决的:

@Overridepublic 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里:@Overridepublic 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的,最初代码如下:

@Overridepublic 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里:@Overridepublic 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是在其根底上拓展的一个库,用于实现滑动返回性能,能够用于各种我的项目构造。

相干教程

Android根底系列教程:
Android根底课程U-小结_哔哩哔哩_bilibili
Android根底课程UI-布局_哔哩哔哩_bilibili
Android根底课程UI-控件_哔哩哔哩_bilibili
Android根底课程UI-动画_哔哩哔哩_bilibili
Android根底课程-activity的应用_哔哩哔哩_bilibili
Android根底课程-Fragment应用办法_哔哩哔哩_bilibili
Android根底课程-热修复/热更新技术原理_哔哩哔哩_bilibili

本文转自 https://juejin.cn/post/7038445433965772813,如有侵权,请分割删除。