Day28-find knows the implementation of Coordinator Layout

Keywords: Android Fragment xml Java

  • This article has authorized the exclusive publication of Weixin Public Number guolin_blog (Guo Lin)

tips:
CoordinatorLayout(Coordinator layout)A layout that can change the effect between coordinated views

The article is divided into three parts.

  1. Brief introduction of Coordinator Layout concept
  2. Simple behavior is customized by copying. (non-gif demonstration)
  3. Complete unpacking analysis knowledge and copying knowledge include MainActivity, List Fragment of Home Page Answer, and Datail Fragment of Open Answer Details by Clicking Home Page Answer. (Implementation of Gif Demo)

It's rather verbose. It's recommended to read for 9 minutes.
You can jump directly to the code section

Demo address

FindZhihu.gif

Coordinator Layout

The concept of "coordination"

  1. Paving the way: Normal event distribution and delivery, if a layer of view consumes Down events. Later, MOVE and UP events are delivered to this layer, which can not directly achieve the "coordination" between sub-views and other sub-views. So Google Design ed in 15 years.
    SupportLibrary package Revision 22.2.0 adds Coordinator Layout.
  2. Coordinator Layout, the core external control of Coordinator, is a ViewGroup that implements the NestedScrolling Parent interface. According to Google, it acts as a top-level layout and as a container for sub-view interaction.
    So what are its subviews?
    • Dependent on the child view, you can scroll the view and implement the child view of NestedScrolling Child. (RecyclerView has been implemented)
    • Dependent on subviews, such as AppbarLayout (View that follows the action as the sliding proceeds)

Google's description of behavior is: Coordinator Layout's child view interaction behavior plug-in, a behavior can achieve one or more interactions.

Method in Behavior

Classification 1: view monitors the state changes of another view, such as size, location, display.

  1. Layout DependsOn is used to determine whether a child View has another peer view as a layout subordinate
  2. onDependentViewChanged responds to changes in dependent subViews

Classification 2: view monitors the sliding state in Coordinator Layout

  1. On Start NestedScroll, do we care, what kind of sliding we care about?
  2. When onNested PreScroll slides through nested slides, the object consumes a rollback before scrolling distance (most frequently used)

Coordination Demo - Simple Behavior link

Let's start by copying the Knowledgeable Details Page to customize a Behavior

Knowing Behavior Induction

1. List fragment in MainActivity

  1. Slide a certain distance and hide
  2. After the trigger effect, the hand is not loose, reset the distance needed to trigger the first step in the new position.
  3. It's not about clicking.
    If you try the default behavior, you will find that it is very similar to the default behavior of the system. app: layout_behavior="@string/appbar_scrolling_view_behavior"

2. Detail Fragment

  1. File content is long enough to open sliding
  2. The bottom view shows when you first come in
  3. Fast sliding to show/hide view
  4. When the bottom view is hidden, click Display/Hide the top and bottom views.
  5. Drop down to the bottom and let out the top and bottom view.

3. Reanalysis of the bottom TabLayout of MainActivity

  1. If you slide very slowly, for the same animation effect, the bottom TabLayout is executed before the top Toolbar, and you can see that the top and bottom of the first page of app are not controlled by a behavior.

Let's first use behavior to write an imitative effect.

Demo - Customize Behavior, Imitate Layout

  1. Create Coordinator Layout Layout Layout

    <CoordinatorLayout>
          <RecyclerView/>            
          <LinearLayout
            app:layout_behavior="@string/my_behavior"
            />          
    </CoordinatorLayout>
    

    If you have to list view, judge version API 21 and then set Nested Scrolling Enabled
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { listView.setNestedScrollingEnabled(true); }

  2. Custom bottom hidden animation

    public class MyBehaviorAnim {
    
        private View mBottomView;
        private float mOriginalY;
    
        public BottomBehaviorAnim(View bottomView) {
            mBottomView = bottomView;
            mOriginalY = mBottomView.getY();
        }
    
    
        public void show() {
            ValueAnimator animator = ValueAnimator.ofFloat(mBottomView.getY(), mOriginalY);
            animator.setDuration(400);
            animator.setInterpolator(new LinearOutSlowInInterpolator());
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    mBottomView.setY((Float) valueAnimator.getAnimatedValue());
                }
            });
            animator.start();
        }
    
    
        public void hide() {
            ValueAnimator animator = ValueAnimator.ofFloat(mBottomView.getY(), mOriginalY + mBottomView.getHeight());
            animator.setDuration(400);
            animator.setInterpolator(new LinearOutSlowInInterpolator());
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    mBottomView.setY((Float) valueAnimator.getAnimatedValue());
                }
            });
            animator.start();
        }
    }
    
  1. Customize Behavior Hidden at the Bottom

    • Judgement gesture
    • Calculated distance
    • Trigger animation
    public class MyBehavior extends CoordinatorLayout.Behavior<View> {
    
        protected BottomBehaviorAnim mBottomAnim;
        private boolean isHide;
        private boolean canScroll = true;
        private int mTotalScrollY;
        protected boolean isInit = true; //Prevent parent and child coordinate changes caused by new Anim
    
        private int mDuration = 400;
        private Interpolator mInterpolator = new LinearOutSlowInInterpolator();
        private int minScrollY = 5;//Minimum Distance of Triggered Sliding Animation
        private int scrollYDistance = 40;//Setting Minimum Sliding Distance
    
        //1. The method of constructing two parameters must be rewritten, because the instantiation of behavior is realized by reflection.
        public BottomBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        //2. who cares
        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
            return super.layoutDependsOn(parent, child, dependency);
        }
    
        /**
         * Method invoked before triggering sliding nested scroll
         *
         * @param coordinatorLayout coordinatorLayout Parent layout
         * @param child             Use Behavior's SubView
         * @param target            Triggering Sliding Nested View (Implementing NestedScrollingChild Interface)
         * @param dx                X-Axis Distance of Sliding
         * @param dy                Sliding Y-Axis Distance
         * @param consumed          The sliding distance of parent layout consumption, consumed[0] and consumed[1] represent the distance of parent layout consumption in X and Y directions, default is 0.
         */
        @Override
        public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target,
                                      int dx, int dy, int[] consumed) {
            super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        }
    
        /**
         * Method of triggering when sliding nested rolling
         *
         * @param coordinatorLayout coordinatorLayout Parent layout
         * @param child             Use Behavior's SubView
         * @param target            Triggering Sliding Nested View
         * @param dxConsumed        TargetView X-Axis Distance of Consumption
         * @param dyConsumed        TargetView Y-Axis Distance of Consumption
         * @param dxUnconsumed      X-Axis Distance Not Consumed by TargetView
         * @param dyUnconsumed      Y-axis distances that are not consumed by TargetView (e.g. RecyclerView has reached the top or bottom, while the user continues to slide, at which time the value of dyUnconsumed is not zero and some cross-border events can be handled)
         */
        @Override
        public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target,
                                   int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
            super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
            if (canScroll) {
                mTotalScrollY += dyConsumed;
                if (Math.abs(dyConsumed) > minScrollY || Math.abs(mTotalScrollY) > scrollYDistance) {
                    if (dyConsumed < 0) {
                        if (isHide) {
                            mBottomAnim.show();
                            isHide = false;
                        }
                    } else if (dyConsumed > 0) {
                        if (!isHide) {
                            mBottomAnim.hide();
                            isHide = true;
                        }
                    }
                    mTotalScrollY = 0;
                }
            }
        }
    
        @Override
        public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int nestedScrollAxes) {
            if (isInit) {
                mBottomAnim = new BottomBehaviorAnim(child);
                isInit = false;
            }
            return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
        }
    
        @Override
        public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, final View target) {
            super.onStopNestedScroll(coordinatorLayout, child, target);
        }
    
    }
    
  2. Relevant xml and behavior
    First add behavior to string, then add behavior to dependency child view

    <resources>
        <string name="my_behavior">com.clickdemo.Behavior.MyBehavior</string>
    </resources>
    
    <LinearLayout
      app:layout_behavior="@string/my_behavior"
      />    
    

Think: Custom behavior does work like Knowing Hidden TabView, but does Knowing Really Do It? Both Activity and Fragment use Coordinator Layout?

Know about

Know the static analysis of unpacking.

  1. Get ready
    • Object of analysis: Home page and Answer Details page.
    • Material Science:
      • The xml and other files you get when unpacking. (4.1.8 package comes from the historical version of Kuan'an app)
      • Luyten (see the obfuscated java code in the. jar file)
      • Hierarchy View (Android Studio View Viewing Tool)
    • Prerequisite: As you can see in the activity_main.xml you get when unpacking, the bottom com.zhihu.android.base.widget.ZHTabLayout uses layout_behavior.
    <?xml version="1.0" encoding="utf-8"?>
    <com.zhihu.android.app.ui.widget.ZHInsetsFrameLayout android:id="@id/content_container" android:tag="layout/activity_main_0" android:layout_width="fill_parent" android:layout_height="fill_parent"
      xmlns:android="http://schemas.android.com/apk/res/android" xmlns:zhihu="http://schemas.android.com/apk/res-auto">
        <com.zhihu.android.app.ui.widget.reveal.widget.RevealFrameLayout android:background="?zhihu.background.window" android:layout_width="fill_parent" android:layout_height="fill_parent">
            <android.support.design.widget.CoordinatorLayout android:id="@id/coordinator_layout" android:layout_width="fill_parent" android:layout_height="fill_parent">
                <com.zhihu.android.base.widget.NonSwipeableViewPager android:id="@id/main_pager" android:layout_width="fill_parent" android:layout_height="fill_parent" />
                <FrameLayout android:id="@android:id/content" android:layout_width="fill_parent" android:layout_height="fill_parent" zhihu:layout_behavior="com.zhihu.android.base.widget.SnackBarBehavior" zhihu:layout_anchorGravity="end|center|bottom" />
                <com.zhihu.android.base.widget.ZHTabLayout android:layout_gravity="end|bottom|center" android:id="@id/main_tab" android:background="?zhihu.background.navigation.tab.bottom" android:layout_width="fill_parent" android:layout_height="@dimen/bottom_navigation_height" zhihu:layout_behavior="com.zhihu.android.base.widget.FooterBehavior" zhihu:layout_anchorGravity="end|center|bottom" zhihu:tabIndicatorColor="@android:color/transparent" zhihu:tabGravity="fill" />
            </android.support.design.widget.CoordinatorLayout>
        </com.zhihu.android.app.ui.widget.reveal.widget.RevealFrameLayout>
        <com.zhihu.android.base.widget.ZHFrameLayout android:id="@id/overlay_container" android:layout_width="fill_parent" android:layout_height="fill_parent" /
    </com.zhihu.android.app.ui.widget.ZHInsetsFrameLayout>
    
    
    • Presumption: MainActivity contains four fragments, which are switched at the bottom by "tabLayout", and then go to the details page to change the content, and then one Coordinator Layout per page?
    • Purpose: To view the xml and java files that know how the behavior of Fragment page is implemented and get the details of Fragment page and home page.
  2. Get the id of the view with Hierarchy View
    The simulator opens app, enters an answer on the home page, and uses Hierarchy View (tool - > Android Device Monitor included in Android Studio) to find that Coordinator Layout is wrapped in NonSwipeable ViewPager + + ZHFrameLayout + ZHTabLayout, and then goes to the details page to replace FrameLayout in NonSwipeable ViewPager.
    image


    The effect of activity_main is shown in the figure above, where FrameLayout displays ListFragment on the home page.

    image


    The frame Layout above shows the details of the answer.

Question 1: Wait, there's only one Coordinator Layout?
How many Coordinator Layouts did you guess before?

Under normal use, the sub-view of Coordinator Layout's sub-view can only be coordinated with the whole view of the higher level. Even AppBarLayout, the effect of coordination is fixed.

So do you know that the view at the bottom of Fragment and the toolbar at the head do not depend on Coordinator Layout for coordination?
Find Activity and Fragment.java files first.

  1. Find out which xml uses it based on the view id
    Next, we start looking for the decompiled resources. According to Step 1, we see a view that looks like the root layout, FrameInterceptLayout. Then we search in res/layout. Sure enough, there are not many places to use FrameInterceptLayout. And fragment_pager and fragment_pager_2 look suspicious.

    image

  2. Find out which java files use it based on the id of xml
    Now let's see where we use "fragment_pager" and pass it on. Android's Reverse Journey It can be concluded that the decompiled apk will produce a vital public.xml file. Just under res/values/public.xml, open it and search. Ha, find you, face code, 0x7f0400be and 0x7f0400bf.

    image


    But this is two hexadecimal things. Right. Android's Reverse Journey It also tells us how to look at 0x7f0400be:

    As you can see here, an id field has its corresponding type, name, and id value.
    The id value here is an integer value, 8 bytes; it consists of three parts:
    PackageId+TypeId+EntryId
    PackageId: It's the Id value of the package. If it's a third-party application in Android, the default value is 0x7F. If it's a system application, it's 0x01. We can see from the aapt source code that it takes two bytes.
    TypeId: is the type Id value of resources. There are several types in Android: attr, drawable, layout, dimen, string, style and so on, and the values of these types increase gradually from 1, and the order can not be changed. attr=0x01, drawable=0x02... He takes up two bytes.
    EntryId: The id value of a resource entity under a specific type, incrementing from zero, takes up four bytes.

    Then use a calculator to convert it to 2130 968766 and 2130 968767 in decimal system.
    Then open Luyten (instead of JD_GUI, some files JD_GUI open after a blank). Open a jar file, first search 2130968766, found.


    image

    Seeing the data binding sigh, I knew I had already been on the data binding.

    But what we want is the following com/zhihu/app/ui/fragment/b/i.class...
    Find this sentence

    public View a(final LayoutInflater layoutInflater, final ViewGroup viewGroup, final Bundle bundle) {
        this.b = android.databinding.e.a(layoutInflater, 2130968766, viewGroup, false);
        return this.b.h();
    }
    

    You can guess that's fragment's onCreateView.
    This brings you the java and xml files of Fragment on the home page.
    Similarly, the ZHObservable WebView with the details page and the ZHRecyclerView extends ObservableRecyclerView with the home page list are available. This frequently occurring ObservableXXXView can find this project according to the package name. ksoichiro/Android-ObservableScrollView

Of course, if you can find a view with a special id in the HirarchyView of Step 1, you can search directly, for example, the details page can be found by fragment_paging_layout. The view of the details page is fragment_paging.xml, as shown below.


image

Solve the case! Combining Question 1, we can conclude that:
Knowledgeable Fragment s coordinate not through Coordinator Layout's behavior, but through open source projects that use the observer model.

Summarize (omit process):
  1. At the bottom of MainActivity, TabLayout uses Coordinator Layout's layout_behavior
  2. The Top toolbar of ListFragment on the home page may be ZHObservableRecyclerView or FrameInterceptLayout control. The xml file is fragment_paging, and the Java file is in the
  3. The top and bottom of the Detail Fragment are ZHObservable WebView control xml file, fragment_pager, and Java file at com/zhihu/app/ui/fragment/b/i.class.

Why is the Home Details page so definitely controlled by ZHObservable WebView? Because the onScrollChange method is rewritten in the source code seen in Luyten, which returns four parameters: int, t, oldl and oldt. When I write, I find that I need these four parameters to implement the results easily.

The general effect is as follows:


image

Specific Demo address

Principle description of the core part:
When switching from List Fragment of MainActivity to Detail Fragment, restore toolbar of List Fragment and tabLayout of MainActivity (GONE first).
When switching back from Detail Fragment to List Fragment, direct VISIBLE bottom tabLayout.

Curving road

  1. How to trigger onNested Scroll of Behr only when it is sliding rapidly
    • The original idea is to monitor the Y-axis velocity of gesture by event, and pass it to behavior. Because onTouchEvent can not receive events when "coordination" occurs, behavior does not belong to ViewGroup, and can not call dispatchTouchEvent in behavior, but can only be tuned in activity, so the Y-axis velocity of event in dispatchTouchEvent monitored by activity is called back to behavior.
    • Realization idea: In fact, the parameter dyConsumed of onNestedScroll is speed.
  2. How to Judge Slipping to the End
    • The original idea: In "Coordination" listening on NestedScroll, dyConsumed = 0 means sliding to the boundary, dyUnconsumed > 0 means sliding to the boundary and still pulling down, so it can be judged by (dyConsumed = 0 & dyUnconsumed > 0)
    • Current ideas: boolean view.canScrollVertically(1) returns a value that can be pulled down, and - 1 returns a value that can be pulled up.
  3. Detailed Fragment s cover the TabLayout that was supposed to be in front of it, that is, how to make the ViewPager blocked by TabLayout in xml, and in turn block TabLayout.
    • The original idea: Seeing afollestad/material-dialogs in the source code, mistakenly thinking that the new Fragments are Dialog Fragments
    • The original idea: through View.bringToFront
    • Current ideas:
      1. Dialog Fragment can see an additional MainActivity process in Hierarchy View. The layout starts with DecorView.
      2. View.bringToFront changes the original process even if it doesn't open a new DecorView, and can't have the same layout as it knows on Hierarchy View.


  4. Why not use dispatch TouchEvent directly
    • There is no rewriting dispatchTouchEvent in behavior

  5. Why not onTouchEvent
    • Behavior will intercept and throw an ACTION_CANCEL directly to Behavior when it gets a section of ACTION_MOVE.



Change from: https://www.jianshu.com/p/788edf302b43

Posted by jacinthe on Tue, 14 May 2019 11:28:53 -0700