This library adds missing support for Constraint Layout in Anko library. It is
based on the 1.1.3
version of the library and supports Group
, Barrier
and Placeholder
views as well.
Constraint Layout is defined and added to other ViewGroups in the same way as any other view in Anko:
anyViewGroupLayout {
constraintLayout {
val name = textView("David")
val surname = textView("Khol")
constraints {
name.connect(
STARTS of parentId with 16.dp,
TOPS of parentId with 16.dp
)
surname.connect(
TOP to BOTTOM of name,
STARTS of name
)
}
}.lparams(matchParent, matchParent)
}
or it can be also created through ViewManager
, Context
, Activity
as usual.
To correctly position all views inside the Constraint Layout, we can set layout params to each child it contains just as if we were using traditional xml definitions.
When defining layout programmatically, another approach is preferred. Instead of specifying layout
params for each child, we can specify relations between children through a ConstraintSet
.
ConstraintSet
adds extra helper methods to define constraints more expressively and intuitively.
You can create, define constraints into and then apply a Constraint Set to a Constraint Layout
using constraints
block:
constraintLayout {
// view definitions
constraints {
// view constraint definitions
}
}
Inside of the constraints
block we can use various methods to define our layout:
The most common thing to do with Constraint Layout is defining constraints. You can define a constraint
using View.connect()
method.
constraints {
name.connect(
STARTS of parentId with 16.dp,
TOPS of parentId with 16.dp
)
surname.connect(
TOP to BOTTOM of name,
STARTS of name
)
avatar.connect(
HORIZONTAL of name,
TOP to BOTTOM of name with 8.dp
)
}
View.connect()
method accepts variable amount of constraints. Each constraint is defined like this:
SIDE to SIDE of VIEW [with MARGIN]
- First
SIDE
defines side of the view we create constraints for. - Second
SIDE
defines side of the view we connect first view to. VIEW
defines the view we connect first view towith MARGIN
clause is optional and defines margin from the first view to the second one
Available SIDE
s are: LEFT
, RIGHT
, TOP
, BOTTOM
, BASELINE
, START
, END
To reduce boilerplate, instead of START to START
you can just specify STARTS
etc.
Additionally you can use HORIZONTAL
to define LEFTS
and RIGHTS
at the same time and
analogously VERTICAL
to define TOPS
and BOTTOMS
.
Moreover, you can use ALL
to define constraints for all four sides at the same time.
You can define chains
with chain()
, chainSpread()
, chainSpreadInside()
or chainPacked()
methods like this:
constraints {
val views = arrayOf(name, surname)
views.chainSpread(TOP of parentId, BOTTOM of parentId)
}
You have to define at least 2 elements of the chain. When either chain spread
or
chain spread inside
is used, you can also pass weights parameter to mimic functionality
of LinearLayout
and it's weights. To make weights work, you also have to set the
view's height or width to matchConstraint
(0dp).
For more information about chains, have a look at this great article by Noman Rafique.
You can define view's width and height with width()
and height()
or size()
methods.
Moreover you can define an dimension ratio
(also refered to as aspect ratio) for any view with dimensionRatio()
method. To make aspect
ratio work you must set at least one of height and width to matchConstraint
.
constraints {
image.size(matchConstraint, matchConstraint)
image.dimensionRatio("H,16:9")
}
You can create guidelines
in two ways - either as a standalone View
:
constraintLayout {
val topGuide: Guideline = horizontalGuidelineBegin(dip(24))
}
or as a part of Constraint Set:
constraints {
val leftGuideId: Int = verticalGuidelineBegin(dip(72))
}
Ultimately, it is up to you which one you want to use as you can make references to either of those
inside of connect()
method.
constraintLayout {
val name = textView("David")
val topGuide: Guideline = horizontalGuidelineBegin(dip(24))
constraints {
val leftGuideId: Int = verticalGuidelineBegin(dip(72))
name.connect(
STARTS of leftGuideId,
TOP of topGuide
)
}
}
All six combinations of helper methods (HORIZONTAL | VERTICAL
combined with
BEGIN | END | PERCENT
) are available.
Similarly to guidelines, you can also define barriers either as a standalone View
:
constraintLayout {
val descriptionBarrier: Barrier = barrierLeft(name, surname)
}
or as a part of Constraint Set:
constraints {
val descriptionBarrier: Int = barrierLeft(name, surname)
}
Ultimately, the use inside of connect()
method does not differ:
constraints {
// ...
description.connect(
ENDS of descriptionBarrier,
TOPS of parentId
)
}
You can define a group of views and control its visibility and elevation (and possibly more with future updates to the Constraint Layout library) for all referenced views.
constraintLayout {
val buttonsGroup = group(buttonOne, buttonTwo)
constraints {
buttonsGroup.visibility(View.GONE)
}
}
Additionally for both
Barrier
andGroup
you can easily add to and remove from referenced views withaddViews(vararg View)
andremoveViews(vararg View)
respectively.
If you constrain a view from both sides horizontally or vertically, you can also define bias.
constraints {
avatar.connect(
HORIZONTAL of parentId,
TOP of parentId
)
avatar.horizontalBias(0.2f)
}
Another option how to define the bias is to use View.center()
function which also
accepts a bias parameter:
constraints {
avatar.center(START of background, START of name, 0.2f)
}
You can define a placeholder and dynamically
replace the contents of the placeholder with placeholder.setContent(View)
method:
constraintLayout {
val placeholder = placeholder()
button("Click me") {
setOnClickListener {
placeholder.setContent(this@button)
}
}
constraints {
button.connect(/* add connections */)
placeholder.connect(/* add connections */)
}
}
When you set a view as a content of the placeholder, the view will be displayed with layout params of the placeholder. When you set another view as a content of the placeholder (or pass null as the content view), the original view will return to its original position and size.
See official docs for more information.
Prior to version 1.1.0-beta 5 it was not possible to define these dimensions through Constraint Set. To use them, you had to define attributes directly in the view's layout params.
constraintLayout {
button().lparams {
constrainedWidth = true
matchConstraintDefaultWidth = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT_PERCENT
matchConstraintPercentWidth = 0.8f
}
}
Now you can set percent dimensions to the Constraint Set.
constraints {
button.width(matchConstraint)
button.percentWidth(0.8f)
}
With version 1.1.0-beta 5 you can now set circular positioning through Constrain Set as well.
constraintLayout {
val centerView = view()
constraints {
button.circle(centerView, 32.dp, 45f)
}
}
Constraint Layout heavily depends on ids of its child views.
For that reason, each child has to have defined a unique id.
We can predefine static ids for each view in ids.xml
file to be generated by aapt and reference
them in code by R.id.name_of_view_id
. This can be burdensome to do for every view we add.
This library automatically generates a dynamic unique id for each view that is added to the
Constraint Layout and does not have a specified id.
This ensures positioning of views works correctly, but these ids do NOT get retained across
configuration changes. That means any view that save its state into a bundle to be persisted across
configuration change will NOT be able to restore its state.
For numerous views such as TextView
, Button
, ImageView
, etc. it is not a big deal because these
views usually don't modify their state based on user input. For other views such as EditText
,
CheckBox
, RadioButton
, SeekBar
, etc. it is strongly advised to specify a static id so that
Android framework can restore the view's state automatically.
If you want to disable this functionality, you may set generateIds to false.
val view = context.constraintLayout {
generateIds = false
// added views here won't have generated ids
generateIds = true
// added views here will once again have generated ids, unless an id has been assigned during their creation
}
Do NOT change ids of views after they have been added to the ConstraintLayout. ConstrainLayout internally stores references to its views via views' id when they have been added. Changing view's id and referencing it through its new id will not work and the view will most likely not even get displayed.
You can define and switch between multiple Constraint Sets by simply using constraints
block
multiple times and storing returned values.
You can also use prepareConstraints
block to define relations between views, but not apply it
to the Constraint Layout. This might be useful when you define multiple constraint sets and
don't want the changes from the first constraint set to be propagated to other ones.
constraintLayout {
val collapsedConstraintSet = prepareConstraints {
// set of constraints
}
val expandedConstraintSet = constraints {
// another set of constraints
}
}
Then during runtime you can easily switch between different layouts without recreating the layout.
if (isActivated) {
expandedConstraintSet
} else {
collapsedConstraintSet
}.applyTo(this@constraintLayout)
You can also chain calls like this:
background
.width(matchConstraint)
.connect(HORIZONTAL of parentId)
.connect(TOPS of parentId)
.dimensionRatio("H,1:1")
Sample app can be found in app
module.
Add this dependency to your project:
implementation 'io.github.ackeecz:anko-constraint-layout:2.0.0'
This library is based on the 1.1.3
version of support library so make sure you add appropriate
dependency.
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
For more information about Constraint Layout in general, check out these websites: