分かってしまえば確かにその通りなのですが、地味にハマってしまったのでメモ。
ConstraintLayout下でGroup指定したViewのvisibilityは個別には変更できなくなるので気をつけてくださいという話です。
具体例
某SNSでよく見かけるようなレイアウトを例に話を進めます。
このレイアウトに関して、以下の要件があるとします。
- 条件Aのtrue/falseに応じてレイアウト全体の表示・非表示を切り替える
- 条件Bのtrue/falseに応じてバッジ部分だけの表示・非表示を切り替える
レイアウトファイルは以下のように記述しました。簡略化のため、今回の話とは無関係な制約の記述は省いています。
レイアウト全体の表示・非表示を切り替えるという要件があったため、ConstraintLayout
内の全コンポーネントのIDをGroup
のconstraint_referenced_ids
に指定しました。
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout> <ImageView android:id="@+id/icon" /> <TextView android:id="@+id/name" /> <ImageView android:id="@+id/badge" /> <TextView android:id="@+id/description" /> <androidx.constraintlayout.widget.Group android:id="@+id/group" app:constraint_referenced_ids="icon,name,badge,description" /> </androidx.constraintlayout.widget.ConstraintLayout>
実装は以下のようにしました。
class SampleActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // conditionA、Bはそれぞれ独立であるとする。 group.visibility = if (conditionA) View.VISIBLE else View.INVISIBLE badge.visibility = if (conditionB) View.VISIBLE else View.INVISIBLE } }
このとき、条件Aがtrueかつ条件Bがfalseのときにバッジ部分が非表示になってほしかったのですが、Group
の制約によってバッジ部分も表示されてしまったという話です。
解決方法は、Group
のconstraint_referenced_ids
からbadge
を外すだけです。
ただ、この例で言えば、条件Aがfalseかつ条件Bがtrueのときにバッジ部分だけ表示されてしまうので、実装側の条件も修正する必要があります。
Group
の内部実装
ついでなので、Group
の内部実装を少し見てみました。
Group
そのものについては公式ドキュメントなどをご参照ください。
Group
はConstraintHelper
というクラスを継承し、ConstraintHelper
はView
クラスを継承しており、実体としてはViewでした。
このクラスのupdatePreLayout()
というメソッドの中でconstraint_referenced_ids
で指定した各IDに対応するViewのvisibilityをGroup
のvisibilityと一致させていました。
public class Group extends ConstraintHelper { ... public void updatePreLayout(ConstraintLayout container) { // Groupに指定されているvisibilityを取得。 int visibility = this.getVisibility(); float elevation = 0.0F; if (VERSION.SDK_INT >= 21) { elevation = this.getElevation(); } for (int i = 0; i < this.mCount; ++i) { // mIdsはconstraint_referenced_idsに指定された各ID。 int id = this.mIds[i]; View view = container.getViewById(id); if (view != null) { // Groupに指定されているvisibilityを指定。 view.setVisibility(visibility); if (elevation > 0.0F && VERSION.SDK_INT >= 21) { view.setElevation(elevation); } } } } }
ここからはざっとしか見ていないですが、updatePreLayout()
はConstraintLayout
のonMeasure()
経由で呼び出されているようだったので、個別のViewのvisibilityを変更して再描画しようとしても、この処理によってvisibilityが上書きされるのだと思われます。
※2020/4/21 追記
この例の内容では
ConstraintLayout
自体のvisibilityを変更すれば十分なため、あまりいい例になっていないことに気づきました🙇♂️実際にこの事象に遭遇したレイアウトファイルでは、
ConstraintLayout
同士のネストを避けるためにView
コンポーネントを利用してレイアウトの表示領域が確保されていました。