📜 ⬆️ ⬇️

We catch everything with the help of CoordinatorLayout Behavior

I bring to your attention a translation of the article Ian Lake Intercepting everything with CoordinatorLayout Behaviors .

You will not go far in learning Android Design Support Library without encountering CoordinatorLayout . Many View from Design Library require CoordinatorLayout. But why? The CoordinatorLayout itself does not do much, if you use it with the View, which is part of the Android framework, it will work like a regular FrameLayout. So where does all his magic come from? This is where CoordinatorLayout.Behavior comes on the scene. By connecting Behavior to a child View of the CoordinatorLayout, you can intercept touches, window inserts (window insets), resize and layout (measurement and layout), and also nested scrolling. Design Library makes extensive use of Behavior to add strength to most of the functionality you see.



Create Behavior


Creating Behavior is quite simple: inheriting our class from Behavior:
')
Behavior creation example
public class FancyBehavior<V extends View> extends CoordinatorLayout.Behavior<V> { /** *     FancyBehavior  . */ public FancyBehavior() { } /** *     FancyBehavior  . * * @param context The {@link Context}. * @param attrs The {@link AttributeSet}. */ public FancyBehavior(Context context, AttributeSet attrs) { super(context, attrs); //     //     behavior_ //      Behavior } } 


Note the generic type specified in this class. In this case, we indicate that we can connect FancyBehavior to any View. However, if you want to allow your Behavior to be connected to a specific type of View, you can write this:

 public class FancyFrameLayoutBehavior extends CoordinatorLayout.Behavior<FancyFrameLayout> 

This can save you from casting a large number of parameters in methods to the subtype of View you need - simple and convenient.

There are methods to save both the temporary data of Behavior.setTag () / Behavior.getTag () and the state of the Behavior instance using onSaveInstanceState () / onRestoreInstanceState () . I urge you to create Behavior as easy as possible, but these methods allow you to create Behavior with the ability to save state.

Behavior connection


Of course, Behavior does not do anything on its own, so that we can use it, it must be connected to a CoordinatorLayout's child View. There are three main ways to do this: programmatically, in XML or automatically using annotation.

Behavior software connection

When you think of Behavior as something additionally connected to each View inside the CoordinatorLayout, you should not be surprised (if you read our post about layouts ) that Behavior is actually stored in the LayoutParams of each View - which is why Behavior should be declared the View is inside the CoordinatorLayout, since only this View has a specific LayoutParams subtype that Behavior can store.

 FancyBehavior fancyBehavior = new FancyBehavior(); CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) yourView.getLayoutParams(); params.setBehavior(fancyBehavior); 

As you can see, in this case we use the usual empty constructor. But this does not mean that you cannot create a constructor that takes as many parameters as you want. When you do something through a code, there is no limit to possibilities.

Connecting Behavior via XML

Of course, if you constantly do everything through the code - it can cause confusion. As with most custom LayoutParams, there is an appropriate layout_ attribute to do the same. In our case, this is the layout_behavior attribute:

 <FrameLayout android:layout_height=”wrap_content” android:layout_width=”match_parent” app:layout_behavior=”.FancyBehavior” /> 

Here, unlike the software method, the constructor of FancyBehavior (Context context, AttributeSet attrs) will always be called. But, as a bonus, you can declare any other user attributes and extract them from the XML AttributeSet, this is important if you want (and you want) to allow other developers to customize the functionality of your Behavior via XML.

Note : Similarly, with the agreement of the name of the layout_ attribute, which the parent class must be able to analyze and understand, use the prefix for the attribute_ for any attributes used within Behavior.

Automatic connection Behavior

If you create your View, which needs your Behavior (as was the case with most components in the Design Library), then you most likely want to connect Behavior by default, without permanent manual connection via code or XML. To do this, you just need to add a simple annotation on top of the class of your View:

 @CoordinatorLayout.DefaultBehavior(FancyFrameLayoutBehavior.class) public class FancyFrameLayout extends FrameLayout { } 

You will notice that your Behavior will be invoked using a standard empty constructor, making this method similar with the Behavior software connection. Note that any layout_behavior attribute will override the DefaultBehavior .

Interception touch


As soon as you connect Behavior, you are ready for action. One of Behavior's abilities is touch interception.

Without a CoordinatorLayout , this usually involved subclasses of each ViewGroup, as stated in the Managing Touch Events training . However, in the case of the CoordinatorLayout , it will transfer the onInterceptTouchEvent () method calls to the onInterceptTouchEvent () method of your Behavior, allowing your Behavior to intercept the touches . If you return true to this method, your Behavior will get all subsequent touches using the onTouchEvent () method - all this happens in secret from View. For example, this is how SwipeDismissBehavior works with any View.

There is another, more complicated case of intercepting touches - blocking any interaction. It is enough to return true in the BlocksInteractionBelow () method. Most likely, you will want to somehow visually show that the interaction is blocked (so that users do not think that the application is broken) - this is why the standard functional blocksInteractionBelow () depends on the value of getScrimOpacity () . Returning a value not equal to zero, at once we draw a color ( getScrimColor () , black by default) over the View and turn off the touch interaction. Conveniently.

Interception of window inserts


Let's say you read the blog Why would I want to fitsSystemWindows? . There we discussed in detail what fitsSystemWindows does, but it all came down to representing window inserts (window insets), necessary to avoid drawing under system windows (such as status bar and navigation bar). Behavior can prove himself here. If your View fitsSystemWindows = “true” , then any connected Behavior will receive a call to the onApplyWindowInsets () method, in priority over the View itself.

Note : in most cases, if your Behavior does not handle window inserts entirely, it must pass these inserts using ViewCompat.dispatchApplyWindowInsets () to make sure that all child View will be able to see WindowInsets.

Interception Measurement and Layout


These are the two key components in how Android draws View . Therefore, it makes sense that Behavior, as an interceptor of all events, will also be the first to receive notification of resizing and layout by calling the onMeasureChild () and onLayoutChild () methods.

For example, let's take any generic ViewGroup and add maxWidth to it. As shown in the MaxWidthBehavior.java class.

Creating a generic Behavior that can work with any View is very convenient. But remember that you can simplify your life by considering whether Behavior will be used only inside your application (not all Behavior should be generic!).

Understanding the dependencies between the View


Everything described above requires a single View. But the real power of Behavior manifests itself in building dependencies between the View, that is, when a View changes, your Behavior receives a notification about this and changes its functionality depending on external conditions.

Behavior can make View dependent on each other in two different ways: when View is connected (anchor) to another View (implied dependency) or when you explicitly return true in the layoutDependsOn () method.

A binding occurs when your View inside the CoordinatorLayout uses the layout_anchor attribute. This attribute, combined with layout_anchorGravity , allows you to effectively link the position of two View together. For example, you can associate FloatingActionButton with AppBarLayout , then FloatingActionButton.Behavior will use an implicit dependency to hide the FAB if AppBarLayout is scrolled off the screen.

In any case, your Behavior will receive calls to the onDependentViewRemoved () method when the dependent View has been deleted and onDependentViewChanged () whenever the dependent View has changed (its size or position has changed).

Most of the cool functionality of the Design Library works because of the possibility of bundling the View together. Take for example the interaction between FloatingActionButton and Snackbar . The FAB Behavior depends on the Snackbar instances added to the CoordinatorLayout , then, using the onDependentViewChanged () method call, moves the FAB higher to prevent the Snackbar from overlapping.

Note : when you add a dependency, the View will always be placed after the dependent View, regardless of the sequence in the markup.

Nested scrolling


Oh, nested scrolling. I just touch this topic in a post. You need to keep in mind about several things:

  1. You should not declare dependencies on the View with nested scrolling. Each View inside the CoordinatorLayout can receive nested scroll events.
  2. Nested scrolling can occur not only for the View inside the CoordinatorLayout , but also for any child View (for example, the “child” of the “child” of the “child” of the CoordinatorLayout ).
  3. I will call this nested scrolling, but in fact it will be understood as just scrolling and fast scrolling (fling).

Nested scrolling events begin with the onStartNestedScroll () method. You will get the scroll axes (horizontal or vertical - we can easily ignore the scrolling in a certain direction) and must return true to receive further scroll events .

After you return true in the onStartNestedScroll () method, nested scrolling works in two steps:


There are similar methods for fast scrolling (but the pre-fling processing method should handle either all the scrolling, or not at all).

When nested (or fast) scrolling ends, you get a call to the onStopNestedScroll () method. This marks the end of the scrolling and waiting for a new call to the onStartNestedScroll () method before the new scrolling begins.

Take for example the occasion when you want to hide the FloatingActionButton when scrolling down and show FAB when scrolling up. To do this, override the onStartNestedScroll () and onNestedScroll () methods, as seen in ScrollAwareFABBehavior .

And this is just the beginning.


If every single part of Behavior is only interesting, then when they are all together, magic begins. I highly recommend viewing the Source Library Design Library for more features. The Chrome Android SDK Search extension is still one of my favorite resources for researching the Android Open Project AOSP code. In addition, you can find the latest versions of the source code along the path <android-sdk> / extras / android / m2repository .

Let me know how you use the basics on what Behavior hashtag #BuildBetterApps can do.

Join the discussion on Google+ and subscribe to the Android Development Patterns Collection for even more information!

Source: https://habr.com/ru/post/277813/


All Articles