Android Development: realize sliding exit Fragment + Activity two in one

Posted by sean14592 on Sat, 12 Feb 2022 03:31:36 +0100

preface

Can you add a sideslip return when the sideslip menu is not included, and finish the current Fragment?

Today, we have completed this work and made it into a separate SwipeBackFragment library and fragment SwipeBack extension library

characteristic:
1. Swipebackfragment, swipebackactivity two in one: when the number of fragments in the Activity is greater than 1, the sliding finish is the Fragment. If it is less than or equal to 1, the finish is the Activity.

2. Support left, right, left & right sliding (more sliding areas may be added in the future)

3. Support sliding monitoring in Scroll

4. Helped you deal with the fragmentation overlap caused by the strong killing of the app by the system

effect

design sketch

Talk about realization

The dragging part is mostly realized by ViewDragHelper. ViewDragHelper helps us deal with a large number of Touch related events and some logical monitoring of speed and release, which greatly simplifies our handling of Touch events. (the ViewDragHelper will not be introduced in detail in this article. If you are unfamiliar with it, you can consult the relevant documents by yourself)

The principle of sliding exit for Fragment and Activiy is the same. Both add a parent View: SwipeBackLayout on the Activity/Fragment View. In the Layout, create ViewDragHelper to control the dragging of Activity/Fragment View.

1. Implementation of Activity

For the SwipeBack implementation of Activity, there are a lot of analysis on the Internet. Here I briefly introduce the principle, as shown in the following figure:

We just need to ensure that the background of SwipeBackLayout, DecorView and Window is transparent, so that when you drag the xml layout of an Activity, you can see the interface of the previous Activity. When you slide the layout away, you can finish the Activity.

public void attachToActivity(FragmentActivity activity) {
    ...
    ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
    ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
    decorChild.setBackgroundResource(background);
    decor.removeView(decorChild);  // Remove decorChild
    addView(decorChild);        // Add decorChild to SwipeBackLayout(FrameLayout)
    setContentView(decorChild);
    decor.addView(this);}        // Add SwipeBackLayout to the DecorView

2. Implementation of Fragment

Here's the point, the implementation of Fragment!
Before implementation, I will explain several related knowledge points of Fragment:

1. The View part of the Fragment is actually the View returned in onCreateView;

**2. Multiple fragments loaded through add in the same Activity are superimposed on the view layer:
hide() does not destroy the view, just makes it invisible, that is, view setVisibility(GONE);,
Make view () visible setVisibility(VISIBLE);;**

add+show/hide

3. The fragments loaded through replace are replaced in the view layer. replace() will destroy the current Fragment view, that is, callback onDestoryView. When returning, recreate the view, that is, callback onCreateView;

replace

4. Regardless of add or replace, the Fragment object will be saved in memory by the FragmentManager. Even if the app is forcibly killed in the background due to insufficient system resources, the FragmentManager will save the fragments for you. When the app is restarted, we can get these fragments from the FragmentManager.

analysis:

The startup between fragments is nothing more than two in the following figure:

In this library, I did not consider the case of replace, because our SwipeBackFragment should be used in the "streaming" scenario (fragmenta - > fragmentb - >...), In this scenario, combined with the above 2, 3 and 4, add+show(),hide() is undoubtedly better than replace, with better performance, faster response and simpler code logic of our app.

Implementation of add+hide mode

From Article 1, we can know that onCreateView's View is a sub View that needs to be put into SwipeBackLayout. We give the sub View a background color, and then SwipeBackLayout is transparent. In this way, when dragging, we can see the "previous Fragment".

When we drag, the View of the previous Fragment A is in the GONE state, so what we need to do is to set the View of Fragment A to the VISIBLE state when we judge that the drag occurs. In this way, when we drag, the previous Fragment A will be displayed intact.

Core code:

@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;
}

However, compared with the Activity, the view state of the last Activity is VISIBLE, while the view state of our last Fragment is GONE, so we need Fragment a getView(). Setvisibility (VISIBLE), but when is the time?

The best solution is to handle it in the tryCaptureView method in ViewDragHelper at the moment before dragging:

@Override
public boolean tryCaptureView(View child, int pointerId) {
    boolean dragEnable = mHelper.isEdgeTouched(ViewDragHelper.EDGE_LEFT);
    if (mPreFragment == null) {
        if (dragEnable && mFragment != null) {
            ...Omit get previous Fragment code
            mPreFragment = fragment;
            mPreFragment.getView().setVisibility(VISIBLE);
            break;
        }
    } else {
       View preView = mPreFragment.getView();
       if (preView != null && preView.getVisibility() != VISIBLE) {
             preView.setVisibility(VISIBLE);
       }
    }
    return dragEnable;
}

Through the above code, the moment before we drag the current Fragment, the view of the PreFragment will be VISIBLE, and the onHiddenChanged method will not be affected at all. It is perfect. (before that, some friends may have thought about the idea of adding without hiding the last Fragment. Obviously, it is not possible, because in this case, the onHiddenChanged method will not be called back, and we use the add method to realize our logic mainly through onHiddenChanged as the "life cycle")

In another case, it should be noted that when I have started dragging FragmentB to pop, I give up halfway. At this time, the view of FragmentA is in VISIBLE state, and I enter Fragment C from B. This is that we should drop the view of A

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

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

Pit point

1. Touch event conflict

For example, when we drag the touchontouch sub event into the touchontouch sub process, the event will be directly consumed in the touchontouch sub process. For example, if we drag the event into the touchontouch sub process, it will be invalid. If the child View consumes events, it will first go to the onInterceptTouchEvent method to judge whether it can be captured. In this process, it will judge the other two callback methods: getViewHorizontalDragRange and getViewVerticalDragRange. Only when these two methods return a value greater than 0 can they be captured normally;

And you need to consider that there are two swipebacklayouts under the current drag page: the current Fragment and the Activity. The final code is as follows:

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

In this way, on the one hand, the event conflict is solved, and on the other hand, when the number of fragments in the Activity is greater than 1, the Fragment is dragged, and when it is equal to 1, the Activity is dragged.

2. Animation

We need to move the Fragment/Activity out of the screen and close it after dragging. The most important thing is to ensure that there is no animation when the current Fragment/Activity is closed and the previous Fragment/Activity is entered!

For Activity, the job is simple: Activity Override pending transition (0, 0).

For Fragment, if you don't set transition animation for Fragment jump, you can use it directly;
If you use setCustomAnimations(enter,exit) or setcustomanimations (enter, exit, poppenter, popexit), you can do this:

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

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

3. Do not call show() when starting a new Fragment

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

Please do not call show(B) in the above code:
On the one hand, the newly add ed B itself is in a visible state. No matter whether you call show or not, you will not call back the onHiddenChanged method of B;
On the other hand, if you call show, there will be abnormal behavior after sliding back. When you return to PreFragment, the view of PreFragment will be in go state; If you have to call show, please handle it in the following way: (if it's not necessary, don't call show, and the following code may flicker)

@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);
            }
        });
    }
}

last

Why do I make this library into two, a separate SwipeBackFragment and a fragmentation swipeback extension library?

The reason is:
SwipeBackFragment library is a basic library that only implements Fragment & Activity drag and drop return. It is suitable for small partners who use Fragment slightly (the project belongs to multi activity + multi Fragment, and there is no complex logic between fragments). Of course, you can expand it at will.

Fragment is mainly a fragment help library when the project structure is single Activity + multiple fragments, or when the multi Activity + multiple fragments structure of fragment is heavily used. Fragment swipeback is a library extended on its basis, which is used to realize the sliding return function and can be used in various project structures.

Topics: Android Fragment Programmer Flutter Activity