I have a widget that, no matter what constraints I place upon it and other widgets, including the addition of barriers, always positions itself at the top of the layout.
This is a fairly simple arrangement of two rows of two elements each, not aligned column wise. The first element in each row is a TextView label, the second an input (Spinner). There is also a lone TextView title above the first row stretching all the way across. By my understanding and previous experience with constraint layout, this shouldn't require a barrier between the rows, and that was my initial version.
This is the design view, where the selected element ("Credentials") is supposed to be in the second row but instead appears above the first row, over top of the title TextView ("PKIX"):
Actual result in the emulator looks much the same. The selected "Credentials" element is the fourth of five elements in the XML layout below. All of the other elements are in the right place.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@ id/addsrv_pkix_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/bottomborder"
android:text="PKIX"
android:textAlignment="center"
android:layout_marginHorizontal="10sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@ id/addsrv_trust_lbl"
/>
<TextView
android:id="@ id/addsrv_trust_lbl"
android:text="Trust"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="10sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@ id/addsrv_trust_spin"
app:layout_constraintTop_toBottomOf="@ id/addsrv_pkix_title"
app:layout_constraintBaseline_toBaselineOf="@ id/addsrv_trust_spin"
app:layout_constraintBottom_toTopOf="@ id/addsrv_cred_lbl"
/>
<Spinner
android:id="@ id/addsrv_trust_spin"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@ id/addsrv_trust_lbl"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@ id/addsrv_pkix_title"
app:layout_constraintBottom_toTopOf="@ id/addsrv_cred_spin"
/>
<TextView
android:id="@ id/addsrv_cred_lbl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:paddingHorizontal="10sp"
android:text="Credentials"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@ id/addsrv_cred_spin"
app:layout_constraintTop_toBottomOf="@ id/addsrv_trust_lbl"
app:layout_constraintBaseline_toBaselineOf="@ id/addsrv_cred_spin"
/>
<Spinner
android:id="@ id/addsrv_cred_spin"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@ id/addsrv_cred_lbl"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@ id/addsrv_trust_spin"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
I'm perplexed. The addsrv_cred_lbl
TextView ("Credentials") is:
- Start aligned with parent.
- End aligned with the
addsrv_cred_spin
spinner, which appears correctly positioned; this alignment is reciprocated to create a horizontal chain. They are also baseline aligned. - Top aligned with the bottom of the TextView above it,
addsrv_trust_lbl
. This alignment is also reciprocated.
There's no bottom alignment yet (there's another row to go); bottom aligning it with the parent makes no difference unless I bottom align the spinner from the same row, in which case the result goes from bad to worse.
Since this did not work, I tried to use a barrier between the rows. If I use it as a "top", with the second row widgets as the constraint referents, the barrier appears at the top, above the title, regardless of what constraints are used to position it below the first row. Used as a "bottom", with the first row widgets referenced and the second row chained below it (which is more logical), things are a little bit better in that the barrier appears in the right place -- but the "Credentials" widget is still up top.
The design view of this looks exactly the same as the previous one except the barrier is visible below the first row. In the XML, I aslo added optimizationLevel="none"
after having read this can help with misbehaving barriers (but it made no difference). There's also a few stylistic elements added back here (such as font size) I removed for brevity before.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_optimizationLevel="none"
>
<TextView
android:id="@ id/addsrv_pkix_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/bottomborder"
android:backgroundTint="@color/tbar"
android:text="PKIX"
android:textAlignment="center"
android:textSize="@dimen/addsrv_bigfont"
android:textColor="@color/titleText"
android:layout_marginHorizontal="10sp"
app:layout_constraintBottom_toTopOf="@ id/addsrv_trust_lbl"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="@ id/addsrv_trust_lbl"
android:text="Trust"
android:textSize="@dimen/addsrv_fontsz"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="10sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@ id/addsrv_trust_spin"
app:layout_constraintTop_toBottomOf="@ id/addsrv_pkix_title"
app:layout_constraintBaseline_toBaselineOf="@ id/addsrv_trust_spin"
app:layout_constraintBottom_toTopOf="@ id/addsrv_bar1"
app:layout_constraintHorizontal_chainStyle="packed"
/>
<Spinner
android:id="@ id/addsrv_trust_spin"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@ id/addsrv_trust_lbl"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@ id/addsrv_pkix_title"
app:layout_constraintBottom_toTopOf="@ id/addsrv_bar1"
/>
<androidx.constraintlayout.widget.Barrier
android:id="@ id/addsrv_bar1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="addsrv_trust_lbl,addsrv_trust_spin"
app:layout_constraintBottom_toTopOf="@ id/addsrv_cred_lbl"
/>
<TextView
android:id="@ id/addsrv_cred_lbl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="10sp"
android:text="Credentials"
android:textSize="@dimen/addsrv_fontsz"
app:layout_constraintBaseline_toBaselineOf="@ id/addsrv_cred_spin"
app:layout_constraintEnd_toStartOf="@ id/addsrv_cred_spin"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@ id/addsrv_bar1" />
<Spinner
android:id="@ id/addsrv_cred_spin"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@ id/addsrv_cred_lbl"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@ id/addsrv_bar1"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Am I correct in observing that some of the constraints on addsrv_cred_lbl
are being completely ignored? Doesn't topToBottom
mean that the top of the widget is aligned with the bottom of the other? Instead, it seems simply to mean that they will be connected with a squiggly, potentially curved and convoluted line in the design view, and the spacial relation of the two widgets is arbitrary, such that the semantic logic might as well be inverted, "top = bottom, bottom = top", etc.
Please note that I do not want to use absolute values to position anything. If the only way to get this to work is to do that, constraint layout seems a complete waste of time even in this simple case, and I'd rather just stack some liner layouts.
CodePudding user response:
The problem is that Spinner
doesn't have a baseline. You were trying to tie it to the baseline of the item inside the Spinner
is my guess - ConstraintLayout
can't reach children of its children.
You can check it via the design tab with a right click. TextView
s will show an option "Show baseline" but the Spinner
doesn't.
Also snippet from the doc
Align the text baseline of a view to the text baseline of another view.
That's what baselines are for, if you want two TextView
s connect together so they don't have the height where the text starts messed up.
I think there is a tiny flaw in your approach to the chain. You were setting the top constraint of both elements in each row to the bottom of the previous row(or title in the first row). Even if the Spinner
would have a baseline, this would make the label off-centered in relation to the Spinner
(slightly higher because the baseline of a text is higher than the actual bottom of the view).
I think the best approach in these types of layouts is to have one guiding element(which is also the element of the chain) that represents the row and let other elements be positioned in relation to it.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@ id/addsrv_pkix_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="PKIX"
android:textAlignment="center"
android:layout_marginHorizontal="10sp"
app:layout_constraintVertical_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@ id/addsrv_trust_spin"
/>
<TextView
android:id="@ id/addsrv_trust_lbl"
android:text="Trust"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="10sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@ id/addsrv_trust_spin"
app:layout_constraintTop_toTopOf="@ id/addsrv_trust_spin"
app:layout_constraintBottom_toBottomOf="@ id/addsrv_trust_spin"
/>
<Spinner
android:id="@ id/addsrv_trust_spin"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@ id/addsrv_trust_lbl"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@ id/addsrv_pkix_title"
app:layout_constraintBottom_toTopOf="@ id/addsrv_cred_spin"
/>
<TextView
android:id="@ id/addsrv_cred_lbl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="10sp"
android:text="Credentials"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@ id/addsrv_cred_spin"
app:layout_constraintTop_toTopOf="@ id/addsrv_cred_spin"
app:layout_constraintBottom_toBottomOf="@ id/addsrv_cred_spin"
/>
<Spinner
android:id="@ id/addsrv_cred_spin"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@ id/addsrv_cred_lbl"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@ id/addsrv_trust_spin"
app:layout_constraintBottom_toBottomOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
NOTE: I added the last bottom constraint to the parent and "spread
" to the chain for demonstration but also know that if you don't have the bottom of the last view constrained to something - it's not a chain.
CodePudding user response:
Working with ConstraintLayout
is easy if you obey few simple rules:
- Create one chain. That means Views hooking to each other reciprocally, all the way.
- Now that you have a working chain, you can manipulate it with
layout_constraintVertical_bias
andlayout_constraintVertical_chainStyle
. If changing those does nothing, it means your chain is broken. - hook remaining Views to ones laid out by the chain
- every control must have 4 constraints: top, bottom, start and end
- do not create competing chains and try to force them into working together.
Spinner
cannot have baseline, only TextView
and its descendants have it. Spinner
is AdapterView
, so it can contain whatever you can imagine.
BTW: don't use @ id to refer to existing ids. Plus means creating new id, so if you make a typo it will create new id that refers to nothing instead of error "there's no such id".
Here's an example: top label and Spinners form the main chain and side labels are positioned each to their Spinner:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@ id/addsrv_pkix_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10sp"
android:background=""
android:text="PKIX"
android:textAlignment="center"
app:layout_constraintBottom_toTopOf="@id/addsrv_trust_spin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed"
/>
<TextView
android:id="@ id/addsrv_trust_lbl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="10sp"
android:text="Trust"
app:layout_constraintBottom_toBottomOf="@id/addsrv_trust_spin"
app:layout_constraintEnd_toStartOf="@id/addsrv_trust_spin"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/addsrv_trust_spin"
app:layout_constraintVertical_bias="1.0"
/>
<Spinner
android:id="@ id/addsrv_trust_spin"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/addsrv_cred_spin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/addsrv_trust_lbl"
app:layout_constraintTop_toBottomOf="@id/addsrv_pkix_title"
app:layout_constraintVertical_bias="0.0"
/>
<TextView
android:id="@ id/addsrv_cred_lbl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="10sp"
android:text="Credentials"
app:layout_constraintBottom_toBottomOf="@id/addsrv_cred_spin"
app:layout_constraintEnd_toStartOf="@id/addsrv_cred_spin"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/addsrv_cred_spin"
app:layout_constraintVertical_bias="1.0"
/>
<Spinner
android:id="@ id/addsrv_cred_spin"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/addsrv_cred_lbl"
app:layout_constraintTop_toBottomOf="@id/addsrv_trust_spin"
/>
</androidx.constraintlayout.widget.ConstraintLayout>