问题
I'm currently developing a small Android application and use the new ConstraintLayout.
I have an ImageView which contains a vector graphic image which is supposed to take the maximum space available with respect to its aspect ratio. I got this to work with the following code:
<ImageView
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="@dimen/margin"
android:src="@drawable/image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
Now, I want to place multiple custom views (buttons) at exact positions. Using Constraint Guidelines, I came up with the following:
<android.support.constraint.Guideline
android:id="@+id/guideline_x"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.20"
tools:layout_editor_absoluteX="..."
tools:layout_editor_absoluteY="..."
/>
<android.support.constraint.Guideline
android:id="@+id/guideline_y"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.20"
tools:layout_editor_absoluteX="..."
tools:layout_editor_absoluteY="..."
/>
<com.example.android.myCustomView
android:id="@+id/myCustomView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doSomething"
app:layout_constraintLeft_toLeftOf="@+id/guideline_x"
app:layout_constraintTop_toTopOf="@+id/guideline_y"
/>
This works great for the specific device that I've been testing with initially. But: As soon as the device dimensions vary, the custom views are placed at wrong positions.
I am looking for a way to place a custom view n% relative to the x coordinate of the image view. I've tried adding app:layout_constraintLeft_toLeftOf="@+id/imageView"
to the guidelines but this changes nothing. Do you have any ideas how I can resolve this issue? Thanks a lot!
Edit: Here are 2 images which illustrate the issue.
Samsung Galaxy S8:
Google Pixel XL:
The little red square should always be at the exact same position relative to the Android icon.
回答1:
Here is a easier way to place a widget precisely on an ImageView
or any other view that will retain it placement regardless of any change in size of the ImageView
as long as the aspect ratio remains constant. In the first proposed solution of my other answer, I suggested using bias to place the widget within the ImageView
. Unfortunately, a change in the relative size of the widget to to the size of the ImageView
causes the widget to shift on the image which is not desireable.
To get around this, I suggest placing a 1x1 pixel Space
view on the ImageView
using bias. Because the Space view is just 1x1 pixel in size, it can be placed anywhere within the ImageView
by using bias. It's relative position will remain constant as the ImageView
changes size due to rotation on a single device or because the app is being hosted on devices with different screen sizes. It is just important that the aspect ratio of the image remain constant regardless of the size of the screen.
Once the 1x1 pixel view is placed, the widget can be attached through constraints. In the example below, I have chosen to center the widgets for the eyes on the placement pixels by constraining all sides of each eye widget to the corresponding sides of its placement pixel.
Here are some images showing the effect. Screen with varying sizes that I have tested show the same result.
Nexus 6 Portrait
Nexus 6 Landscape
Because the ImageView
s for the eyes are not scaled, they appear smaller on the Nexus 10 screen. The eyes are placed in the same location as on the Nexus 6. Scaling the eyes is a different matter.
Nexus 10 Landscape
Here is the layout showing the constraints:
position_view.xml
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/androidGreen"
android:layout_width="0dp"
android:layout_height="0dp"
android:src="@drawable/android_image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<Space
android:id="@+id/pinpoint1"
android:layout_width="1px"
android:layout_height="1px"
app:layout_constraintBottom_toBottomOf="@id/androidGreen"
app:layout_constraintEnd_toEndOf="@id/androidGreen"
app:layout_constraintHorizontal_bias="0.373"
app:layout_constraintStart_toStartOf="@id/androidGreen"
app:layout_constraintTop_toTopOf="@id/androidGreen"
app:layout_constraintVertical_bias="0.19" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/circle"
app:layout_constraintBottom_toBottomOf="@id/pinpoint1"
app:layout_constraintEnd_toEndOf="@id/pinpoint1"
app:layout_constraintStart_toStartOf="@id/pinpoint1"
app:layout_constraintTop_toTopOf="@id/pinpoint1"
app:srcCompat="@drawable/circle"
tools:ignore="ContentDescription" />
<Space
android:id="@+id/pinpoint2"
android:layout_width="1px"
android:layout_height="1px"
android:background="@android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="@id/androidGreen"
app:layout_constraintEnd_toEndOf="@id/androidGreen"
app:layout_constraintHorizontal_bias="0.625"
app:layout_constraintStart_toStartOf="@id/androidGreen"
app:layout_constraintTop_toTopOf="@id/androidGreen"
app:layout_constraintVertical_bias="0.19" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/circle"
app:layout_constraintBottom_toBottomOf="@id/pinpoint2"
app:layout_constraintEnd_toEndOf="@id/pinpoint2"
app:layout_constraintStart_toStartOf="@id/pinpoint2"
app:layout_constraintTop_toTopOf="@id/pinpoint2"
tools:ignore="ContentDescription" />
</android.support.constraint.ConstraintLayout>
You can also achieve the same goal by wrapping the ImageView
in a ConstraintLayout
and setting the width and height of the ImageView
to match_parent
. You can then use percent guidelines to position a widget on the ImageView
. This method introduces another level in the layout hierarchy but may be easier to understand.
Here is XML that will demonstrate this method.
XML using guidelines within nested ConstraintLayout
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/androidGreen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/android_image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<android.support.constraint.Guideline
android:id="@+id/guidelineHorizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.19" />
<android.support.constraint.Guideline
android:id="@+id/guidelineVerticalLeft"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.375" />
<android.support.constraint.Guideline
android:id="@+id/guidelineVerticalRight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.625" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/circle"
app:layout_constraintBottom_toBottomOf="@id/guidelineHorizontal"
app:layout_constraintEnd_toEndOf="@id/guidelineVerticalLeft"
app:layout_constraintStart_toStartOf="@id/guidelineVerticalLeft"
app:layout_constraintTop_toTopOf="@id/guidelineHorizontal"
app:srcCompat="@drawable/circle"
tools:ignore="ContentDescription" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/circle"
app:layout_constraintBottom_toBottomOf="@id/guidelineHorizontal"
app:layout_constraintEnd_toEndOf="@id/guidelineVerticalRight"
app:layout_constraintStart_toStartOf="@id/guidelineVerticalRight"
app:layout_constraintTop_toTopOf="@id/guidelineHorizontal"
tools:ignore="ContentDescription" />
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
回答2:
If you want to position a view within another view, take a look at Centering positioning and bias in ConstraintLayout
.
Bias
The default when encountering such opposite constraints is to center the widget; but you can tweak the positioning to favor one side over another using the bias attributes:
Here is a TextView
positioned 20% from the left side of the image and 70% from the top. The positioning will remain constant with different screen sizes, but you will have to make sure the aspect ratio stays constant; otherwise, the TextView
will drift.
Here is the XML for the image above:
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:src="@drawable/ic_android_green_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:text="TextView here"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="@id/imageView"
app:layout_constraintEnd_toEndOf="@id/imageView"
app:layout_constraintHorizontal_bias="0.2"
app:layout_constraintStart_toStartOf="@id/imageView"
app:layout_constraintTop_toTopOf="@id/imageView"
app:layout_constraintVertical_bias="0.7" />
</android.support.constraint.ConstraintLayout>
Another way to handle positioning within a view that provides more precise targeting is to define a horizontal and vertical chain of Space
views that use weights to divide up the container view. Here is the same layout as above but with a vertical boundary between two Space
views at 20% of the width of the ImageView
. An additional two Space
views have a horizontal boundary at 70% of the container view. See Chains in the documentation for ConstraintLayout
.
Here is the XML:
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:src="@drawable/ic_android_green_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Space
android:id="@+id/space1"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@android:color/holo_blue_light"
app:layout_constraintBottom_toBottomOf="@id/imageView"
app:layout_constraintEnd_toStartOf="@id/space2"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintHorizontal_weight="20"
app:layout_constraintStart_toStartOf="@id/imageView"
app:layout_constraintTop_toTopOf="@id/imageView" />
<Space
android:id="@+id/space2"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="@id/imageView"
app:layout_constraintEnd_toEndOf="@id/imageView"
app:layout_constraintHorizontal_weight="80"
app:layout_constraintStart_toEndOf="@id/space1"
app:layout_constraintTop_toTopOf="@id/imageView" />
<Space
android:id="@+id/space3"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@android:color/holo_blue_light"
app:layout_constraintBottom_toTopOf="@id/space4"
app:layout_constraintEnd_toEndOf="@id/imageView"
app:layout_constraintStart_toStartOf="@id/imageView"
app:layout_constraintTop_toTopOf="@id/imageView"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintVertical_weight="70" />
<Space
android:id="@+id/space4"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="@id/imageView"
app:layout_constraintEnd_toEndOf="@id/imageView"
app:layout_constraintStart_toStartOf="@id/imageView"
app:layout_constraintTop_toBottomOf="@id/space3"
app:layout_constraintVertical_weight="30" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView here"
android:textSize="24sp"
app:layout_constraintStart_toEndOf="@id/space1"
app:layout_constraintTop_toBottomOf="@id/space3" />
</android.support.constraint.ConstraintLayout>
来源:https://stackoverflow.com/questions/46876928/constraintlayout-relative-to-imageview-dimensions