23. Managing Constraints using Constraint Sets
Up until this point in the book, all user interface design tasks have been performed using the Android Studio Layout Editor tool, either in text or design mode. An alternative to writing XML resource files or using the Android Studio Layout Editor is to write Java code to directly create, configure and manipulate the view objects that comprise the user interface of an Android activity. Within the context of this chapter, we will explore some of the advantages and disadvantages of writing Java code to create a user interface before describing some of the key concepts such as view properties and the creation and management of layout constraints.
In the next chapter, an example project will be created and used to demonstrate some of the typical steps involved in this approach to Android user interface creation.
23.1 Java Code vs. XML Layout Files
There are a number of key advantages to using XML resource files to design a user interface as opposed to writing Java code. In fact, Google goes to considerable lengths in the Android documentation to extol the virtues of XML resources over Java code. As discussed in the previous chapter, one key advantage to the XML approach includes the ability to use the Android Studio Layout Editor tool, which, itself, generates XML resources. A second advantage is that once an application has been created, changes to user interface screens can be made by simply modifying the XML file, thereby avoiding the necessity to recompile the application. Also, even when hand writing XML layouts, it is possible to get instant feedback on the appearance of the user interface using the preview feature of the Android Studio Layout Editor tool. In order to test the appearance of a Java created user interface the developer will, inevitably, repeatedly cycle through a loop of writing code, compiling and testing in order to complete the design work.
In terms of the strengths of the Java coding approach to layout creation, perhaps the most significant advantage that Java has over XML resource files comes into play when dealing with dynamic user interfaces. XML resource files are inherently most useful when defining static layouts, in other words layouts that are unlikely to change significantly from one invocation of an activity to the next. Java code, on the other hand, is ideal for creating user interfaces dynamically at run-time. This is particularly useful in situations where the user interface may appear differently each time the activity executes subject to external factors.
A knowledge of working with user interface components in Java code can also be useful when dynamic changes to a static XML resource based layout need to be performed in real-time as the activity is running.
Finally, some developers simply prefer to write Java code than to use layout tools and XML, regardless of the advantages offered by the latter approaches.
23.2 Creating Views
As previously established, the Android SDK includes a toolbox of view classes designed to meet most of the basic user interface design needs. The creation of a view in Java is simply a matter of creating instances of these classes, passing through as an argument a reference to the activity with which that view is to be associated.
The first view (typically a container view to which additional child views can be added) is displayed to the user via a call to the setContentView() method of the activity. Additional views may be added to the root view via calls to the object’s addView() method.
When working with Java code to manipulate views contained in XML layout resource files, it is necessary to obtain the ID of the view. The same rule holds true for views created in Java. As such, it is necessary to assign an ID to any view for which certain types of access will be required in subsequent Java code. This is achieved via a call to the setId() method of the view object in question. In later code, the ID for a view may be obtained via the object’s getId() method.
23.3 View Attributes
Each view class has associated with it a range of attributes. These property settings are set directly on the view instances and generally define how the view object will appear or behave. Examples of attributes are the text that appears on a Button object, or the background color of a ConstraintLayout view. Each view class within the Android SDK has a pre-defined set of methods that allow the user to set and get these property values. The Button class, for example, has a setText() method which can be called from within Java code to set the text displayed on the button to a specific string value. The background color of a ConstraintLayout object, on the other hand, can be set with a call to the object’s setBackgroundColor() method.
23.4 Constraint Sets
While property settings are internal to view objects and dictate how a view appears and behaves, constraint sets are used to control how a view appears relative to its parent view and other sibling views. Every ConstraintLayout instance has associated with it a set of constraints that define how its child views are positioned and constrained.
The key to working with constraint sets in Java code is the ConstraintSet class. This class contains a range of methods that allow tasks such as creating, configuring and applying constraints to a ConstraintLayout instance. In addition, the current constraints for a ConstraintLayout instance may be copied into a ConstraintSet object and used to apply the same constraints to other layouts (with or without modifications).
A ConstraintSet instance is created just like any other Java object:
ConstraintSet set = new ConstraintSet();
Once a constraint set has been created, methods can be called on the instance to perform a wide range of tasks.
23.4.1 Establishing Connections
The connect() method of the ConstraintSet class is used to establish constraint connections between views. The following code configures a constraint set in which the left-hand side of a Button view is connected to the right-hand side of an EditText view with a margin of 70dp:
set.connect(button1.getId(), ConstraintSet.LEFT,
editText1.getId(), ConstraintSet.RIGHT, 70);
23.4.2 Applying Constraints to a Layout
Once the constraint set is configured, it must be applied to a ConstraintLayout instance before it will take effect. A constraint set is applied via a call to the applyTo() method, passing through a reference to the layout object to which the settings are to be applied:
set.applyTo(myLayout);
23.4.3 Parent Constraint Connections
Connections may also be established between a child view and its parent ConstraintLayout by referencing the ConstraintSet.PARENT_ID constant. In the following example, the constraint set is configured to connect the top edge of a Button view to the top of the parent layout with a margin of 100dp:
set.connect(button1.getId(), ConstraintSet.TOP,
ConstraintSet.PARENT_ID, ConstraintSet.TOP, 100);
23.4.4 Sizing Constraints
A number of methods are available for controlling the sizing behavior of views. The following code, for example, sets the horizontal size of a Button view to wrap_content and the vertical size of an ImageView instance to a maximum of 250dp:
set.constrainWidth(button1.getId(), ConstraintSet.WRAP_CONTENT);
set.constrainMaxHeight(imageView1.getId(), 250);
23.4.5 Constraint Bias
As outlined in the chapter entitled “A Guide to using ConstraintLayout in Android Studio”, when a view has opposing constraints it is centered along the axis of the constraints (i.e. horizontally or vertically). This centering can be adjusted by applying a bias along the particular axis of constraint. When using the Android Studio Layout Editor, this is achieved using the controls in the Attributes tool window. When working with a constraint set, however, bias can be added using the setHorizontalBias() and setVerticalBias() methods, referencing the view ID and the bias as a floating point value between 0 and 1.
The following code, for example, constrains the left and right-hand sides of a Button to the corresponding sides of the parent layout before applying a 25% horizontal bias:
set.connect(button1.getId(), ConstraintSet.LEFT,
ConstraintSet.PARENT_ID, ConstraintSet.LEFT, 0);
set.connect(button1.getId(), ConstraintSet.RIGHT,
ConstraintSet.PARENT_ID, ConstraintSet.RIGHT, 0);
set.setHorizontalBias(button1.getId(), 0.25f);
23.4.6 Alignment Constraints
Alignments may also be applied using a constraint set. The full set of alignment options available with the Android Studio Layout Editor may also be configured using a constraint set via the centerVertically() and centerHorizontally() methods, both of which take a variety of arguments depending on the alignment being configured. In addition, the center() method may be used to center a view between two other views.
In the code below, button2 is positioned so that it is aligned horizontally with button1:
set.centerHorizontally(button2.getId(), button1.getId());
23.4.7 Copying and Applying Constraint Sets
The current constraint set for a ConstraintLayout instance may be copied into a constraint set object using the clone() method. The following line of code, for example, copies the constraint settings from a ConstraintLayout instance named myLayout into a constraint set object:
set.clone(myLayout);
Once copied, the constraint set may be applied directly to another layout or, as in the following example, modified before being applied to the second layout:
ConstraintSet set = new ConstraintSet();
set.clone(myLayout);
set.constrainWidth(button1.getId(), ConstraintSet.WRAP_CONTENT);
set.applyTo(mySecondLayout);
23.4.8 ConstraintLayout Chains
Vertical and horizontal chains may also be created within a constraint set using the createHorizontalChain() and createVerticalChain() methods. The syntax for using these methods is as follows:
createHorizontalChain(int leftId, int leftSide, int rightId,
int rightSide, int[] chainIds, float[] weights, int style);
Based on the above syntax, the following example creates a horizontal spread chain that starts with button1 and ends with button4. In between these views are button2 and button3 with weighting set to zero for both:
int[] chainViews = {button2.getId(), button3.getId()};
float[] chainWeights = {0, 0};
set.createHorizontalChain(button1.getId(), ConstraintSet.LEFT,
button4.getId(), ConstraintSet.RIGHT,
chainViews, chainWeights,
ConstraintSet.CHAIN_SPREAD);
A view can be removed from a chain by passing the ID of the view to be removed through to either the removeFromHorizontalChain() or removeFromVerticalChain() methods. A view may be added to an existing chain using either the addToHorizontalChain() or addToVerticalChain() methods. In both cases the methods take as arguments the IDs of the views between which the new view is to be inserted as follows:
set.addToHorizontalChain(newViewId, leftViewId, rightViewId);
23.4.9 Guidelines
Guidelines are added to a constraint set using the create() method and then positioned using the setGuidelineBegin(), setGuidelineEnd() or setGuidelinePercent() methods. In the following code, a vertical guideline is created and positioned 50% across the width of the parent layout. The left side of a button view is then connected to the guideline with no margin:
set.create(R.id.myGuideline, ConstraintSet.VERTICAL_GUIDELINE);
set.setGuidelinePercent(R.id.myGuideline, 0.5f);
set.connect(button.getId(), ConstraintSet.LEFT,
R.id.myGuideline, ConstraintSet.RIGHT, 0);
set.applyTo(layout);
23.4.10 Removing Constraints
A constraint may be removed from a view in a constraint set using the clear() method, passing through as arguments the view ID and the anchor point for which the constraint is to be removed:
set.clear(button.getId(), ConstraintSet.LEFT);
Similarly, all of the constraints on a view may be removed in a single step by referencing only the view in the clear() method call:
set.clear(button.getId());
23.4.11 Scaling
The scale of a view within a layout may be adjusted using the ConstraintSet setScaleX() and setScaleY() methods which take as arguments the view on which the operation is to be performed together with a float value indicating the scale. In the following code, a button object is scaled to twice its original width and half the height:
set.setScaleX(myButton.getId(), 2f);
set.setScaleY(myButton.getId(), 0.5f);
23.4.12 Rotation
A view may be rotated on either the X or Y axis using the setRotationX() and setRotationY() methods respectively both of which must be passed the ID of the view to be rotated and a float value representing the degree of rotation to be performed. The pivot point on which the rotation is to take place may be defined via a call to the setTransformPivot(), setTransformPivotX() and setTransformPivotY() methods. The following code rotates a button view 30 degrees on the Y axis using a pivot point located at point 500, 500:
set.setTransformPivot(button.getId(), 500, 500);
set.setRotationY(button.getId(), 30);
set.applyTo(layout);
Having covered the theory of constraint sets and user interface creation from within Java code, the next chapter will work through the creation of an example application with the objective of putting this theory into practice. For more details on the ConstraintSet class, refer to the reference guide at the following URL:
https://developer.android.com/reference/android/support/constraint/ConstraintSet.html
23.5 Summary
As an alternative to writing XML layout resource files or using the Android Studio Layout Editor tool, Android user interfaces may also be dynamically created in Java code.
Creating layouts in Java code consists of creating instances of view classes and setting attributes on those objects to define required appearance and behavior.
How a view is positioned and sized relative to its ConstraintLayout parent view and any sibling views is defined through the use of constraint sets. A constraint set is represented by an instance of the ConstraintSet class which, once created, can be configured using a wide range of method calls to perform tasks such as establishing constraint connections, controlling view sizing behavior and creating chains.
With the basics of the ConstraintSet class covered in this chapter, the next chapter will work through a tutorial that puts these features to practical use.