我想在运行时使用 ConstraintSet() 移动按钮。我知道这个问题之前在stackoverflow上被问过,但是尝试了整整两天在帖子中给出的所有代码我仍然无法通过以下方式使其工作:
1:什么都没有发生
2:我想移动的视图消失了,再也没有出现过
3:我收到错误消息:孩子已经有了父母,请先在父母上调用 removeView()
我确定我错过了一些东西,所以请帮忙!
为了演示这个问题,我简化了应用程序。一些解释:我想要一个显示一些数据集的活动(暂时不感兴趣)。要添加数据集,FAB 会启动对话片段。以下是 xml 布局main
:
<?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.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/open_dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@android:drawable/btn_star" />
</androidx.constraintlayout.widget.ConstraintLayout>
和全屏对话框的主题:
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.ConstraintSetTutorial" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
<style name ="FullScreenDialog" parent ="Theme.ConstraintSetTutorial">
<item name="windowNoTitle">true</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
<!-- No backgrounds, titles or window float -->
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:backgroundDimAmount">0.7</item>
<item name="android:windowIsFloating">false</item>
</style>
</resources>
这是代码activity_main
:
package com.example.constraintsettutorial
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.floatingactionbutton.FloatingActionButton
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fabButton: FloatingActionButton =findViewById(R.id.open_dialog)
fabButton.setOnClickListener {
val addDialog =AddDialogFragment()
addDialog.show(supportFragmentManager, "New Add Dialog")
}
}
}
对话框片段旨在获取新数据集的所有用户输入。在最终的应用程序中,我将为数据集设置几个类别,其中对话框会略有不同。因此我决定使用 ViewStub。布局dialog_frame
将包含不会通过各个对话框更改的所有部分。
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewStub
android:id ="@+id/add_content_stub"
android:inflatedId="@+id/add_content_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
ViewStubdialog_content
本身对于各个对话框会有所不同,并包含我想在运行时移动的视图。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:fitsSystemWindows="true">
<ScrollView
android:id="@+id/add_content_sv"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/add_content_sv_cl"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/add_content_card"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="8dp"
app:cardPreventCornerOverlap="true"
app:contentPadding="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id ="@+id/add_content_card_cl"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Just some text"
android:textSize="24sp"
android:gravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<Button
android:id="@+id/add_more_views_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="More Fields"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/textView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<Button
android:id="@+id/add_save_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Save"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/add_more_views_btn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/textView2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="800dp"
android:text="Just more text"
android:textSize="24sp"
android:gravity="center"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
该对话框将有一些基本信息的输入字段,由上面的 TextView (id = textview) 展示,但也有一些为专业人士提供的额外输入,由最初隐藏的第二个 TextView (id = textview2) 展示。这些额外的输入应该通过按下“更多字段”按钮来显示。由于会有很多“专业”输入,因此 ViewStub 的布局包含一个滚动视图。cardView 只是为了变得更好......但是,随着布局的扩展和其他输入的显示,我希望“保存”按钮向下移动到最后一个输入下方,并且“更多字段”按钮消失。
对话框AddDialogFragment()
本身的代码是:
package com.example.constraintsettutorial
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewStub
import android.widget.Button
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.fragment.app.DialogFragment
class AddDialogFragment: DialogFragment() {
private lateinit var dialogFrameView: View
private lateinit var viewStubContent: ViewStub
private lateinit var viewStubContentLayout: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(
STYLE_NO_FRAME, R.style.FullScreenDialog
)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val layoutStubResource: Int =R.layout.dialog_content
dialogFrameView =inflater.inflate(R.layout.dialog_frame, container, false)
viewStubContent =dialogFrameView.findViewById(R.id.add_content_stub)
viewStubContent.layoutResource =layoutStubResource
viewStubContentLayout =viewStubContent.inflate()
return dialogFrameView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val addFieldsButton: Button =viewStubContentLayout.findViewById(R.id.add_more_views_btn)
addFieldsButton.setOnClickListener {
// Function for visibility
// first switch on all views where new views may / will be constrained to
setupVisibility()
// Function for ConstraintSet()
// second, create new constrains
setupNewConstrains(
viewStubContentLayout.findViewById(R.id.add_save_btn),
viewStubContentLayout.findViewById(R.id.textview2)
)
}
}
private fun setupVisibility(){
val textViewToShow: TextView =viewStubContentLayout.findViewById(R.id.textview2)
val addMoreViewsButton: Button = viewStubContentLayout.findViewById(R.id.add_more_views_btn)
textViewToShow.visibility =View.VISIBLE
addMoreViewsButton.visibility =View.GONE
}
private fun setupNewConstrains(viewToMove: View, viewToConstrainTo: View){
val newConstraintSet =ConstraintSet()
// next line correct???
val layout:ConstraintLayout =viewStubContentLayout.findViewById(R.id.add_content_card_cl) as ConstraintLayout
// using the next line gives error when adding a View
//val layout =ConstraintLayout(requireContext())
// Error Message: Child already has parent, call removeView() on parent first
// hence, get the parent but parent doesn't know the method removeView()
val viewToMoveParent = viewToMove.parent
// clone the dialog content layout, get constrains of it
newConstraintSet.clone(requireContext(),R.layout.dialog_content)
// clear all constrains of view intended to move
newConstraintSet.clear(viewToMove.id, ConstraintSet.TOP)
newConstraintSet.clear(viewToMove.id, ConstraintSet.BOTTOM)
newConstraintSet.clear(viewToMove.id, ConstraintSet.START)
newConstraintSet.clear(viewToMove.id, ConstraintSet.END)
// set new Constrains
newConstraintSet.connect(
viewToMove.id, ConstraintSet.START,
viewToConstrainTo.id, ConstraintSet.START
)
newConstraintSet.connect(
viewToMove.id, ConstraintSet.END,
viewToConstrainTo.id, ConstraintSet.END
)
newConstraintSet.connect(
viewToMove.id, ConstraintSet.TOP,
viewToConstrainTo.id, ConstraintSet.BOTTOM
)
// remove view from layout by calling from layout val
// does not work
//layout.removeView(viewToMove)
// try to remove view from layout by calling from parent: method not resolved
//viewToMoveParent.removeView(viewToMove)
// generating new ID, in combination with val layout:ConstraintLayout...:
// app runs but Save Button does not move
// generating new ID, in combination with val layout =ConstraintLayout(context) :
// error, removeView() from parent should be called
//viewToMove.id = View.generateViewId()
// add View to layout
//layout.addView(viewToMove)
// apply to layout
newConstraintSet.applyTo(layout)
}
}
在函数的代码中,private fun setupNewConstrains(viewToMove: View, viewToConstrainTo:View)
我尝试了创建布局的方法:首先只是简单地继承ConstraintLayout(context)
和其次,通过val layout: ConstraintLayout
引用 xml 文件中的约束布局标记进行初始化,该文件承载我要移动的按钮。稍后我ConstraintSet()
通过引用完整的布局文件来克隆布局dialog_content.xml
。也许问题与这些线路有关?
如果有人可以对我上面的问题发表评论或让我进入正确的方向以使应用程序运行,我将非常感激。