Android sideslip return analysis and Implementation (not high imitation wechat, college student)

Posted by Warz on Thu, 16 Dec 2021 23:47:57 +0100

     stay styles The following two properties are configured in:
    
    ```
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowIsTranslucent">true</item> 
    ```
    
    Then monitor the sideslip event and move the top layer Activity of ContentView,You can really see through to the lower layer Activity Interface. At this time, no matter the layout changes or data updates, there is no problem. BUT!There are many problems in this scheme...  
    **Existing problems**: `windowIsTranslucent`by true It will cause a series of animation problems, such as switching between front and background animation Activity Fallback animation, etc. There are solution settings on the Internet`"android:windowEnterAnimation"`and`"android:windowExitAnimation"`,No eggs tested. At the same time, in SDK26(Android8.0)And above, it will conflict with the fixed screen direction and cause flash back. At the same time, the lower layer Activity Will only enter onPause Status, no onStop,When the page opens too many, it will crash you.
    
*   **Transparency programme II**
    
    For example, transparent scheme I is still in progress styles Configure the two properties in onPause Use reflection to make the window opaque in onResume Then use reflection to make the window transparent. It seems that maozi has successfully solved the problem below the lower layer Activity can't onStop Performance problems caused by. BUT!The problem with the scheme is still terrible...  
    **Existing problems**: Because of the top layer Activity Transparent, lower layer when rotating the screen Activity Will rebuild, and then onResume Make the window transparent, and then lower the lower layer Activity Also resurrected... A series of chain reactions, it's terrible! At the same time,`windowIsTranslucent`by true A series of animation problems have not been solved.

realization

It can be seen from the above that if you want to see no illusion when sliding, the window must be transparent and let the lower Activity receive layout changes and data updates. However, window transparency will affect the animation effect and conflict with screen rotation. Can the window remain transparent only when sliding?
ofcourse~
We can use reflection to make the window transparent when the sideslip is triggered, and use reflection to make the window opaque at the end of the sideslip. In this way, you can get a glimpse of the lower level Activity when sliding, and it will not conflict with the screen rotation, nor will it affect the use of animation. The principle is very simple. Let's start to implement it step by step.

Note: some students asked that the use of non SDK interfaces is prohibited in Android P, but the interfaces for window transparent conversion belong to the light gray list, which is not limited at present.

  • Step.1 status bar transparent

    Since the sideslip return is to be realized, the status bar must be killed to realize the immersive experience. There are not many BB S here. Go directly to the code.

    private boolean setStatusBarTransparent(boolean darkStatusBar) {
         //If the SDK is greater than or equal to 24, you need to judge whether it is in window mode
        boolean isInMultiWindowMode = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && mSwipeBackActivity.isInMultiWindowMode();
        //If the window mode or SDK is less than 19, the status bar transparency is not set
        if (isInMultiWindowMode || Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            return false;
        } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            //SDK less than 21
            mSwipeBackActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        } else {
            //SDK ≥ 21
            int systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
             //SDK ≥ 23 supports flipping the color of the status bar
            if (darkStatusBar && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                //Set status bar text & icon dark
                systemUiVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
            }
            //Remove status bar background
            mDecorView.setSystemUiVisibility(systemUiVisibility);
            //Set status bar transparency
            mSwipeBackActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            mSwipeBackActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            mSwipeBackActivity.getWindow().setStatusBarColor(Color.TRANSPARENT);
        }
        //Listen for layout changes in DecorView
        mDecorView.addOnLayoutChangeListener(mPrivateListener);
        return true;
    } 
    

    Here are a few things to pay attention to.
    1. SDK less than 19 does not support status bar transparency, and the implementation methods of SD21 and above are also different.
    II. SD23 and above support status bar color reversal.
    3. Sd24 and above support window mode, which should be judged here. When in window mode, do not set the status bar to be transparent.
    4. After the status bar is set to transparent, the adjustResize of the input method will become invalid. Network transmission solution android:fitsSystemWindows="true" is not recommended because it will make it impossible to draw under the status bar. Therefore, the DecorView layout changes are monitored here. When the layout changes, the height of the sub View is dynamically adjusted to be the visible part of the DecorView. Post the following code:

     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
            //Gets the visible area of the DecorView
            Rect visibleDisplayRect = new Rect();
            mDecorView.getWindowVisibleDisplayFrame(visibleDisplayRect);
            /**A short piece of code is omitted here, which will be mentioned later*/
            //When the status bar is transparent, the adjustResize of the input method will not take effect. Here, manually adjust the height of the View to fit
            if (isStatusBarTransparent()) {
                for (int i = 0; i < mDecorView.getChildCount(); i++) {
                    View child = mDecorView.getChildAt(i);
                    if (child instanceof ViewGroup) {
                        //Gets the child ViewGroup of the DecorView
                        ViewGroup.LayoutParams childLp = child.getLayoutParams();
                        //Adjust the paddingBottom of the child ViewGroup
                        int paddingBottom = bottom - visibleDisplayRect.bottom;
                        if (childLp instanceof ViewGroup.MarginLayoutParams) {
                            //The bottom margin is subtracted here to take into account the height of the navigation bar
                            paddingBottom -= ((ViewGroup.MarginLayoutParams) childLp).bottomMargin;
                        }
                        paddingBottom = Math.max(0, paddingBottom);
                        if (paddingBottom != child.getPaddingBottom()) {
                            //Adjust the paddingBottom of the child ViewGroup to ensure that the entire ViewGroup is visible
                            child.setPadding(child.getPaddingLeft(), child.getPaddingTop(), child.getPaddingRight(), paddingBottom);
                        }
                        break;
                    }
                }
            }
        } 
    

    There are also two small points to note: one is that the height of the navigation bar must be considered in the calculation of the paddingBottom. The paddingBottom cannot be negative.

  • Step.2 support sideslip

    The status bar is already transparent. The next step is to make our interface slide. Here we implement it in the dispatchTouchEvent method of Activity.
    First, in the action of dispatchTouchEvent_ Judge whether the pressing area is a side in the down event and mark it.
    Then, in the action of dispatchTouchEvent_ Judge the moving direction in the move event and mark it. If it is horizontal sliding, call setTranslationX on the parent container of ContentView to set the offset value to make the interface move. Why is it the parent container of ContentView? Because ContentView does not contain ActionBar, although ActionBar is not recommended...
    Finally, in the action of dispatchTouchEvent_ Judge the distance in the up event, and judge whether to finish the current page according to the end speed and displacement. The basic idea of making the page slide is purple. BUT, which also involves multi Touch, sub View Touch event cancellation, end speed calculation, animation processing after release, etc. Limited to a little more code, it's not the point. I won't post it here. If you are interested in learning more, please Read source code

  • Step.3 window transparency

    At this stage, many students may have to ask, because the bottom is black after Mao I slide. Don't worry, because we haven't thrown Wang fried yet. As mentioned earlier, we need to use reflection to turn the window transparent when the sideslip is triggered, and use reflection to turn the window opaque at the end of the sideslip. The previous step has explained how to make the page slide, and the rest is easy to do. Please see Wang fried Code:

    //Make window transparent
    private void convertToTranslucent(Activity activity) {
        if (activity.isTaskRoot()) return;//The Activity at the bottom of the stack is not processed
        isTranslucentComplete = false;//Conversion completion flag
        try {
            //Gets the class object of the transparent conversion callback class
            if (mTranslucentConversionListenerClass == null) {
                Class[] clazzArray = Activity.class.getDeclaredClasses();
                for (Class clazz : clazzArray) {
                    if (clazz.getSimpleName().contains("TranslucentConversionListener")) {
                        mTranslucentConversionListenerClass = clazz;
                    }
                }
            }
            //Proxy transparent conversion callback
            if (mTranslucentConversionListener == null && mTranslucentConversionListenerClass != null) {
                InvocationHandler invocationHandler = new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        isTranslucentComplete = true;
                        return null;
                    }
                };
                mTranslucentConversionListener = Proxy.newProxyInstance(mTranslucentConversionListenerClass.getClassLoader(), new Class[]{mTranslucentConversionListenerClass}, invocationHandler);
            }
            //Use reflection to make the window transparent. Note that the parameters of SDK21 and above are different
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                Object options = null;
                try {
                    Method getActivityOptions = Activity.class.getDeclaredMethod("getActivityOptions");
                    getActivityOptions.setAccessible(true);
                    options = getActivityOptions.invoke(this);
                } catch (Exception ignored) {
                }
                Method convertToTranslucent = Activity.class.getDeclaredMethod("convertToTranslucent", mTranslucentConversionListenerClass, ActivityOptions.class);
                convertToTranslucent.setAccessible(true);
                convertToTranslucent.invoke(activity, mTranslucentConversionListener, options);
            } else {
                Method convertToTranslucent = Activity.class.getDeclaredMethod("convertToTranslucent", mTranslucentConversionListenerClass);
                convertToTranslucent.setAccessible(true);
                convertToTranslucent.invoke(activity, mTranslucentConversionListener);
            }
        } catch (Throwable ignored) {
            isTranslucentComplete = true;
        }
        if (mTranslucentConversionListener == null) {
            isTranslucentComplete = true;
        }
        //Remove window background
        mSwipeBackActivity.getWindow().setBackgroundDrawable(null);
    } 
    

    //Make the window opaque
    private void convertFromTranslucent(Activity activity) {
    if (activity.isTaskRoot()) return;// The Activity at the bottom of the stack is not processed
    try {
    Method convertFromTranslucent = Activity.class.getDeclaredMethod("convertFromTranslucent");
    convertFromTranslucent.setAccessible(true);
    convertFromTranslucent.invoke(activity);
    } catch (Throwable t) {
    }
    }

    The code is a little long, but it's easy to understand.`convertToTranslucent`First get the transparent conversion callback class, then proxy the transparent conversion callback, and finally turn the reflection window into transparent.`convertFromTranslucent`I won't explain more. You only need to call before sideslip.`convertToTranslucent`You can turn the window to transparent, and call it after loosening.`convertFromTranslucent`You can restore the window to opaque. You should notice that there is a conversion completion flag here, and its function will be explained later.
    
    
  • Step.4 bottom shadow

    Here, the sideslip return has been basically realized. It's done in three steps. But some students may think it's not nice without a shadow! This is simple. We can customize a ShadowView and call setTranslationX when sliding.

    public View getShadowView(ViewGroup swipeBackView) {
        if (mShadowView == null) {
            mShadowView = new ShadowView(mSwipeBackActivity);
            mShadowView.setTranslationX(-swipeBackView.getWidth());
            ((ViewGroup) swipeBackView.getParent()).addView(mShadowView, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        }
        return mShadowView;
    } 
    

    The swipbackview here is step 2. The parent container of ContentView mentioned by sideslip is supported, and ShadowView is inserted into the parent container of swipbackview. No one may notice that the getShadowView method is public, because I think so. Maybe some people don't like my shadow, but they want to see a Picchu when sliding on the side? You say so...
    In addition, we have to say at this step, but all sideslip return libraries used by several people support the linkage of lower level activities like wechat. Here, for the sake of some questions, we (actually) don't (lazy) support (recover) from (cancer).

matters needing attention

After the above four simple steps, the effect is basically great. However, there are still some places that need special attention, as well as the two pits in front, which are now backfilled.

  • Tips.1

    Fill out the holes occupied during the layout change monitoring of DecorView explained earlier. When the layout changes, we adjust the paddingBottom of the DecorView sub View to achieve the adjustResize of the adaptive input method. This will lead to a problem. The pop-up of the input method has a bottom-up animation. During the animation period, the position of this block will display the color of the window - black. This is certainly unbearable for those who pursue perfection. Our solution is to block the black spot with a new View. Is the method a little earthy... But it worked...
    Paste the missing code in the previous onLayoutChange code block:

     mWindowBackGroundView = getWindowBackGroundView(mDecorView);
            if (mWindowBackGroundView != null) {
                //Block the black one
                mWindowBackGroundView.setTranslationY(visibleDisplayRect.bottom);
            } 
    
  • Tips.2

    In the previous window transparency processing, a pit is also left: the transparent conversion completion flag is transparent complete. Why this? Because it takes about 100ms to turn the window into transparent, if you move the ContentView before the conversion is completed, you will see that there is another darkness at the bottom... Of course, this is not what I want, so judge before moving that if the window has not become transparent, it will not be processed

    private void swipeBackEvent(int translation) {
        if (!isTranslucentComplete) return;
        if (mShadowView.getBackground() != null) {
            int alpha = (int) ((1F - 1F * translation / mShadowView.getWidth()) * 255);
            alpha = Math.max(0, Math.min(255, alpha));
            mShadowView.getBackground().setAlpha(alpha);
        }
        mShadowView.setTranslationX(translation - mShadowView.getWidth());
        mSwipeBackView.setTranslationX(translation);
    } 
    

    There may be some students here who want to say that they don't deal with it before the conversion is completed. After the conversion is completed, it will jump suddenly. For example, suddenly jump from 0 to 100. The idea is very rigorous, but because the window conversion is about 100ms, unless the hand speed is fast, there is not much distance and it can't be seen. If the hand speed is fast and changes too fast, you can't see whether the front is gradual change or sudden change. So it's good to deal with it like this...

  • Tips.3

    There are two situations after sideslip release: one is to return to the left origin, the other is to continue sliding to the right boundary, and then finish the Activity. As mentioned earlier, you need to turn the window opaque after you release the sideslip. It should be noted that if you will finish the Activity, do not make the window opaque. Because the Activity at the lower level is transparent at this time, if it becomes opaque, and then finish the Activity at the top level, a black window will flash. In addition, after finishing, cancel the exit animation of the Activity.

     public void onAnimationEnd(Animator animation) {
            if (!isAnimationCancel) {
                //The final moving distance exceeds half width, ending the current Activity
                if (mShadowView.getWidth() + 2 * mShadowView.getTranslationX() >= 0) {
                    mShadowView.setVisibility(View.GONE);
                    mSwipeBackActivity.finish();
                    mSwipeBackActivity.overridePendingTransition(-1, -1);//Cancel return to animation
                } else {
                    mShadowView.setTranslationX(-mShadowView.getWidth());
                    mSwipeBackView.setTranslationX(0);
                    convertFromTranslucent(mSwipeBackActivity);
                }
            }
        } 
    
  • Tips.4

    The core principle of sideslip is to use reflection to convert window transparency. As mentioned in the previous exploration of transparency scheme, window transparency will affect the life cycle of lower level activities. When we make the window transparent, the lower level Activity will wake up and enter the onStart state. If the screen rotates, the lower level Activity will be rebuilt. When we restore the window to opaque, the underlying Activity will re-enter the onStop state. Therefore, if the logic of your Activity code is chaotic, be sure to optimize the logic before using it.

  • Tips.5

last

All materials of the article have been packaged and sorted out. In addition, Xiaobian has sorted out a large number of full sets of learning materials for Android architects, PDF document of Android core advanced technology + full set of advanced learning materials + Video + real problem analysis of 2021 BAT factory interview

CodeChina open source project: Android learning notes summary + mobile architecture Video + big factory interview real questions + project actual combat source code

Data display:

Analysis of real interview questions in AT factory**

CodeChina open source project: Android learning notes summary + mobile architecture Video + big factory interview real questions + project actual combat source code

Data display:

[external chain picture transferring... (IMG dzikocxo-1630650416316)]

[external chain picture transferring... (img-GXLIQEHC-1630650416318)]

[external chain picture transferring... (img-KrGw9BCn-1630650416319)]

Topics: Android Design Pattern