One of the biggest challenges of building elegant mobile interfaces is making them adapt properly to the full gamut of screen sizes. This is especially true for Android development. Back in 2016, Google introduced a new container view class called ConstraintLayout which makes this task much easier.
One nice feature of the ConstraintLayout class is its ability to chain views in a horizontal or vertical sequence and then configure how those views will be distributed across the screen. There are a lot of other blog posts that describe how to set up a chain with Android’s XML-based layout framework, but I find there are often cases where you have to build views at runtime, which means you have to do it programmatically.
Unfortunately, I had a difficult time finding a good example, or sufficient documentation from Google, on how to create a chain of views in a ConstraintLayout programmatically. After a fair amount of trial and error (and frustration), I finally got it working. In the steps below, I’ll walk you through it.
Let’s say you have a list of TextViews that you need to arrange in a horizontal chain.
ConstraintLayout constraintLayout = ... some ConstraintLayout
List <textViews> = ... some list of views that you created;
First, you need to add the views to the ConstraintLayout.
for(TextView tv : textViews) {
tv.setId(generateViewId()); // Views must have IDs in order to add them to chain later.
constraintLayout.addView(tv);
}
Next, create a new ConstraintSet where you can add constraints. Clone the ConstraintLayout to initialize the ConstraintSet with its layout params.
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(constraintLayout);
Now things will start to get a little interesting. Loop over all of the TextViews and constrain them to the adjacent views. The first and last views get constrained to the edges of the parent view. It’s a little disappointing that the ConstraintLayout doesn’t do this for us.
View previousItem = null;
for(TextView tv : textViews) {
boolean lastItem =textViews.indexOf(tv) ==textViews.size() - 1;
if(previousItem == null) {
constraintSet.connect(tv.getId(), ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.LEFT);
} else {
constraintSet.connect(tv.getId(), ConstraintSet.LEFT, previousItem.getId(), ConstraintSet.RIGHT);
if(lastItem) {
constraintSet.connect(tv.getId(), ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT);
}
}
previousItem = tv;
}
Finally, you need to tell the ConstraintSet to create the chain and communicate how you want it to distribute the items in the chain. In this case, I’m using “ConstraintSet.CHAIN_SPREAD,” which means it will evenly distribute the items.
Once that’s done, apply all of these constraints to the ConstraintLayout by calling applyTo
.
int[] viewIds = ByteUtils.toIntArray(new ArrayList<>(Collections2.transform(textViews, View::getId)));
constraintSet.createHorizontalChain(ConstraintSet.PARENT_ID, ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT, viewIds, null, ConstraintSet.CHAIN_SPREAD);
constraintSet.applyTo(constraintLayout);
You may have noticed above that I made a call to ByteUtils.toIntArray
. That’s a helper method that takes a List of Integers (List) and returns an equivalent array of ints (int[]). Without it, you’ll have to iterate over your list of views and add each item’s ID
to an array.
That’s it! As you can see, chaining views programmatically is a little nuanced, but it’s not too difficult.
Ever since I discovered the ConstraintLayout, I’ve been a big fan. I think it makes laying out UIs for Android much more tolerable, and it feels a lot more like the constraint-based layout system used in iOS, which I generally prefer.
Nice article! a precision: you should not need to manually build up connections — this is exactly what createHorizontalChain() and createVerticalChain() do.
Thank you so much Jordan! You have no idea how long I’ve been looking for more information on generating CL chains programatically. This helped me achieve the design I wanted.
What needs to be imported for using ByteUtils? I have an error while using it.