Using ConstraintLayout

When you converted list_item_crime.xml to use ConstraintLayout, Android Studio automatically added the constraints it thinks will replicate the behavior of your old layout. However, to learn how constraints work you are going to start from scratch.

Select the top-level view in the component tree, labeled linearLayout. Why does it say linearLayout, when you converted your view to a ConstraintLayout? That is the ID the ConstraintLayout converter supplied. linearLayout is, in fact, your ConstraintLayout. You can check the XML version of your layout if you want to confirm this.

With linearLayout selected in the component tree, click the Clear All Constraints button (shown in Figure 11.8) and confirm your intentions in the pop-up. You will immediately see red warning flags, including one at the top right of the screen. Click it to see what that is all about (Figure 11.9).

Figure 11.9  ConstraintLayout warnings

ConstraintLayout warnings

When views do not have enough constraints, ConstraintLayout cannot know exactly where to put them. Your TextViews have no constraints at all, so they each have a warning that says they will not appear in the right place at runtime.

In the preview, the views look the same as they did when you were using a LinearLayout. But – as the errors indicate – the positioning you see in the preview is not what you would see if you ran the app. The preview allows you to position your views anywhere on the canvas to make it easier to add your constraints, but these positions are only valid in the preview, not at runtime.

As you go through the chapter, you will add constraints to fix those warnings. In your own work, keep an eye on that warning indicator to avoid unexpected behavior at runtime.

Making room

You need to make some room to work in the layout editor. Your two TextViews are taking up the entire area, which will make it hard to wire up anything else. Time to shrink those two views.

Select crime_title in the component tree and look at the attributes window on the right (Figure 11.10). If this window is not open for you, click the Attributes tab on the right to open it.

Figure 11.10  Title TextView’s attributes

Title TextView’s attributes

The vertical and horizontal sizes of your TextView are governed by the height setting and width setting, respectively. There are three setting types for height and width (shown in Figure 11.11 and summarized in Table 11.1), each of which corresponds to a value for layout_height or layout_width.

Figure 11.11  Three view size settings

Three view size settings

Table 11.1  View size setting types

Setting type

Setting value

Usage

fixed

Xdp

Specifies an explicit size (that will not change) for the view. The size is specified in dp units and should be a positive number. (If you need a refresher on dp units, see the section called Screen Pixel Densities in Chapter 2.)

wrap content

wrap_content

Assigns the view its desired size. For a TextView, this means that the size will be just big enough to show its contents.

match constraint

0dp

Allows the view to stretch to meet the specified constraints.

Both the title and date TextViews are set to a large fixed width, which is why they are taking up the whole screen. Adjust the width and height of both views. With crime_title still selected in the component tree, click the width setting until it cycles around to the wrap content setting (or use the drop-down menu to choose the setting). If necessary, adjust the height setting until the height is also set to wrap content (Figure 11.12).

Figure 11.12  Adjusting the title width and height

Adjusting the title width and height

Repeat the process with the crime_date view to set its width and height.

Now the two views are the correct size (Figure 11.13).

Figure 11.13  Correctly sized TextViews

Correctly sized TextViews

This change has not fixed the errors in your layout, because your views still have no constraints. You will add constraints to correctly position your TextViews and get rid of the errors later. First, you will add the third view you need in your layout.

Adding views

With your other views out of the way, you can add the handcuffs image to your layout. Add an ImageView to your layout file. In the palette, find ImageView in the Common category (Figure 11.14). Drag it into your component tree as a child of the ConstraintLayout, just underneath crime_date.

Figure 11.14  Finding the ImageView

Finding the ImageView

In the dialog that pops up, scroll to the CriminalIntent.app.main section and choose ic_solved as the resource for the ImageView (Figure 11.15). This image will be used to indicate which crimes have been solved. Click OK.

Figure 11.15  Choosing the ImageView’s resource

Choosing the ImageView’s resource

(The “ic” is short for “icon,” by the way. Just as fragment layout files begin with the “fragment_” prefix and activity layout files begin with the “activity_” prefix, it is a convention to prefix your icons with the “ic_” prefix. That allows you to easily organize your various drawables.)

The ImageView is now a part of your layout, but it has no constraints. So while the layout editor gives it a position, that position does not really mean anything.

Click your ImageView in the preview. (You may want to zoom the preview in to get a better look. The zoom controls are in the toolbar in the lower right of the canvas.) You will see circles on each side of the ImageView (Figure 11.16). Each of these circles represents a constraint handle.

Figure 11.16  ImageView’s constraint handles

ImageView’s constraint handles

You want the ImageView to be anchored on the right side of the view and centered vertically. To accomplish this, you need to create constraints from the top, right, and bottom edges of the ImageView.

Before adding constraints, drag the ImageView to the right and down to move it away from the TextViews (Figure 11.17). Do not worry about where you place the ImageView. This placement will be ignored once you get your constraints in place.

Figure 11.17  Moving a view temporarily

Moving a view temporarily

Time to add some constraints. First, you are going to set a constraint between the top of the ImageView and the top of the ConstraintLayout. In the preview, drag the top constraint handle from the ImageView to the top of the ConstraintLayout. The handle will display an arrow and turn solid blue (Figure 11.18).

Figure 11.18  Part of the way through creating a top constraint

Part of the way through creating a top constraint

Watch for the constraint handle to turn blue, then release the mouse to create the constraint (Figure 11.19).

Figure 11.19  Creating a top constraint

Creating a top constraint

Be careful to avoid dragging one of the square handles in the corner of the image view – this will resize it instead. Also, make sure you do not inadvertently attach the constraint to one of your TextViews. If you do, click the constraint handle to delete the bad constraint, then try again.

When you let go and set the constraint, the view will snap into position to account for the presence of the new constraint. This is how you move views around in a ConstraintLayout – by setting and removing constraints.

Verify that your ImageView has a top constraint connected to the top of the ConstraintLayout by hovering over the top constraint handle with your mouse. You should see an animated box around the constraint layout, with the top edge blue to show where the constraint handle is connected (Figure 11.20).

Figure 11.20  ImageView with a top constraint

ImageView with a top constraint

Do the same for the bottom constraint handle, dragging it from the ImageView to the bottom of the root view (Figure 11.21), again taking care to avoid attaching it to the TextViews.

Figure 11.21  ImageView with top and bottom constraints

ImageView with top and bottom constraints

(The squiggly lines you see in the preview represent constraints that are stretching.)

Finally, drag the right constraint handle from the ImageView to the right side of the root view. That should set all the constraints for the image view. Your constraints should look like Figure 11.22.

Figure 11.22  ImageView’s three constraints

ImageView’s three constraints

ConstraintLayout’s inner workings

Any edits that you make to constraints with the layout editor are reflected in the XML behind the scenes. You can still edit the raw ConstraintLayout XML, but the layout editor will often be easier for adding the initial constraints. ConstraintLayout is more verbose than other ViewGroups, so adding the initial constraints manually can be a lot of work. On the other hand, working directly with the XML can be more useful when you need to make smaller changes to the layout.

Switch to the code view to see what happened to the XML when you created the three constraints on your ImageView:

    <androidx.constraintlayout.widget.ConstraintLayout
            ... >
        ...
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:srcCompat="@drawable/ic_solved" />

    </androidx.constraintlayout.widget.ConstraintLayout>

(You will still see errors related to the two TextViews. Do not worry – you will fix them shortly.)

All the views are direct children of the single ConstraintLayout – there are no nested layouts. If you had created the same layout using LinearLayout, you would have had to nest one inside another. As we said earlier, reducing nesting also reduces the time needed to render the layout, and that results in a quicker, more seamless user experience.

Take a closer look at the top constraint on the ImageView:

    app:layout_constraintTop_toTopOf="parent"

This attribute begins with layout_. All attributes that begin with layout_ are known as layout parameters. Unlike other attributes, layout parameters are directions to that view’s parent, not the view itself. They tell the parent layout how to arrange the child element within itself. You have seen a few layout parameters so far, like layout_width and layout_height.

The name of the constraint is constraintTop. This means that this is the top constraint on your ImageView.

Finally, the attribute ends with toTopOf="parent". This means that this constraint is connected to the top edge of the parent. The parent here is the ConstraintLayout.

Whew, what a mouthful. Time to leave the raw XML behind and return to the layout editor.

(The layout editor’s tools are useful, especially with ConstraintLayout. But not everyone is a fan. You do not have to choose sides – you can switch back and forth between the layout editor and directly editing XML. After this chapter, use whatever approach you prefer to create the layouts in this book – XML, layout editor, or some of each.)

Editing properties

Your ImageView is almost positioned correctly. Since the parent view is larger than the image and the image is centered vertically, it looks good on the vertical axis. However, the image is flush against the right side of the parent view. This looks a little off.

With the image still selected in the preview, check out the attributes window to the right. Because you added constraints to the top, bottom, and right of the ImageView, drop-down menus appear to allow you to select the margin for each constraint (Figure 11.23). You do not need to add margins to the top or bottom, but select 16dp for the right margin.

Figure 11.23  Adding a margin to the end of the ImageView

Adding a margin to the end of the ImageView

Notice that Android Studio offers you margin values in increments of 8dp. These values follow Android’s material design guidelines. You can find all the Android design guidelines at developer.android.com/​design/​index.html. Your Android apps should follow these guidelines as closely as possible.

With that taken care of, let’s move on to the text. Start with the position and size of the title TextView.

First, select Crime Date in the preview and drag it out of the way (Figure 11.24). Remember that any changes you make to the position in the preview will not be represented when the app is running. At runtime, only constraints remain.

Figure 11.24  Get out of here, date

Get out of here, date

Now, select crime_title in the component tree. This will also highlight Crime Title in the preview.

You want Crime Title to be at the top left of your layout, positioned to the left of your ImageView. That requires three constraints:

  • from the left side of your view to the left side of the parent

  • from the top of your view to the top of the parent

  • from the right of your view to the left side of the ImageView

Modify your layout so that all these constraints are in place (Figure 11.25). If a constraint does not work as you expected, press Command-Z (Ctrl-Z) to undo and try again.

Figure 11.25  TextView constraints

TextView constraints

Now you are going to add margins to the constraints on your TextView. With Crime Title still selected in the preview, check out the attributes window to the right. Because you added constraints to the top, left, and right of the TextView, drop-down menus appear to allow you to select the margin for each constraint (Figure 11.26). Select 16dp for the left and top margins and 8dp for the right margin.

Figure 11.26  Adding margins to the TextView

Adding margins to the TextView

Verify that your constraints look like Figure 11.27.

Figure 11.27  Title TextView’s constraints

Title TextView’s constraints

Now that the constraints are set up, you can restore the width of the title TextView to its full glory. Adjust its horizontal view setting to 0 dp (match constraint) to allow the TextView to fill all the space available within its constraints. Make the vertical view setting wrap_content, if it is not already, so that the TextView will be just tall enough to show the title of the crime. Verify that your settings match those shown in Figure 11.28.

Figure 11.28  crime_title view settings

crime_title view settings

Now, add constraints to the date TextView. Select crime_date in the component tree. Repeat the steps from the title TextView to add three constraints:

  • from the left side of your view to the left side of the parent, with a 16dp margin

  • from the top of your view to the bottom of the crime title, with an 8dp margin

  • from the right of your view to the left side of the ImageView, with an 8dp margin

After adding the constraints, adjust the properties of the TextView. You want the width of your date TextView to be match_constraint and the height to be wrap_content, just like the title TextView. Verify that your settings match those shown in Figure 11.29.

Figure 11.29  crime_date view settings

crime_date view settings

Your layout in the preview should look similar to Figure 11.1, at the beginning of the chapter. Up close, your preview should match Figure 11.30.

Figure 11.30  Final constraints up close

Final constraints up close

Switch to the code view in the editor tool window to review the XML resulting from the changes you made in the layout editor. Red underlines no longer appear under the TextView tags. This is because the TextView views are now adequately constrained, so the ConstraintLayout that contains them can figure out where to properly position the views at runtime.

Two yellow warning indicators remain related to the TextViews, and if you explore them you will see that the warnings have to do with their hardcoded strings. These warnings would be important for a production application, but for CriminalIntent you can disregard them. (If you prefer, feel free to follow the advice to extract the hardcoded text into string resources. This will resolve the warnings.)

Additionally, one warning remains on the ImageView, indicating that it does not have a content description. For now, you can disregard this warning as well. You will address this issue when you learn about accessibility in Chapter 19. In the meantime, your app will function, although the image will not be accessible to users utilizing a screen reader.

Run CriminalIntent and verify that you see all three components lined up nicely in each row of your RecyclerView (Figure 11.31).

Figure 11.31  Now with three views per row

Now with three views per row

Making list items dynamic

Now that the layout includes the correct constraints, update the ImageView so that the handcuffs are only shown on crimes that have been solved.

First, update the ID of your ImageView. When you added the ImageView to your ConstraintLayout, it was given a default name. That name is not very descriptive. In the design view, select your ImageView and, in the attributes window, update the ID attribute to crime_solved (Figure 11.32).

Figure 11.32  Updating the image ID

Updating the image ID

You will be asked whether Android Studio should update all usages of the ID; select Refactor. Next, Android Studio will warn you that you are already using the ID crime_solved (Figure 11.33).

Figure 11.33  Reusing an ID

Reusing an ID

The crime_solved ID is used in both the list_item_crime.xml and fragment_crime_detail.xml layouts. You might think that reusing an ID would be a problem, but in this case it is not. Layout IDs only need to be unique in the same layout. Since your IDs are defined in different layout files, there is no problem using the same ID in both. Click Continue to ignore this warning.

With a proper ID in place, you are ready to update your code. Open CrimeListAdapter.kt. In CrimeHolder, add an ImageView instance variable and toggle its visibility based on the solved status of the crime.

Listing 11.1  Updating handcuff visibility (CrimeListAdapter.kt)

class CrimeHolder(
    private val binding: ListItemCrimeBinding
) : RecyclerView.ViewHolder(binding.root) {
    ...
    fun bind(crime: Crime) {
        ...
        binding.root.setOnClickListener {
            Toast.makeText(
                binding.root.context,
                "${crime.title} clicked!",
                Toast.LENGTH_SHORT
            ).show()
        }

        binding.crimeSolved.visibility = if (crime.isSolved) {
            View.VISIBLE
        } else {
            View.GONE
        }
    }
    ...
}

Run CriminalIntent and verify that the handcuffs now appear on every other row. (Check CrimeListViewModel if you do not recall why this would be the case.)

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset