What to implement: ConstraintLayout or CoordinatorLayout for proper material design in android ?
CoordinatorLayout is a super-powered FrameLayout.
CoordinatorLayout
CoordinatorLayout is intended for two primary use cases:
As a top-level application decor or chrome layout
As a container for a specific interaction with one or more child views
By default, if you add multiple children to a FrameLayout, they would overlap each other. A FrameLayout should be used most often to hold a single child view. The main appeal of the CoordinatorLayout is its ability to coordinate the animations and transitions of the views within it. By specifying Behaviors for child views of a CoordinatorLayout you can provide many different interactions within a single parent and those views can also interact with one another. View classes can specify a default behavior when used as a child of a CoordinatorLayout using the CoordinatorLayout.DefaultBehavior annotation.
Behaviors may be used to implement a variety of interactions and additional layout modifications ranging from sliding drawers and panels to swipe-dismissable elements and buttons that stick to other elements as they move and animate.
ConstraintLayout is a super-powered ViewGroup similar to a RelativeLayout, but more flexible than RelativeLayout.
ConstraintLayout
ConstraintLayout allows you to create large and complex layouts with a flat view hierarchy (no nested view groups). It's similar to RelativeLayout in that all views are laid out according to relationships between sibling views and the parent layout, but it's more flexible than RelativeLayout and easier to use with Android Studio's Layout Editor.
ConstraintLayout can be used anywhere, you don't need any other ViewGroup like RelativeLayout, LinearLayout or FrameLayout once you start using ConstraintLayout.
There are currently various types of constraints that you can use:
Relative positioning
Margins
Centering positioning
Circular positioning
Visibility behavior
Dimension constraints
Chains
Virtual Helpers objects
Optimizer
What to implement: ConstraintLayout or CoordinatorLayout for proper material design in android ?
You may need to use both ConstraintLayout and CoordinatorLayout to build efficient UI and material animations.
A common example which uses both CoordinatorLayout and ConstraintLayout is given below for your reference.
Use Coordinatorlayout as the top-level application decor. It will usually used to layout AppBarLayout , FloatingActionButton, and the main body of your screen, say NestedScrollView. Inside the NestedScrollView use ConstraintLayout to describe the rest of the layout as a flat hierarchy.
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<!-- Your scrolling content -->
<androidx.constraintlayout.widget.ConstraintLayout
...>
<!-- body of constraint layout -->
<Button android:id="#+id/button" ...
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.appbar.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent">
<androidx.appcompat.widget.Toolbar
...
app:layout_scrollFlags="scroll|enterAlways"/>
<com.google.android.material.tabs.TabLayout
...
app:layout_scrollFlags="scroll|enterAlways"/>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
What do the above snippet? here you go.
We have placed the androidx.coordinatorlayout.widget.CoordinatorLayout as the root layout. And we put androidx.core.widget.NestedScrollView and com.google.android.material.appbar.AppBarLayout as direct children.
We defined app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" attribute for androidx.core.widget.NestedScrollView. This is the key point. We defined a behavior for the NestedScrollView. That is we are telling the Coordinator layout that the NestedScrollView depends on the AppBarLayout.
Of course, Behaviors don’t do anything on their own, but CoordinatorLayout does. It act accordingly and helps to intercept touch events, window insets, measurement, layout, and nested scrolling. So here, it places the NestedScrollView below the AppBarLayout as we instructed. Cool right?
We placed the ConstraintLayout inside the NestedScrollView to make it scrollable. As we already discussed, the ConstraintLayout is used to align child views with in the bounds of the ConstraintLayout.
Can I add ConstraintLayout inside another ConstraintLayout?
Of course yes, You can use any combination to align views as per your design requirements.
Can I add CoordinatorLayout inside another CoordinatorLayout ?
That is not a usual practice. the most common use case of CoordinatorLayout is as a the top-level application decor to coordinate between other direct children. But yes, if you really want to nest the CoordinatorLayout, you can do so by creating a custom CoordinatorLayout which extends the CoordinatorLayout and implements NestedScrollingChild to pass the scroll events to the parent CoordinatorLayout.
Bonus point
You can use the powerful MotionLayout which is a subclass of ConstraintLayout for building animations.
You may check this for a detailed example for custom animation using MotionLayout.
CoordinatorLayout is intended to be the top-level layout for activity to manage the Behaviors e.g. interactions and animations.
ConstraintLayout's main goal is to provide a convenient way to create a flat layout with multiple children (much more powerful RelativeLayout).
So the CoordinatorLayout is to manage the complex behavior (especially animations) of your activity's components, and ConstraintLayout for components proper placement (especially list items).
It seems like you (almost) always use a CoordinatorLayout, and sometimes use a ConstraintLayout inside. See the following resources
The codelab at https://codelabs.developers.google.com/codelabs/material-design-style/index.html#3 only uses a CoordinatorLayout
The example android-sunflower app ("illustrating Android development best practices") uses neither for the top-level activity, but uses both inside its fragment_plant_detail.xml, with the ConstraintLayout being inside the CoordinatorLayout:
<layout ...>
<data .../>
<android.support.design.widget.CoordinatorLayout ...>
<android.support.design.widget.AppBarLayout ...>
<android.support.design.widget.CollapsingToolbarLayout ...>
<ImageView... />
<android.support.v7.widget.Toolbar... />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView ...>
<android.support.constraint.ConstraintLayout ...>
<TextView.../>
<TextView... />
</android.support.constraint.ConstraintLayout>
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.FloatingActionButton ... />
</android.support.design.widget.CoordinatorLayout>
</layout>
#Darish has a great, comprehensive answer to this one.
I second everything he said and wanted to just add a little info. In my experience, a Constraint layout as the parent view is good enough most of the time. When you need to bring in the Coordinator Layout is when you have specific behaviors that you want to manage (for example Bottom Sheets). Coordinator Layout is more trouble than it's worth if you won't be using the behavior capabilities of it or if you are trying to mess with multiple views as CoordinatorLayout acts as a "super-powered FrameLayout".
I wrote a blog post a while ago with illustrations about the differences and usages of Coordinator vs. Constraint layout. Check it out here if you are interested.
I would also second the plug for MotionLayout as a great comprehensive way to add animations to your layouts without too much additional code! This Google Developers series with examples is a great way to get started with MotionLayout.
Related
I've the following use case -
<ScrollView> // parent
<ContentView/> // content
</ScrollView>
I do not have any access to the parent ScrollView because the library only allows setting the content view. And it doesn't provide any API or reference to modify the attributes of the parent ScrollView. And the content of ContentView is arbitrary in a way that it can take recycler-view, list-view and any other views with scrolling effects.
Because I do not have access to the parent ScrollView, I can't change its property / attributes anyhow. This causes the issue will ill-behaved scrolling. This specially shows the problem when
<ScrollView>
<ContentView>
<LinearLayout orientation=horizonal>
<RecyclerView/>
<RecyclerView/>
</LinearLayout>
<ContentView>
<ScrollView
And this causes two recycler-views to scroll together, while I want individual recycler-view scroll independently.
I was thinking of a solution that I can build a container-view and the children of container-view are agnostic of its ScrollView parent. So that my recycler-views have no knowledge of its parent ScrollView.
Is there any way to implement such solution? Have you ever encountered such an issue and how did you fix it?
I am working as OEM developer where we are using common GUI library for all Applications. In our application we are extending that GUI library which carry ScrollView as base layout for my application. Now my team is planning to use constrain layout in Application. Can we use constrain layout inside Scroll View?
I converted linear layout into constrain layout using Android Design tool. But
scrolling is not working.
<ScrollView
style="#style/Body_ScrollView"
android:id="#+id/no_sim_layout">
<LinearLayout style="#style/Body.LinearLayout.No_Sim">
<-- All other child views are going here-->
</LinearLayout>
</ScrollView>
Want to convert into
<ScrollView
style="#style/Body_ScrollView"
android:id="#+id/no_sim_layout">
<ConstrainLayout style="#style/Body.LinearLayout.No_Sim">
<-- All other child views are going here-->
</ConstrainLayout>
</ScrollView>
Yes, definitely the Constraint-layout first understand this:
Intention of ConstraintLayout is to optimize and flatten the view hierarchy of your layouts by applying some rules to each view to avoid nesting.Which recommends of Relative-layout rules.
More-ever it provide dynamic view alignment property as constraints, baseline, chaining of views and many other which provide seamless flatten hierarchy. If we used Constraint in Scrollview, we dont need to manage each view property like weighing in LinearLayout and many others. It much more simple and directly obtained benefits from provided dependencies.
<ConstrintLayout>
<ScrollView>
<ConstrintLayout>
//Single ParentConstrain else child
</ConstrintLayout>
</ScrollView>
</ConstrintLayout>
If goes by other way, this make your complex XML code more hard to understand and is heavy to build UI based on weighing and position calculated at run time on machine.
Constraint are directly benefit from input dependencies.
<ConstrintLayout>
<ScrollView>
<LinearLayout>
//Many different view to manage view
</LinearLayout>
</ScrollView>
</ConstrintLayout>
I'd like to create a Layout like this.
What is the best way to perform this?
There are several ways to do it, first and common step is define border around parent layout and define margin for child layouts. after that in second step you can use one of the following to achieve this.
you can use Linearayouts with orientation vertical and then by using weightsum and weights you can achieve this.
another approach is by using Relative. in relative layout you can provide other views position relating to other layout component position.
third approach is by using Constraint layouts, provide constraints and you will achieve this.
You can use this code to make that design:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="2"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#android:color/black"
android:layout_weight="0.4"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.6"></RelativeLayout>
</LinearLayout>
You can change values of layout_weight to change the rate.
Some of the ways to achieve this layout and a few a performance cautions with these are stated below:-
1.With a linear layouts using the weights parameters will cause a performance hit, as the it would cause the views to be measured twice before being layout.And we has a deeper heirarchy with linear layouts which again causes slow rendering.
With relative layouts , even though we get a flat heirarachy but the views are measured twice before drawn, again a nested relative layout (relative layout with in another relative layout) will cause the rendering time to increase as now, the view would be measured 4 times.
3.It would be better to use constraint layout to get the better performance with flater view heirarachy.
4.You might also want to consider using fragments if the inner layout has a menu structure causing changes in first child , with frame layout as the root parent.
A few links to understand about the performance benefits:-
Android Layout Tricks #1
Understanding the performance benefits of ConstraintLayout
Related question. Answer: RelativeLayout can't do it. I'm asking how to do it anyway, with not just RL, or with something else.
General story: you have a complex layout that would be difficult to adjust, and along comes a request for something to be added, aligning with a nested view.
What is the best approach? A popup with a custom style? (not familiar with those yet)? Spending days changing the whole hierarchy to a single RelativeLayout? A custom Layout class as wrapper?
AbsoluteLayout (deprecated) or FrameLayout with programmatically changed LayoutParams or margins? (this I'd rather avoid, I prefer not to touch onMeasure, etc)
Simplified example (no relation to pic above):
LinearLayout defines relative heights of the elements. I don't know to do it with RelativeLayout.
anExpandableView is something to be animated as sliding from under someBar (here; full-width, but perhaps it may need to align its width, as well as vertical position).
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="#+id/topStuff"
layout="#layout/incl_topstuff"
android:layout_width="match_parent"
android:layout_weight="7"
android:layout_height="0dip" />
<include
android:id="#+id/someBar"
layout="#layout/incl_filters_and_stuff"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include
android:id="#+id/bottomStuff"
layout="#layout/incl_bottomstuff"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="10" />
</LinearLayout>
<include
android:id="#+id/anExpandableView"
layout="#layout/incl_filters"
android:visibility="gone"
android:layout_below="#id/someBar"/>
</RelativeLayout>
I know SO has an aversion to general questions, but I don't want an ad-hoc solution. I am asking what to do in cases which would be solved if only a wrapping RelativeLayout would allow alignment to a view that is not a direct sibling.
Putting it simply, RelativeLayout can only measure and layout it's direct children based on each other, but I guess you already knew that.
The only general solution would be to implement your own custom Layout class, which I wouldn't recommend. If I had to guess why RelativeLayout does not traverse the entire layout hierarchy at it's level and below, it's probably for performance reasons.
Unfortunately if you're using RelativeLayouts and LinearLayouts and you want views to be dependent on each other you have to pick one approach and stick to it, either the flat hierarchy of RelativeLayout, or the nested one of LinearLayout.
Based on your example, as far as I know, there is no way to implement weighted views with a RelativeLayout, so you're stuck with using a LinearLayout.
The easiest way to do what you want is to inflate your expandableView in code, align it with the bottom of the RelativeLayout, set it's height and position based on bottomStuff, and animate from there.
If you really want to do it in xml, I can think of one somewhat hacky, ad-hoc approach, but which can can be generalized to mirroring the measurement and layout of any hierarchy with a bit of work.
Create a parallel but invisible LinearLayout that is a sibling of the first one. Give it an empty view with weight 7 on top, an invisible copy of someBar in the middle, then your expandable view under that with weight 10. To have it slide up, either animate the height of the invisible someBar and the weight of the empty view on top towards 0, or remove them/set them to gone and set animateLayoutChanges on your LinearLayout.
Should it be set at the AppBarLayout sibling's parent or at the first Scrollable View inside its sibling?
With Material Design for Android, there are Views that let us work with the behavior of the layout depending on its surroundings, one of them is the CoordinatorLayout, as this CodePath guide mentions:
CoordinatorLayout extends the ability to accomplish many of the
Google's Material Design scrolling effects. Currently, there are
several ways provided in this framework that allow it to work without
needing to write your own custom animation code.
The one I'm interested in now is:
Expanding or contracting the Toolbar or header space to make room for the main content.
So, we would use the AppBarLayout with a Toolbar with app:layout_scrollFlags set and another ViewGroup sibling to the AppBarLayout with app:layout_behavior.
My question is: in what exact ViewGroup (or maybe View) should we put that
app:layout_behavior?
So far, I've tried with (And they have all worked, and they are all siblings to the AppBarLayout):
Scrolling View
First ViewGroup inside a Scrollable View
ScrollView inside a ViewGroup
And this one didn't work:
ViewGroup with no Scrollable View children.
There are multiple examples online, but none of them really state where should you put it, like:
http://www.ingloriousmind.com/blog/quick-look-on-the-coordinatorlayout/
https://guides.codepath.com/android/Handling-Scrolls-with-CoordinatorLayout
https://developer.android.com/training/basics/firstapp/building-ui.html
https://www.bignerdranch.com/blog/becoming-material-with-android-design-support-library/
Check this link: https://developer.android.com/reference/android/support/design/widget/AppBarLayout.html
AppBarLayout also requires a separate scrolling sibling in order to
know when to scroll. The binding is done through the
AppBarLayout.ScrollingViewBehavior class, meaning that you
should set your scrolling view's behavior to be an instance of AppBarLayout.ScrollingViewBehavior. A string resource containing the
full class name is available.
They mentioned about that, it should be the View which will be shown under the AppBarLayout like this:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<!-- Your scrolling content -->
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent">
<android.support.v7.widget.Toolbar
...
app:layout_scrollFlags="scroll|enterAlways"/>
<android.support.design.widget.TabLayout
...
app:layout_scrollFlags="scroll|enterAlways"/>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
My question is: in what exact ViewGroup (or maybe View) should we put
that app:layout_behavior?
And in this link: http://guides.codepath.com/android/Handling-Scrolls-with-CoordinatorLayout
Next, we need to define an association between the AppBarLayout and
the View that will be scrolled. Add an app:layout_behavior to a
RecyclerView or any other View capable of nested scrolling such as
NestedScrollView. The support library contains a special string
resource #string/appbar_scrolling_view_behavior that maps to
AppBarLayout.ScrollingViewBehavior, which is used to notify the
AppBarLayout when scroll events occur on this particular view. The
behavior must be established on the view that triggers the event.
Make sure you added the appbar_scrolling_view_behavior field in your String.xml
<!-- The class name to the ScrollingChildBehavior required for AppBarLayout -->
<string name="appbar_scrolling_view_behavior" translatable="false">android.support.design.widget.AppBarLayout$ScrollingViewBehavior</string>
And as everyone knows we just can use this like below
<android.support.v7.widget.RecyclerView
android:id="#+id/rvSomeList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
Its just for info not OP answer.
app:layout_behavior should be set to those views which are direct child of Coordinator layout
AppBarLayout also requires a separate scrolling sibling in order to
know when to scroll.
This description from Android is woefully incomplete and caused me hours of wasted time.
Scrolling sibling is a misnomer and need not be a scrolling view of any type.
For example, below my AppBarLayout, I'm using a ViewPager2 that will render a Fragment that will render a Scrollview, so I needed to set app:layout_behavior="#string/appbar_scrolling_view_behavior" directly on the ViewPager2 in the main layout, NOT the deeply nested Scrollview in the fragment layout.
I also have no use for scrolling the AppBarLayout or any of its children on or off the screen, so I falsely assumed I could get away with not setting the app:layout_behavior anywhere.
Wrong.
This reveals a more insidious issue: AppBarLayout requires the scrolling sibling, yes. But not just to "know when to scroll", but to actually adjust the size of the sibling to fit properly on screen alongside it! Otherwise, the sibling maintains its configured size and will be nudged downward offscreen by the height of the AppBarLayout! You can even see this in Android Studio's layout editor.
Long story short: If you're going to use an AppBarLayout, you need to mark one of your views with app:layout_behavior="#string/appbar_scrolling_view_behavior", whether it's a scroll view or not.
I had to add the following to the gradle file otherwise it gave me a compile error.
implementation 'com.google.android.material:material:1.0.0'
Hope this would help some others too!
For someone who uses CoordinatorLayout with FragmentContainer and AppBarLayout:
It is really good to set the app:layout_behavior also on the container (not just on NestedScrollView or RecyclerView). It deletes unnecessary bottom margin of the FragmentContainer and guarantees that the appbar hides when the keyboard is shown.