ConstraintLayout動(dòng)態(tài)添加View,改變約束

DEMO源碼

使用的ConstraintLayout版本

implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

如果不使用androidx的話(huà)可以使用下面的版本

implementation 'com.android.support.constraint:constraint-layout:1.1.3'

注意:使用不同的ConstraintLayout版本可能會(huì)有坑,如果在使用過(guò)程中發(fā)現(xiàn)實(shí)現(xiàn)不了想要添加的約束,可以嘗試改變ConstraintLayout的版本如上所示。

1. 動(dòng)態(tài)添加View

第一種情況:所有的View都是動(dòng)態(tài)添加

舉個(gè)例子

<?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:id="@+id/clRoot"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/ivLeft"
        android:layout_width="100dp"
        android:layout_height="0dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:scaleType="centerCrop"
        android:src="@drawable/ic_lake"
        app:layout_constraintDimensionRatio="h,16:9"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tvRight"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="@string/lake_tahoe_title"
        android:textSize="30sp"
        app:layout_constraintLeft_toRightOf="@+id/ivLeft"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tvBottom"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="24dp"
        android:layout_marginEnd="8dp"
        android:text="@string/lake_discription"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/ivLeft" />

</androidx.constraintlayout.widget.ConstraintLayout>

初始布局.jpg

上面的布局文件中呈現(xiàn)的效果如圖所示,接下來(lái)我們用代碼的方式動(dòng)態(tài)添加View,實(shí)現(xiàn)上面的效果。

首先在res/values文件夾下新建一個(gè)ids.xml,在ids.xml中聲明我們要添加的View的控件id。

ids.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="clRoot" type="id" />
    <item name="ivLeft" type="id" />
    <item name="tvRight" type="id" />
    <item name="tvBottom" type="id" />
</resources>

然后開(kāi)始寫(xiě)代碼

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    addViewUseLayoutParams()
}

使用ConstraintLayout.LayoutParams

    private fun addViewUseLayoutParams() {
        val constraintLayout = ConstraintLayout(this)
        constraintLayout.id = R.id.clRoot
        constraintLayout.layoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )
        //先設(shè)置根布局
        setContentView(constraintLayout)

        val ivLeft = ImageView(this)
        ivLeft.id = R.id.ivLeft
        ivLeft.scaleType = ImageView.ScaleType.CENTER_CROP
        ivLeft.setImageResource(R.drawable.ic_lake)

        val ivLeftLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
            ScreenUtil.dpToPx(this, 100), 0
        )
        ivLeftLayoutParams.leftToLeft = R.id.clRoot
        ivLeftLayoutParams.marginStart = ScreenUtil.dpToPx(this, 8)
        ivLeftLayoutParams.topToTop = R.id.clRoot
        ivLeftLayoutParams.topMargin = ScreenUtil.dpToPx(this, 8)
        ivLeftLayoutParams.dimensionRatio = "h,16:9"

        ivLeft.layoutParams = ivLeftLayoutParams

        val tvRight = TextView(this)
        tvRight.id = R.id.tvRight
        tvRight.text = getString(R.string.lake_tahoe_title)
        tvRight.textSize = 30F
        val tvRightLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
            ConstraintLayout.LayoutParams.WRAP_CONTENT,
            ConstraintLayout.LayoutParams.WRAP_CONTENT
        )
        tvRightLayoutParams.startToEnd = R.id.ivLeft
        tvRightLayoutParams.topToTop = R.id.clRoot
        tvRightLayoutParams.marginStart = ScreenUtil.dpToPx(this, 16)
        tvRightLayoutParams.topMargin = ScreenUtil.dpToPx(this, 16)

        tvRight.layoutParams = tvRightLayoutParams


        val tvBottom = TextView(this)
        tvBottom.id = R.id.tvBottom
        tvBottom.text = getString(R.string.lake_discription)
        val tvBottomLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
            0,
            ConstraintLayout.LayoutParams.WRAP_CONTENT
        )
        tvBottomLayoutParams.startToStart = R.id.clRoot
        tvBottomLayoutParams.marginStart = ScreenUtil.dpToPx(this, 8)

        tvBottomLayoutParams.endToEnd = R.id.clRoot
        tvBottomLayoutParams.marginEnd = ScreenUtil.dpToPx(this, 8)

        tvBottomLayoutParams.topToBottom = R.id.ivLeft
        tvBottomLayoutParams.topMargin = ScreenUtil.dpToPx(this, 24)

        tvBottom.layoutParams = tvBottomLayoutParams

        constraintLayout.addView(ivLeft)
        constraintLayout.addView(tvRight)
        constraintLayout.addView(tvBottom)

    }

效果和上面是一樣的,就不截圖了。在上面的方法中,我們是使用ConstraintLayout.LayoutParams來(lái)實(shí)現(xiàn)添加view并指定約束的。接下來(lái),我們換一種方式,使用ConstraintSet來(lái)添加view并指定約束。關(guān)于ConstraintSet的介紹請(qǐng)參考 ConstraintSet

使用ConstraintSet

    private fun addViewUseConstraintSet() {
        val constraintLayout = ConstraintLayout(this)
        constraintLayout.id = R.id.clRoot
        constraintLayout.layoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )
        //先設(shè)置根布局
        setContentView(constraintLayout)

        val constraintSet = ConstraintSet()

        val ivLeft = ImageView(this)
        ivLeft.id = R.id.ivLeft
        ivLeft.scaleType = ImageView.ScaleType.CENTER_CROP
        ivLeft.setImageResource(R.drawable.ic_lake)

        constraintSet.constrainWidth(R.id.ivLeft, ScreenUtil.dpToPx(this, 100))
        constraintSet.constrainHeight(R.id.ivLeft, 0)
        constraintSet.setDimensionRatio(R.id.ivLeft, "h,16:9")

        //layout_constraintTop_toTopOf
        constraintSet.connect(
            R.id.ivLeft, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP,
            ScreenUtil.dpToPx(this, 16)
        )

        constraintSet.connect(
            R.id.ivLeft, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START,
            ScreenUtil.dpToPx(this, 16)
        )

        val tvRight = TextView(this)
        tvRight.id = R.id.tvRight
        tvRight.text = getString(R.string.lake_tahoe_title)
        tvRight.textSize = 30F

        constraintSet.constrainHeight(R.id.tvRight, ConstraintLayout.LayoutParams.WRAP_CONTENT)
        constraintSet.constrainWidth(R.id.tvRight, ConstraintLayout.LayoutParams.WRAP_CONTENT)
        
        constraintSet.connect(
            R.id.tvRight, ConstraintSet.START, R.id.ivLeft, ConstraintSet.END,
            ScreenUtil.dpToPx(this, 16)
        )
        constraintSet.connect(
            R.id.tvRight, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP,
            ScreenUtil.dpToPx(this, 16)
        )

        val tvBottom = TextView(this)
        tvBottom.id = R.id.tvBottom
        tvBottom.text = getString(R.string.lake_discription)
        //設(shè)置高度
        constraintSet.constrainHeight(R.id.tvBottom, ConstraintLayout.LayoutParams.WRAP_CONTENT)

        constraintSet.connect(
            R.id.tvBottom, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START,
            ScreenUtil.dpToPx(this, 8)
        )

        constraintSet.connect(
            R.id.tvBottom, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END,
            ScreenUtil.dpToPx(this, 8)
        )

        constraintSet.connect(
            R.id.tvBottom, ConstraintSet.TOP, R.id.ivLeft, ConstraintSet.BOTTOM,
            ScreenUtil.dpToPx(this, 24)
        )

        constraintLayout.addView(ivLeft)
        constraintLayout.addView(tvRight)
        constraintLayout.addView(tvBottom)

        TransitionManager.beginDelayedTransition(constraintLayout)

        constraintSet.applyTo(constraintLayout)
    }

效果也是一樣的。

第二種情況,動(dòng)態(tài)添加個(gè)別View,感覺(jué)這種場(chǎng)景應(yīng)該不多

在上面的例子中,我們假設(shè)tvBottom已經(jīng)在布局中了。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/clRoot"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tvBottom"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="24dp"
        android:layout_marginEnd="8dp"
        android:text="@string/lake_discription"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

31573289245_.pic.jpg

接下來(lái),我們動(dòng)態(tài)的把ivLefttvRight 添加到布局中去,實(shí)現(xiàn)和第一個(gè)例子中同樣的效果。

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //布局文件別忘了
        setContentView(R.layout.activity_main)
        addPartView()
    }
    private fun addPartView() {
        val ivLeft = ImageView(this)
        ivLeft.id = R.id.ivLeft
        ivLeft.scaleType = ImageView.ScaleType.CENTER_CROP
        ivLeft.setImageResource(R.drawable.ic_lake)

        val ivLeftLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
            ScreenUtil.dpToPx(this, 100), 0
        )
        ivLeftLayoutParams.dimensionRatio = "h,16:9"
        ivLeftLayoutParams.topMargin = ScreenUtil.dpToPx(this, 16)
        ivLeftLayoutParams.marginStart = ScreenUtil.dpToPx(this, 16)
        ivLeftLayoutParams.leftToLeft = R.id.clRoot
        ivLeftLayoutParams.topToTop = R.id.clRoot

        ivLeft.layoutParams = ivLeftLayoutParams

        val tvRight = TextView(this)
        tvRight.id = R.id.tvRight
        tvRight.text = getString(R.string.lake_tahoe_title)
        tvRight.textSize = 30F
        val tvRightLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
            ConstraintLayout.LayoutParams.WRAP_CONTENT,
            ConstraintLayout.LayoutParams.WRAP_CONTENT
        )
        tvRightLayoutParams.startToEnd = R.id.ivLeft
        tvRightLayoutParams.topToTop = R.id.clRoot
        tvRightLayoutParams.marginStart = ScreenUtil.dpToPx(this, 16)
        tvRightLayoutParams.topMargin = ScreenUtil.dpToPx(this, 16)

        tvRight.layoutParams = tvRightLayoutParams


        val tvBottomLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
            0,
            ConstraintLayout.LayoutParams.WRAP_CONTENT
        )
        tvBottomLayoutParams.startToStart = R.id.clRoot
        tvBottomLayoutParams.marginStart = ScreenUtil.dpToPx(this, 8)

        tvBottomLayoutParams.endToEnd = R.id.clRoot
        tvBottomLayoutParams.marginEnd = ScreenUtil.dpToPx(this, 8)

        tvBottomLayoutParams.topToBottom = R.id.ivLeft
        tvBottomLayoutParams.topMargin = ScreenUtil.dpToPx(this, 24)

        tvBottomLayoutParams.bottomMargin = ScreenUtil.dpToPx(this, 8)

        //重新為布局中已經(jīng)存在的tvBottom設(shè)置新的布局參數(shù)。
        tvBottom.layoutParams = tvBottomLayoutParams

        clRoot.addView(ivLeft)
        clRoot.addView(tvRight)

    }

這種方式要注意重新為布局中已經(jīng)存在的控件設(shè)置新的布局參數(shù)。

動(dòng)態(tài)改變約束

如果我們想動(dòng)態(tài)改變布局中的View的約束該怎么做呢?比如我們想把上面的布局樣式改成下圖所示。


Screenshot_1559374001.png

其實(shí),在上面我們已經(jīng)給tvBottom動(dòng)態(tài)改變約束了,就是給View重新設(shè)置布局參數(shù)就好了。

給View重新設(shè)置布局參數(shù)

下面我們?cè)诖a中,重新改變View的布局參數(shù)。

    private fun changeLayoutParams() {
        val ivLeftLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
            0, 0
        )
        ivLeftLayoutParams.leftMargin = ScreenUtil.dpToPx(this, 16)
        ivLeftLayoutParams.rightMargin = ScreenUtil.dpToPx(this, 16)
        ivLeftLayoutParams.topMargin = ScreenUtil.dpToPx(this, 16)
        ivLeftLayoutParams.leftToLeft = R.id.clRoot
        ivLeftLayoutParams.rightToRight = R.id.clRoot
        ivLeftLayoutParams.dimensionRatio = "h,16:9"
        ivLeftLayoutParams.topToTop = R.id.clRoot

        //修改布局參數(shù)
        ivLeft.layoutParams = ivLeftLayoutParams

        val tvRightLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
            ConstraintLayout.LayoutParams.WRAP_CONTENT,
            ConstraintLayout.LayoutParams.WRAP_CONTENT
        )
        tvRightLayoutParams.leftToLeft = R.id.clRoot
        tvRightLayoutParams.topToBottom = R.id.ivLeft
        tvRightLayoutParams.marginStart = ScreenUtil.dpToPx(this, 16)
        tvRightLayoutParams.topMargin = ScreenUtil.dpToPx(this, 16)

        tvRight.layoutParams = tvRightLayoutParams


        val tvBottomLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
            0,
            ConstraintLayout.LayoutParams.WRAP_CONTENT
        )
        tvBottomLayoutParams.startToStart = R.id.clRoot
        tvBottomLayoutParams.marginStart = ScreenUtil.dpToPx(this, 8)

        tvBottomLayoutParams.endToEnd = R.id.clRoot
        tvBottomLayoutParams.marginEnd = ScreenUtil.dpToPx(this, 8)

        tvBottomLayoutParams.topToBottom = R.id.tvRight
        tvBottomLayoutParams.topMargin = ScreenUtil.dpToPx(this, 24)

        tvBottomLayoutParams.bottomMargin = ScreenUtil.dpToPx(this, 8)

        tvBottom.layoutParams = tvBottomLayoutParams
    }

改變后的效果就不貼了。

使用ConstraintSet 動(dòng)態(tài)修改約束

使用ConstraintSet 動(dòng)態(tài)修改約束分四步。

  1. 首先要聲明一下ConstraintSet對(duì)象
  val constraintSet = ConstraintSet()
  1. 復(fù)制一份現(xiàn)有的約束關(guān)系,這一步不是必須的。
//從一個(gè)constraintLayout中復(fù)制約束
set.clone(constraintLayout: ConstraintLayout);
//從一個(gè)ConstraintSet中復(fù)制約束
set.clone(set: ConstraintSet);
//從一個(gè)布局文件中復(fù)制約束
set.clone(context: Context, constraintLayoutId: Int);

如果說(shuō)你要改變布局中某些控件的約束,但是還要保存其他控件的約束關(guān)系,那么你就需要從已有的根布局中復(fù)制一份約束,然后只更改哪些需要改變的控件的約束關(guān)系。

注意復(fù)制約束關(guān)系的時(shí)候,布局中的每個(gè)控件必都有id,不然會(huì)報(bào)下面的錯(cuò)誤。

 java.lang.RuntimeException: All children of ConstraintLayout must have ids to use ConstraintSet

  1. 設(shè)置控件之間的約束
  2. 應(yīng)用新的約束。在應(yīng)用約束的時(shí)候,為了讓約束改變的時(shí)候不是那么突兀,我們可以設(shè)置一個(gè)動(dòng)畫(huà),來(lái)讓約束改變平滑一點(diǎn)。
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_dynamic_add_view)
      
        ivLeft.setOnClickListener {
            changeConstraintSet()
        }
    }
    private fun changeConstraintSet() {
        val constraintSet = ConstraintSet()
        //從根布局中克隆約束參數(shù)
        constraintSet.clone(clRoot)

        //清空控件原有的約束
        constraintSet.clear(R.id.ivLeft)
        constraintSet.clear(R.id.tvRight)
        constraintSet.clear(R.id.tvBottom)

        constraintSet.constrainWidth(R.id.ivLeft, 0)
        constraintSet.constrainHeight(R.id.ivLeft, 0)
        //設(shè)置ivLeft頂部和父布局頂部對(duì)齊
        constraintSet.connect(
            R.id.ivLeft, ConstraintSet.TOP, R.id.clRoot, ConstraintSet.TOP,
            ScreenUtil.dpToPx(this, 16)
        )
        constraintSet.connect(
            R.id.ivLeft, ConstraintSet.START, R.id.clRoot, ConstraintSet.START,
            ScreenUtil.dpToPx(this, 16)
        )
        constraintSet.connect(
            R.id.ivLeft, ConstraintSet.END, R.id.clRoot, ConstraintSet.END,
            ScreenUtil.dpToPx(this, 16)
        )
        //設(shè)置寬高比
        constraintSet.setDimensionRatio(R.id.ivLeft, "h,16:9")


        constraintSet.constrainWidth(R.id.tvRight, ConstraintLayout.LayoutParams.WRAP_CONTENT)
        constraintSet.constrainHeight(R.id.tvRight, ConstraintLayout.LayoutParams.WRAP_CONTENT)

        constraintSet.connect(
            R.id.tvRight, ConstraintSet.TOP, R.id.ivLeft, ConstraintSet.BOTTOM,
            ScreenUtil.dpToPx(this, 24)
        )

        constraintSet.connect(
            R.id.tvRight, ConstraintSet.START, R.id.clRoot, ConstraintSet.START,
            ScreenUtil.dpToPx(this, 8)
        )

        constraintSet.constrainHeight(R.id.tvBottom, ConstraintLayout.LayoutParams.WRAP_CONTENT)

        constraintSet.connect(
            R.id.tvBottom, ConstraintSet.START, R.id.clRoot, ConstraintSet.START,
            ScreenUtil.dpToPx(this, 8)
        )
        constraintSet.connect(
            R.id.tvBottom, ConstraintSet.END, R.id.clRoot, ConstraintSet.END,
            ScreenUtil.dpToPx(this, 8)
        )

        constraintSet.connect(
            R.id.tvBottom, ConstraintSet.TOP, R.id.tvRight, ConstraintSet.BOTTOM,
            ScreenUtil.dpToPx(this, 24)
        )

        constraintSet.applyTo(clRoot)
        //設(shè)置一個(gè)動(dòng)畫(huà)效果,讓約束改變平滑一點(diǎn),這一步不是必須的
        TransitionManager.beginDelayedTransition(clRoot)
    }

效果如下所示

1559465809770862.gif

遇到的一個(gè)問(wèn)題

在測(cè)試的時(shí)候,我想添加一個(gè)水平方向上的Guideline,讓它在父布局豎直方向比例為0.4的地方,然后在Guideline之上添加一個(gè)ImageView。代碼如下

    private fun addGuideLine() {
        val constraintLayout = ConstraintLayout(this)
        constraintLayout.id = R.id.clRoot
        constraintLayout.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT)
        //先設(shè)置根布局
        setContentView(constraintLayout)

        val guideline = Guideline(this)
        guideline.id = R.id.guideline

        val guideLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
                ConstraintLayout.LayoutParams.WRAP_CONTENT, ConstraintLayout.LayoutParams.WRAP_CONTENT)
        guideLayoutParams.guidePercent = 0.4f
        guideLayoutParams.topToTop = R.id.clRoot
        guideLayoutParams.bottomToBottom = R.id.clRoot
        //注意
        guideLayoutParams.orientation = ConstraintLayout.LayoutParams.VERTICAL

        guideline.layoutParams = guideLayoutParams

        constraintLayout.addView(guideline)

        val ivLeft = ImageView(this)
        ivLeft.id = R.id.ivLeft
        ivLeft.scaleType = ImageView.ScaleType.CENTER_CROP
        ivLeft.setImageResource(R.drawable.lake)

        val ivLeftLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
                0, 0)
        ivLeftLayoutParams.dimensionRatio = "h,16:9"
        ivLeftLayoutParams.bottomToTop = R.id.guideline
        ivLeftLayoutParams.startToStart = R.id.clRoot
        ivLeftLayoutParams.endToEnd = R.id.clRoot

        ivLeft.layoutParams = ivLeftLayoutParams

        constraintLayout.addView(ivLeft)
    }

在測(cè)試的時(shí)候報(bào)了一個(gè)錯(cuò)誤

java.lang.AssertionError: TOP at android.support.constraint.solver.widgets.Guideline.getAnchor(Guideline.java:159)


折騰了半天,發(fā)現(xiàn)是Guideline的方向?qū)戝e(cuò)了。

 guideLayoutParams.orientation = ConstraintLayout.LayoutParams.VERTICAL

正確的寫(xiě)法

 guideLayoutParams.orientation = ConstraintLayout.LayoutParams.HORIZONTAL

如果Guideline的方向?qū)戝e(cuò)了,會(huì)導(dǎo)致依賴(lài)Guideline的方向的控件的約束無(wú)法正確指定,所以會(huì)報(bào)錯(cuò)。如果遇到類(lèi)似的問(wèn)題請(qǐng)仔細(xì)檢查,是否正確的設(shè)置了約束。

參考鏈接

  1. Android開(kāi)發(fā)筆記(一百四十九)約束布局ConstraintLayout
  2. ConstraintLayout 之 ConstraintSet 動(dòng)態(tài)修改約束(動(dòng)畫(huà))
  3. ConstraintSet
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容