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 it 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 10.8). You will immediately see red warning flags, including one at the top right of the screen. Click on it to see what that is all about (Figure 10.9).

Figure 10.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.

As you go through the chapter, you will add those constraints back 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 graphical 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 widgets.

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

Figure 10.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. These can be set to one of three view size settings (Figure 10.11), each of which corresponds to a value for layout_width or layout_height.

Figure 10.11  Three view size settings

Three view size settings

Table 10.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. (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

match_constraint

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 of these widgets. With crime_title still selected in the component tree, click the width setting until it cycles around to the wrap content setting. If necessary, adjust the height setting until the height is also set to wrap content (Figure 10.12).

Figure 10.12  Adjusting the title width and height

Adjusting the title width and height

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

Now the two widgets are the correct size (Figure 10.13), but they will still overlap when the app runs because they have no constraints. Note that the positioning you see in the preview differs from what you see when you run the app. The preview allows you to position the views to make it easier to add your constraints, but these positions are only valid in the preview, not at runtime.

Figure 10.13  Correctly sized TextViews

Correctly sized TextViews

You will add constraints to correctly position your TextViews later. First, you will add the third view you need in your layout.

Adding widgets

With your other widgets 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 10.14). Drag it into your component tree as a child of the ConstraintLayout, just underneath crime_date.

Figure 10.14  Finding the ImageView

Finding the ImageView

In the pop-up, expand the Project section and choose ic_solved as the resource for the ImageView (Figure 10.15). This image will be used to indicate which crimes have been solved. Click OK.

Figure 10.15  Choosing the ImageView’s resource

Choosing the ImageView’s resource

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

Time to add some constraints. Click on your ImageView in the preview pane. (You may want to zoom the preview in to get a better look. The zoom controls are in the toolbar above the constraint tools.) You will see dots on each side of the ImageView (Figure 10.16). Each of these dots represents a constraint handle.

Figure 10.16  ImageView’s constraint handles

ImageView’s constraint handles

You want the ImageView to be anchored in the right side of the view. 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 10.17). Do not worry about where you place the ImageView. This placement will be ignored once you get your constraints in place.

Figure 10.17  Moving a widget temporarily

Moving a widget temporarily

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 green (Figure 10.18).

Figure 10.18  Part of the way through creating a top constraint

Part of the way through creating a top constraint

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

Figure 10.19  Creating a top constraint

Creating a top constraint

Be careful to avoid clicking when the mouse cursor is a corner shape – this will resize your ImageView instead. Also, make sure you do not inadvertently attach the constraint to one of your TextViews. If you do, click on 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 ImageView with your mouse. It should look like Figure 10.20.

Figure 10.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 10.21), again taking care to avoid attaching it to the TextViews.

Figure 10.21  ImageView with top and bottom constraints

ImageView with top and bottom constraints

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

Figure 10.22  ImageView’s three constraints

ImageView’s three constraints

ConstraintLayout’s inner workings

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

(The graphical layout tools are useful, especially with ConstraintLayout. Not everyone is a fan, though. You do not have to choose sides – you can switch between the graphical layout editor and directly editing XML at any time. Use whichever tool you prefer to create the layouts in this book. You can decide for yourself how to create it – XML, graphical layout editor, or some of each.)

Switch to the text 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"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="8dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:srcCompat="@drawable/ic_solved" />

    </android.support.constraint.ConstraintLayout>

(You will still see errors related to the two TextViews. Leave them as is – you will fix them later.)

All of the widgets 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 widget’s parent, not the widget 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 graphical layout editor.

Editing properties

Your ImageView is now positioned correctly. Next up: Position and size the title TextView.

First, select Crime Date in the preview and drag it out of the way (Figure 10.23). 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 10.23  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 new 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 new ImageView

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

Figure 10.24  TextView constraints

TextView constraints

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

Figure 10.25  Adding margins to TextView

Adding margins to TextView

Notice that Android Studio defaulted to either a 16dp or a 8dp value for the margins. These values follow Android’s material design guidelines. You can find all of the Android design guidelines at developer.android.com/​design/​index.html. Your Android apps should follow these guidelines as closely as possible.

Verify that your constraints look like Figure 10.26. (The selected widget will show squiggly lines for any of its constraints that are stretching.)

Figure 10.26  Title TextView’s constraints

Title TextView’s constraints

Now that the constraints are set up, you can restore the title TextView to its full glory. Adjust its horizontal view setting to match_constraint to allow the title TextView to fill all of the space available within its constraints. Adjust the vertical view setting to 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 10.27.

Figure 10.27  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 10.28.

Figure 10.28  crime_date view settings

crime_date view settings

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

Figure 10.29  Final constraints up close

Final constraints up close

Switch to the text view in the editor tool window to review the XML resulting from the changes you made in the graphical layout editor. Red underlines no longer appear under each of the TextView tags. This is because the TextView widgets are now adequately constrained, so the ConstraintLayout that contains them can figure out where to properly position the widgets 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 you failed to set a content description. For now, you can disregard this warning as well. You will address this issue when you learn about content descriptions in Chapter 18. In the meantime, your app will function fine, 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 10.30).

Figure 10.30  Now with three views per row

Now with three views per row

Making list items dynamic

Now that the layout includes the right 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. Select your ImageView and, in the attributes pane, update the ID attribute to crime_solved (Figure 10.31). You will be asked whether Android Studio should update all usages of the ID; select Yes.

Figure 10.31  Updating the image ID

Updating the image ID

You may notice that you are using the same view IDs in different layouts. The crime_solved ID is used in both the list_item_crime.xml and fragment_crime.xml layouts. You may think reusing IDs would be an issue, but in this case it is not a problem. 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.

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

Listing 10.1  Updating handcuff visibility (CrimeListFragment.kt)

private inner class CrimeHolder(view: View)
    : RecyclerView.ViewHolder(view), View.OnClickListener {
    ...
    private val dateTextView: TextView
    private val solvedImageView: ImageView = itemView.findViewById(R.id.crime_solved)

    init {
        ...
    }

    fun bind(crime: Crime) {
        this.crime = crime
        titleTextView.text = this.crime.title
        dateTextView.text = this.crime.date.toString()
        solvedImageView.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
3.238.62.124