何らかの理由で、 をプログラムで変更して (チェーンに属する) ビューの位置を変更すると、期待どおりの結果が得られませんConstraintLayout
。ConstraintSet
次の例では、ボタンの最初または最後に画像を配置できるアイコン ビュー付きのボタンを作成しました。アイコンが最後に配置されている場合は、すべて問題ありません。しかし、ボタンの先頭に配置するように設定すると、そのコンテンツは理由もなく左に配置されます。
その問題を解決する方法がわかりません。コードでいくつかの変更を既に試みましたが、どれも機能しませんでした。
どうすれば解決できますか?
アイコンがボタンの先頭に配置されるように設定されている場合のバグのある動作。どういうわけか、ボタンの左側に配置されます
ButtonWithIconView.kt
package com.example.buttonwithimageexample
import android.content.Context
import android.content.res.Resources
import android.graphics.Color
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.res.getIntOrThrow
class ButtonWithIconView : ConstraintLayout {
private val iconView by lazy { findViewById<ImageView>(R.id.icon) }
private val textView by lazy { findViewById<TextView>(R.id.text) }
/**
* Acceptable values: Gravity.START and Gravity.END
*/
private var iconGravity = Gravity.START
constructor(context: Context?) : super(context) {
commonInit(context, null)
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
commonInit(context, attrs)
}
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int
) : super(context, attrs, defStyleAttr) {
commonInit(context, attrs)
}
private fun commonInit(context: Context?, attrs: AttributeSet?) {
if (context == null) {
return
}
this.setBackgroundColor(Color.LTGRAY)
this.setPadding(
BUTTON_PADDING,
BUTTON_PADDING,
BUTTON_PADDING,
BUTTON_PADDING
)
View.inflate(context, R.layout.button_with_icon_view, this)
if (attrs != null) {
applyAttrs(attrs)
}
if (isInEditMode) {
return
}
}
private fun applyAttrs(attrs: AttributeSet) {
val typedArray = context.obtainStyledAttributes(
attrs,
R.styleable.ButtonWithIconView,
0,
0
)
if (typedArray.hasValue(R.styleable.ButtonWithIconView_button_text)) {
textView.text = typedArray.getText(R.styleable.ButtonWithIconView_button_text)
}
if (typedArray.hasValue(R.styleable.ButtonWithIconView_button_icon_position)) {
when (typedArray.getIntOrThrow(R.styleable.ButtonWithIconView_button_icon_position)) {
ATTR_BUTTON_ICON_POS_START -> setIconPosition(Gravity.START)
ATTR_BUTTON_ICON_POS_END -> setIconPosition(Gravity.END)
}
}
typedArray.recycle()
}
private fun getACopyOfTheCurrentConstraintSet(): ConstraintSet {
return ConstraintSet().apply {
this.clone(this@ButtonWithIconView)
}
}
private fun onBeforeMovingIcon(constrainSet: ConstraintSet) {
constrainSet.removeFromHorizontalChain(textView.id)
constrainSet.removeFromHorizontalChain(iconView.id)
constrainSet.clear(iconView.id, ConstraintSet.LEFT)
constrainSet.clear(iconView.id, ConstraintSet.TOP)
constrainSet.clear(iconView.id, ConstraintSet.RIGHT)
constrainSet.clear(iconView.id, ConstraintSet.BOTTOM)
constrainSet.clear(iconView.id, ConstraintSet.START)
constrainSet.clear(iconView.id, ConstraintSet.END)
when (iconGravity) {
Gravity.START -> {
constrainSet.clear(
textView.id,
ConstraintSet.START
)
constrainSet.connect(
textView.id,
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START,
0
)
}
Gravity.END -> {
constrainSet.clear(
textView.id,
ConstraintSet.END
)
constrainSet.connect(
textView.id,
ConstraintSet.END,
ConstraintSet.PARENT_ID,
ConstraintSet.END,
0
)
}
}
}
private fun moveIconToLeftOfTheText() {
val newConstraintSet = getACopyOfTheCurrentConstraintSet()
onBeforeMovingIcon(newConstraintSet)
newConstraintSet.clear(
textView.id,
ConstraintSet.START
)
newConstraintSet.connect(
iconView.id,
ConstraintSet.END,
textView.id,
ConstraintSet.START,
HALF_DISTANCE_BETWEEN_ICON_AND_TEXT
)
/**
* When this line is set, the resulting layout becomes bugged. Instead of the chain
* being centralized in the parent, it is to the start of it =,/.
* Without that function call, everything works as expected, but it shouldn't, because
* it as a chain (<left to right of> and <right to left of> are required).
*/
newConstraintSet.connect(
textView.id,
ConstraintSet.START,
iconView.id,
ConstraintSet.END,
HALF_DISTANCE_BETWEEN_ICON_AND_TEXT
)
newConstraintSet.connect(
iconView.id,
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START,
0
)
newConstraintSet.connect(
iconView.id,
ConstraintSet.TOP,
ConstraintSet.PARENT_ID,
ConstraintSet.TOP,
0
)
newConstraintSet.connect(
iconView.id,
ConstraintSet.BOTTOM,
ConstraintSet.PARENT_ID,
ConstraintSet.BOTTOM,
0
)
newConstraintSet.createHorizontalChain(
ConstraintSet.PARENT_ID,
ConstraintSet.LEFT,
ConstraintSet.PARENT_ID,
ConstraintSet.RIGHT,
intArrayOf(
iconView.id,
textView.id
),
null,
ConstraintSet.CHAIN_PACKED
)
newConstraintSet.applyTo(this)
iconGravity = Gravity.START
}
private fun moveIconToTheRightOfTheText() {
val newConstraintSet = getACopyOfTheCurrentConstraintSet()
onBeforeMovingIcon(newConstraintSet)
newConstraintSet.clear(
textView.id,
ConstraintSet.END
)
newConstraintSet.connect(
iconView.id,
ConstraintSet.START,
textView.id,
ConstraintSet.END,
HALF_DISTANCE_BETWEEN_ICON_AND_TEXT
)
newConstraintSet.connect(
textView.id,
ConstraintSet.END,
iconView.id,
ConstraintSet.START,
HALF_DISTANCE_BETWEEN_ICON_AND_TEXT
)
newConstraintSet.connect(
iconView.id,
ConstraintSet.TOP,
ConstraintSet.PARENT_ID,
ConstraintSet.TOP,
0
)
newConstraintSet.connect(
iconView.id,
ConstraintSet.END,
ConstraintSet.PARENT_ID,
ConstraintSet.END,
0
)
newConstraintSet.connect(
iconView.id,
ConstraintSet.BOTTOM,
ConstraintSet.PARENT_ID,
ConstraintSet.BOTTOM,
0
)
newConstraintSet.createHorizontalChain(
ConstraintSet.PARENT_ID,
ConstraintSet.LEFT,
ConstraintSet.PARENT_ID,
ConstraintSet.RIGHT,
intArrayOf(
textView.id,
iconView.id
),
null,
ConstraintSet.CHAIN_PACKED
)
newConstraintSet.applyTo(this)
iconGravity = Gravity.END
}
/**
* @param gravity may be Gravity.START or Gravity.END (from the text)
*/
fun setIconPosition(gravity: Int) {
when (gravity) {
Gravity.START -> moveIconToLeftOfTheText()
Gravity.END -> moveIconToTheRightOfTheText()
else -> throw IllegalArgumentException("Invalid gravity: $gravity")
}
}
companion object {
private val BUTTON_PADDING = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
16f,
Resources.getSystem().displayMetrics
).toInt()
private val HALF_DISTANCE_BETWEEN_ICON_AND_TEXT = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
4f,
Resources.getSystem().displayMetrics
).toInt()
private const val ATTR_BUTTON_ICON_POS_START = 0
private const val ATTR_BUTTON_ICON_POS_END = 1
}
}
button_with_icon_view.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
tools:background="#CCCCCC"
tools:layout_height="wrap_content"
tools:layout_width="wrap_content"
tools:padding="8dp"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<ImageView
android:id="@+id/icon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginRight="4dp"
android:background="#FF0000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/text"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:includeFontPadding="false"
android:text="Clicker"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/icon"
app:layout_constraintTop_toTopOf="parent" />
</merge>
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ButtonWithIconView">
<attr name="button_text" />
<attr name="button_icon_position" format="enum">
<enum name="start" value="0" />
<enum name="end" value="1" />
</attr>
</declare-styleable>
</resources>
activity_main.xml
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.buttonwithimageexample.ButtonWithIconView
android:id="@+id/left_button"
android:layout_width="170dp"
android:layout_height="wrap_content"
app:button_icon_position="start"
app:button_text="Left Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/right_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.buttonwithimageexample.ButtonWithIconView
android:id="@+id/right_button"
android:layout_width="170dp"
android:layout_height="wrap_content"
app:button_icon_position="end"
app:button_text="Right Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/left_button"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>