【问题标题】:Arranging ConstraintLayout Flow References From Bottom To Top And Right To Left从下到上和从右到左排列 ConstraintLayout 流引用
【发布时间】:2021-09-23 04:24:13
【问题描述】:

我需要从下到上和从右到左填充 ConstraintLayout Flow 视图。这是我的代码:

<androidx.constraintlayout.helper.widget.Flow
    android:id="@+id/flow"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:orientation="vertical"
    app:constraint_referenced_ids="tv1,tv2,tv3,tv4,tv5,tv6,tv7,tv8,tv9,tv10"
    app:flow_horizontalGap="8dp"
    app:flow_verticalGap="8dp"
    app:flow_verticalStyle="packed"
    app:flow_wrapMode="chain"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintHeight_percent=".5"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

结果如下:

(事实上,由于 API 响应,我将动态设置引用,因此假设视图不在 XML 布局中)

但我需要以下一个:

感谢任何帮助。

【问题讨论】:

    标签: android xml android-layout android-constraintlayout constraintlayout-helper-widget-flow


    【解决方案1】:

    要从下到上和从右到左排列项目,您可以使用FlexboxLayout Google Library。您只需要在 FlexboxLayout 中使用以下属性:

    1. app:flexDirection="column_reverse" 这将从下到上绘制每个项目。

    2. app:flexWrap="wrap_reverse"这将从右到左绘制每个项目。

    3. app:justifyContent="flex_start" 这将控制每个项目的对齐方式,我们使用flex_start 从底部对齐。

    Xml 示例如下:

    <?xml version="1.0" encoding="utf-8"?>
    <HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <com.google.android.flexbox.FlexboxLayout
            android:id="@+id/flexLayout"
            android:layout_width="wrap_content"
            android:layout_height="300dp"
            app:flexDirection="column_reverse"
            app:flexWrap="wrap_reverse"
            app:justifyContent="flex_start"
            android:background="@android:color/white">
    
            <TextView
                android:id="@+id/tv1"
                android:layout_width="120dp"
                android:layout_height="65dp"
                android:background="@android:color/holo_orange_dark"
                android:gravity="center"
                android:layout_margin="5dp"
                android:text="1"
                android:textSize="18sp" />
    
            <TextView
                android:id="@+id/tv2"
                android:layout_width="120dp"
                android:layout_height="65dp"
                android:background="@android:color/holo_orange_dark"
                android:gravity="center"
                android:layout_margin="5dp"
                android:text="2"
                android:textSize="18sp" />
    
            <TextView
                android:id="@+id/tv3"
                android:layout_width="120dp"
                android:layout_height="65dp"
                android:background="@android:color/holo_orange_dark"
                android:gravity="center"
                android:layout_margin="5dp"
                android:text="3"
                android:textSize="18sp" />
    
            <TextView
                android:id="@+id/tv4"
                android:layout_width="120dp"
                android:layout_height="65dp"
                android:background="@android:color/holo_orange_dark"
                android:gravity="center"
                android:layout_margin="5dp"
                android:text="4"
                android:textSize="18sp" />
    
            <TextView
                android:id="@+id/tv5"
                android:layout_width="120dp"
                android:layout_height="65dp"
                android:background="@android:color/holo_orange_dark"
                android:gravity="center"
                android:layout_margin="5dp"
                android:text="5"
                android:textSize="18sp" />
    
            <TextView
                android:id="@+id/tv6"
                android:layout_width="120dp"
                android:layout_height="65dp"
                android:background="@android:color/holo_orange_dark"
                android:gravity="center"
                android:layout_margin="5dp"
                android:text="6"
                android:textSize="18sp" />
    
            <TextView
                android:id="@+id/tv7"
                android:layout_width="120dp"
                android:layout_height="65dp"
                android:background="@android:color/holo_orange_dark"
                android:gravity="center"
                android:layout_margin="5dp"
                android:text="7"
                android:textSize="18sp" />
    
            <TextView
                android:id="@+id/tv8"
                android:layout_width="120dp"
                android:layout_height="65dp"
                android:background="@android:color/holo_orange_dark"
                android:gravity="center"
                android:layout_margin="5dp"
                android:text="8"
                android:textSize="18sp" />
    
            <TextView
                android:id="@+id/tv9"
                android:layout_width="120dp"
                android:layout_height="65dp"
                android:background="@android:color/holo_orange_dark"
                android:gravity="center"
                android:layout_margin="5dp"
                android:text="9"
                android:textSize="18sp" />
    
            <TextView
                android:id="@+id/tv10"
                android:layout_width="120dp"
                android:layout_height="65dp"
                android:background="@android:color/holo_orange_dark"
                android:gravity="center"
                android:layout_margin="5dp"
                android:text="10"
                android:textSize="18sp" />
    
        </com.google.android.flexbox.FlexboxLayout>
    
    </HorizontalScrollView>
    

    或以编程方式:

    val flexLayout: FlexboxLayout = findViewById<FlexboxLayout>(R.id.flexLayout)
    val itemWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 120f, resources.displayMetrics).toInt()
    val itemHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 65f, resources.displayMetrics).toInt()
    val itemMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics).toInt()
    
    for (i in 0..9) {
        val tv = TextView(this)
        tv.text = (i + 1).toString()
        tv.setBackgroundColor(ContextCompat.getColor(this, android.R.color.holo_orange_dark))
        tv.gravity = Gravity.CENTER
        val params = FlexboxLayout.LayoutParams(itemWidth, itemHeight)
        params.setMargins(itemMargin, itemMargin, itemMargin, itemMargin)
        tv.layoutParams = params
        flexLayout.addView(tv)
    }
    

    结果:

    【讨论】:

      【解决方案2】:

      我没有使用 Flow 的经验,但我知道,你想动态地处理这个, 因此,如果您能够处理来自服务器的项目订单,这将对您有很大帮助。

      只要您想在 4 行中显示它们,它对任何项目大小(不仅仅是 10)都很有用。

      我将使用 GridLayoutManagerspanSizeLookup 来处理这个问题。

      val items = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
      val layoutManager = GridLayoutManager(context, 4, GridLayoutManager.HORIZONTAL, false)
      
      val targetPosition = (items.size / 4) * 4
      val spanSizeLookup: GridLayoutManager.SpanSizeLookup by lazy {
                  object : GridLayoutManager.SpanSizeLookup() {
                      override fun getSpanSize(position: Int): Int {
                          return if (position == targetPosition) (items.size % 4) + 1 else 1
                      }
                  }
              }
      
      layoutManager.spanSizeLookup = spanSizeLookup
      recyclerView.layoutManager = layoutManager
      adapter.setData(items)
      recyclerView.adapter = adapter
      

      这将导致目标项目填充回收器视图的空高度以实现您想要的。

      您需要做的最后一步是将现有 xml 项的根包装在 FrameLayout 中,并赋予该根、底部布局重力,如下所示:

      <FrameLayout
          android:layout_width="wrap_content"
          android:layout_height="match_parent"
          android:layout_margin="6dp">
      
          <TextView
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_gravity="bottom"
              />
      </FrameLayout>
      

      我知道它并不完全是你想要的,但我希望你能设法对你的数组进行排序并使用这个方法来动态创建它,就像上图一样

      最好的问候

      【讨论】:

        【解决方案3】:

        您可以在布局中保持 Id 的正常顺序,并创建一个自定义 Flow 类,将 Id 重新排列为您想要的自定义顺序(从下到上,从右到左):

        class CustomFlow(context: Context, attrs: AttributeSet?) : Flow(context, attrs) {
        
            init {
                val newIds = IntArray(referencedIds.size)
                Log.d("LOG_TAG", "init BEFORE: ${Arrays.toString(referencedIds)}")
                for ((i, item) in (referencedIds).withIndex())
                    newIds[referencedIds.size - i - 1] = item
        
                referencedIds = newIds
                Log.d("LOG_TAG", "init AFTER: ${Arrays.toString(referencedIds)}")
            }
        
        }
        

        并在您的布局中使用CustomFlow

        演示布局:

        <?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="wrap_content">
        
            <com.example.android.constraintlayoutflow.CustomFlow
                android:id="@+id/flow"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:constraint_referenced_ids="button1, button2, button3, button4, button5, button6, button7, button8, button9, button10, button11, button12"
                app:flow_horizontalGap="8dp"
                app:flow_maxElementsWrap="4"
                app:flow_verticalGap="8dp"
                app:flow_verticalStyle="packed"
                app:flow_wrapMode="chain"
                tools:ignore="MissingConstraints" />
        
            <Button
                android:id="@+id/button1"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_orange_light"
                android:text="1"
                android:textSize="18sp"
                tools:ignore="MissingConstraints" />
        
            <Button
                android:id="@+id/button2"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_orange_light"
                android:text="2"
                android:textSize="18sp"
                tools:ignore="MissingConstraints" />
        
            <Button
                android:id="@+id/button3"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_orange_light"
                android:text="3"
                android:textSize="18sp"
                tools:ignore="MissingConstraints" />
        
            <Button
                android:id="@+id/button4"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_orange_light"
                android:text="4"
                android:textSize="18sp"
                tools:ignore="MissingConstraints" />
        
            <Button
                android:id="@+id/button5"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_orange_light"
                android:text="5"
                android:textSize="18sp"
                tools:ignore="MissingConstraints" />
        
            <Button
                android:id="@+id/button6"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_orange_light"
                android:text="6"
                android:textSize="18sp"
                tools:ignore="MissingConstraints" />
        
            <Button
                android:id="@+id/button7"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_orange_light"
                android:text="7"
                android:textSize="18sp"
                tools:ignore="MissingConstraints" />
        
            <Button
                android:id="@+id/button8"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_orange_light"
                android:text="8"
                android:textSize="18sp"
                tools:ignore="MissingConstraints" />
        
            <Button
                android:id="@+id/button9"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_orange_light"
                android:text="9"
                android:textSize="18sp"
                tools:ignore="MissingConstraints" />
        
            <Button
                android:id="@+id/button10"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_orange_light"
                android:text="10"
                android:textSize="18sp"
                tools:ignore="MissingConstraints" />
        
            <Button
                android:id="@+id/button11"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_orange_light"
                android:textSize="18sp"
                tools:ignore="MissingConstraints" />
        
            <Button
                android:id="@+id/button12"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_orange_light"
                android:textSize="18sp"
                tools:ignore="MissingConstraints" />
        
        
        </androidx.constraintlayout.widget.ConstraintLayout>
        

        预览:

        更新

        如果您删除 button11 和 button12,它没有可接受的行为。请问能不能改进一下?

        因此,您希望通过删除 button11button12 来使其保持相同的行为。

        即您不需要额外的按钮或流程的空白填充物(这可以通过将其可见性设置为不可见来解决)。

        但是在这里,我将通过以下更改摆脱它们:

        • 修改自定义Flow 以处理该列是否为全尺寸列。
        • android:rotationY="180"反转布局以模拟RTL方向,并对每个单独的元素进行反转以恢复this answer中描述的反转文本问题。
        • 分配app:flow_verticalBias="1" 以便从底部开始偏置元素。

        演示:

        <?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="wrap_content"
            android:rotationY="180">
        
        
            <com.example.android.constraintlayoutflow.CustomFlow
                android:id="@+id/flow"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:constraint_referenced_ids="button1, button2, button3, button4, button5, button6, button7, button8, button9, button10"
                app:flow_horizontalGap="8dp"
                app:flow_verticalBias="1"
                app:flow_verticalGap="8dp"
                app:flow_verticalStyle="packed"
                app:flow_wrapMode="chain"
                tools:flow_maxElementsWrap="4"
                tools:ignore="MissingConstraints" />
        
            <FrameLayout
                android:id="@+id/button1"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:rotationY="180"
                tools:ignore="MissingConstraints">
        
                <Button
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@android:color/holo_orange_light"
                    android:text="1"
                    android:textSize="18sp" />
            </FrameLayout>
        
            <FrameLayout
                android:id="@+id/button2"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:rotationY="180"
                tools:ignore="MissingConstraints">
        
                <Button
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@android:color/holo_orange_light"
                    android:text="2"
                    android:textSize="18sp" />
            </FrameLayout>
        
        
            <FrameLayout
                android:id="@+id/button3"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:rotationY="180"
                tools:ignore="MissingConstraints">
        
                <Button
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@android:color/holo_orange_light"
                    android:text="3"
                    android:textSize="18sp" />
            </FrameLayout>
        
            <FrameLayout
                android:id="@+id/button4"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:rotationY="180"
                tools:ignore="MissingConstraints">
        
                <Button
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@android:color/holo_orange_light"
                    android:text="4"
                    android:textSize="18sp" />
            </FrameLayout>
        
        
            <FrameLayout
                android:id="@+id/button5"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:rotationY="180"
                tools:ignore="MissingConstraints">
        
                <Button
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@android:color/holo_orange_light"
                    android:text="5"
                    android:textSize="18sp" />
            </FrameLayout>
        
        
            <FrameLayout
                android:id="@+id/button6"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:rotationY="180"
                tools:ignore="MissingConstraints">
        
                <Button
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@android:color/holo_orange_light"
                    android:text="6"
                    android:textSize="18sp" />
            </FrameLayout>
        
        
            <FrameLayout
                android:id="@+id/button7"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:rotationY="180"
                tools:ignore="MissingConstraints">
        
                <Button
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@android:color/holo_orange_light"
                    android:text="7"
                    android:textSize="18sp" />
            </FrameLayout>
        
        
            <FrameLayout
                android:id="@+id/button8"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:rotationY="180"
                tools:ignore="MissingConstraints">
        
                <Button
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@android:color/holo_orange_light"
                    android:text="8"
                    android:textSize="18sp" />
            </FrameLayout>
        
        
            <FrameLayout
                android:id="@+id/button9"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:rotationY="180"
                tools:ignore="MissingConstraints">
        
                <Button
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@android:color/holo_orange_light"
                    android:text="9"
                    android:textSize="18sp" />
            </FrameLayout>
        
            <FrameLayout
                android:id="@+id/button10"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:rotationY="180"
                tools:ignore="MissingConstraints">
        
                <Button
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@android:color/holo_orange_light"
                    android:text="10"
                    android:textSize="18sp" />
            </FrameLayout>
        
        </androidx.constraintlayout.widget.ConstraintLayout>
        

        自定义流程:

        class CustomFlow(context: Context, attrs: AttributeSet?) : Flow(context, attrs) {
        
            init {
                val size = referencedIds.size
                val newIds = IntArray(size)
                Log.d("LOG_TAG", "init BEFORE: ${Arrays.toString(referencedIds)}")
        
                val COL_SIZE = 4
                setMaxElementsWrap(COL_SIZE)
                val extraItemCount = size % COL_SIZE
                val fullSize = extraItemCount == 0
                val colCount = if (fullSize) size / COL_SIZE else size / COL_SIZE + 1
        
                var counter = 1
                var baseIndex = -1
                for ((i, item) in (referencedIds).withIndex()) {
                    val col: Int = i / COL_SIZE + 1
        
                    var newI: Int
        
                    if (fullSize || col < colCount) {
                        newI = col * COL_SIZE - i - 1 + (col - 1) * COL_SIZE
        
                    } else {
                        if (baseIndex == -1) baseIndex = i
                        newI = baseIndex + extraItemCount - counter
                        counter++
                    }
                    newIds[newI] = item
        
                }
        
                referencedIds = newIds
                Log.d("LOG_TAG", "init AFTER: ${Arrays.toString(referencedIds)}")
            }
        
        
        }
        

        结果:

        【讨论】:

        • 如果您删除 button11button12,它没有可接受的行为。请问你能做得更好吗?
        • @AlirezaNoorali 只是为了确保,您的流程中的按钮始终不超过 10 个,并且想要摆脱 button11button12
        • 正如我在问题帖子中所说,查看次数取决于 API 响应,因此查看次数可能会超过 10 个。
        • @AlirezaNoorali 我根据您的要求更新了答案。请查看UPDATE 部分
        【解决方案4】:

        您实际上并不需要按数字顺序设置 constraint_referenced_ids,而是您的布局顺序。您可以使用不可见的视图/空间作为空间占位符。

        所以我编辑以下代码供您参考:

        <?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"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        
            <TextView
                android:id="@+id/space1"
                style="@style/text_style"
                android:layout_width="0dp"
                android:layout_height="60dp"
                android:gravity="center"
                android:text=""
                android:visibility="invisible" />
        
            <TextView
                android:id="@+id/A"
                style="@style/text_style"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:gravity="center"
                android:text="8" />
        
            <TextView
                android:id="@+id/B"
                style="@style/text_style"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:gravity="center"
                android:text="4" />
        
            <TextView
                android:id="@+id/space2"
                style="@style/text_style"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:gravity="center"
                android:text=""
                android:visibility="invisible" />
        
            <TextView
                android:id="@+id/C"
                style="@style/text_style"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:gravity="center"
                android:text="7" />
        
            <TextView
                android:id="@+id/D"
                style="@style/text_style"
                android:layout_width="0dp"
                android:layout_height="60dp"
                android:gravity="center"
                android:text="3" />
        
            <TextView
                android:id="@+id/E"
                style="@style/text_style"
                android:layout_width="0dp"
                android:layout_height="60dp"
                android:gravity="center"
                android:text="10" />
        
            <TextView
                android:id="@+id/F"
                style="@style/text_style"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:gravity="center"
                android:text="6" />
        
            <TextView
                android:id="@+id/G"
                style="@style/text_style"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:gravity="center"
                android:text="2" />
        
            <TextView
                android:id="@+id/H"
                style="@style/text_style"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:gravity="center"
                android:text="9" />
        
            <TextView
                android:id="@+id/I"
                style="@style/text_style"
                android:layout_width="0dp"
                android:layout_height="60dp"
                android:gravity="center"
                android:text="5" />
        
            <TextView
                android:id="@+id/J"
                style="@style/text_style"
                android:layout_width="0dp"
                android:layout_height="60dp"
                android:gravity="center"
                android:text="1" />
        
            <androidx.constraintlayout.helper.widget.Flow
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:constraint_referenced_ids="space1,A,B,space2,C,D,E,F,G,H,I,J"
                app:flow_horizontalGap="8dp"
                app:flow_horizontalStyle="packed"
                app:flow_maxElementsWrap="3"
                app:flow_verticalGap="8dp"
                app:flow_verticalStyle="packed"
                app:flow_wrapMode="aligned"
                app:layout_constraintTop_toTopOf="parent" />
        
        </androidx.constraintlayout.widget.ConstraintLayout>
        

        预览:

        【讨论】:

        • 哦不,我需要一个通用的解决方案,而不是一个技巧。事实上,由于 API 响应,我将动态设置引用,因此假设视图不在 xml 布局中。对不起,这是我的错,我会尽快编辑我的问题。
        • 如果动态添加View,为什么不使用RecyclerView。
        • GridView无法像第二张图片那样排列项目。
        猜你喜欢
        • 2010-12-02
        • 2015-05-08
        • 2014-08-23
        • 2015-06-20
        • 2018-06-15
        • 1970-01-01
        • 1970-01-01
        • 2018-12-29
        • 2014-12-31
        相关资源
        最近更新 更多