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 Kotlin code to directly create, configure and manipulate the view objects comprising an Android activity’s user interface. This chapter will explore some advantages and disadvantages of writing Kotlin code to create a user interface before describing 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.
Kotlin Code vs. XML Layout Files
There are several advantages to using XML resource files to design a user interface instead of writing Kotlin code. In fact, Google goes to considerable lengths in the Android documentation to extol the virtues of XML resources over Kotlin code. As discussed in the previous chapter, one key advantage of the XML approach is using the Android Studio Layout Editor tool, which generates XML resources. A second advantage is that once an application has been created, changes to user interface screens can be made by modifying the XML file, thereby avoiding recompiling 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. To test the appearance of a Kotlin-created user interface, the developer will inevitably cycle through a loop of writing code, compiling, and testing to complete the design work.
Regarding the strengths of the Kotlin coding approach to layout creation, the most significant advantage that Kotlin 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, which are unlikely to change significantly from one invocation of an activity to the next. Kotlin code, on the other hand, is ideal for creating user interfaces dynamically at run-time. This is particularly useful when the user interface may appear differently each time the activity executes, subject to external factors.
Knowledge of working with user interface components in Kotlin code can also be useful when dynamic changes to a static XML resource-based layout must be performed in real time as the activity is running.
Finally, some developers prefer to write Kotlin code than to use layout tools and XML, regardless of the advantages offered by the latter approaches.
As previously established, the Android SDK includes a toolbox of view classes to meet most basic user interface design needs. The creation of a view in Kotlin is 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() activity method. Additional views may be added to the root view via calls to the object’s addView() method.
When working with Kotlin 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 Kotlin. As such, it is necessary to assign an ID to any view for which certain types of access will be required in subsequent Kotlin 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 id property.
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 Kotlin code to set the text displayed on the button to a specific string value. On the other hand, the background color of a ConstraintLayout object can be set with a call to the object’s setBackgroundColor() method.
While property settings are internal to view objects and dictate how a view appears and behaves, constraint sets 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 Kotlin 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 applied to other layouts (with or without modifications).
A ConstraintSet instance is created just like any other Kotlin object:
val set = ConstraintSet()Code language: Kotlin (kotlin)
Once a constraint set has been created, methods can be called on the instance to perform a wide range of tasks.
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 righthand side of an EditText view with a margin of 70dp:
set.connect(button1.id, ConstraintSet.LEFT, editText1.id, ConstraintSet.RIGHT, 70)Code language: Kotlin (kotlin)
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)Code language: Kotlin (kotlin)
Parent Constraint Connections
Connections between a child view and its parent ConstraintLayout may also be established 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.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, 100)Code language: Kotlin (kotlin)
Several 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.id, ConstraintSet.WRAP_CONTENT) set.constrainMaxHeight(imageView1.id, 250)Code language: Kotlin (kotlin)
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 constraint axis. 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.id, ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.LEFT, 0) set.connect(button1.id, ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT, 0) set.setHorizontalBias(button1.id, 0.25f)Code language: Kotlin (kotlin)
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 various 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.id, button1.id)Code language: Kotlin (kotlin)
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)Code language: Kotlin (kotlin)
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:
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:
createVerticalChain(int topId, int topSide, int bottomId, int bottomSide, int chainIds, float weights, int style)Code language: Kotlin (kotlin)
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:
val set = ConstraintSet() val chainViews = intArrayOf( button2.id, button3.id ) val chainWeights = floatArrayOf(0f, 0f) set.createHorizontalChain(button1.id, ConstraintSet.LEFT, button4.id, ConstraintSet.RIGHT, chainViews, chainWeights, ConstraintSet.CHAIN_SPREAD)Code language: Kotlin (kotlin)
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)Code language: Kotlin (kotlin)
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:
val set = ConstraintSet() set.create(R.id.myGuideline, ConstraintSet.VERTICAL_GUIDELINE) set.setGuidelinePercent(R.id.myGuideline, 0.5f) set.connect(button.id, ConstraintSet.LEFT, R.id.myGuideline, ConstraintSet.RIGHT, 0) set.applyTo(layout)Code language: Kotlin (kotlin)
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.id, ConstraintSet.LEFT)Code language: Kotlin (kotlin)
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.id)Code language: Kotlin (kotlin)
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.id, 2f) set.setScaleY(myButton.id, 0.5f)Code language: Kotlin (kotlin)
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.id, 500, 500) set.setRotationY(button.id, 30) set.applyTo(layout)Code language: Kotlin (kotlin)
Having covered the theory of constraint sets and user interface creation from within Kotlin code, the next chapter will work through creating an example application to put this theory into practice. For more details on the ConstraintSet class, refer to the reference guide at the following URL:
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 Kotlin code.
Creating layouts in Kotlin 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 are defined using 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.