ScrollView 内部指南 - 使用视口 %

Guidelines Inside ScrollView - use Viewport %

我想向应用程序添加内容,该应用程序从垂直向下约 70% 开始,并且可以向上滚动以覆盖前 70% 的观看次数。

我考虑过在父 ConstraintLayout 中使用两个子 ConstraintLayout - 这两个子 ConstraintLayout 将在彼此之上。一个将包含将填充屏幕前 70% 的视图,而另一个将包含一个 NestedScrollView,它有一个不可见的 <View>,它占据 70% 的高度,然后是可以向上滚动的附加内容。

我在标记 70% 点时遇到问题 - 在 NestedScrollView 中使用指南不起作用,因为 %s 是流动的(它匹配 NestedScrollView 中 70% 的内容,而不是 70%可视屏幕)。在 NestedScrollView 之外使用 Guideline 是行不通的,因为...约束必须是兄弟才能编译。

我怎样才能做到这一点?

           <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/parentLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            >

            <androidx.constraintlayout.widget.ConstraintLayout
                android:id="@+id/firstConstraintLayout"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/red5F"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent">
                // A bunch of content that should fill up the first 70% of the screen and be covered by the overlay if user scrolls
            </androidx.constraintlayout.widget.ConstraintLayout>
            <androidx.constraintlayout.widget.ConstraintLayout
                android:id="@+id/overlayConstraintLayout"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent">
              
                <androidx.core.widget.NestedScrollView
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:fillViewport="true"
                    android:id="@+id/scrollView"
                    app:layout_constraintLeft_toLeftOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent">
                    <androidx.constraintlayout.widget.ConstraintLayout
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:id="@+id/overlayInnerLayout">
                        <androidx.constraintlayout.widget.Guideline
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:id="@+id/verticalGuidelineOverlay"
                            android:orientation="horizontal"
                            app:layout_constraintGuide_percent="0.7"/>
                        <View
                            android:layout_width="match_parent"
                            android:layout_height="0dp"
                            android:id="@+id/spacerView"
                            app:layout_constraintTop_toTopOf="parent"
                            app:layout_constraintBottom_toTopOf="@id/verticalGuidelineOverlay"
                            app:layout_constraintLeft_toLeftOf="parent"/>
                        // More content here that the user could scroll upwards that would start at the 70% point and eventually cover the entire screen.
                     </ConstraintLayout>
           </NestedScrollView>
      </ConstraintLayout>
    </ConstraintLayout>

视频 w/example 此处:https://imgur.com/a/BTolYUu

删除指南并使用像这样的视图作为间隔视图。它的高度限制为宽度的 1.15。你可以稍微改变它以获得你想要的

<View
    android:id="@+id/spacerView"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintDimensionRatio="1:1.15"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>

也仅供参考

  1. 你不应该在 ConstraintLayout 中使用 match_parent,使用 0dp 并将其约束到两侧。
  2. 顶部布局可以用 FrameLayout 代替,因为您实际上并没有使用任何约束

试试这个方法,

<?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/parentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/firstConstraintLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent">


</androidx.constraintlayout.widget.ConstraintLayout>

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/overlayConstraintLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent">

    <androidx.core.widget.NestedScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:fillViewport="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:weightSum="1">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="0.7"
                android:orientation="horizontal">

                <RelativeLayout
                    android:id="@+id/transparentView"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"/>
                <View
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    />

            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"

                android:orientation="vertical"
                android:layout_weight="0.3" />

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@drawable/bg">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="20sp"
                android:text="@string/lorem_ipsum"
                tools:ignore="MissingConstraints"
                android:textSize="18sp"/>
            </RelativeLayout>
        </LinearLayout>
    </androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

使用 layoutParams 以编程方式设置 70% 高度

val transparentView = findViewById<RelativeLayout>(R.id.transparentView)

    val metrics = DisplayMetrics()
    windowManager.defaultDisplay.getMetrics(metrics)

    val height = Math.min(metrics.widthPixels, metrics.heightPixels) //height


    val params = transparentView.layoutParams
    params.height = (height * 70) / 70
    transparentView.layoutParams = params

您将得到所需的结果:enter link description here

您可以使用主题为 Theme_Translucent_NoTitleBar 的自定义 BottomSheetDialogFragment,并在用户向上或向下拖动时更改对话框根布局的 y 值.

class MyDialogFragment(height: Int) : BottomSheetDialogFragment(), View.OnTouchListener {

    private val outsideWindowHeight = height

    private val rootLayout by lazy {
        requireView().findViewById<LinearLayout>(R.id.dialog_root)
    }

    private var oldY = 0
    private var baseLayoutPosition = 0
    private var defaultViewHeight = 0
    private var isClosing = false
    private var isScrollingUp = false
    private var isScrollingDown = false

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return BottomSheetDialog(
            requireContext(),
            android.R.style.Theme_Translucent_NoTitleBar
        )
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {

        val view: View = inflater.inflate(
            R.layout.fragment_dialog, container,
            false
        )
        view.setBackgroundResource(R.drawable.rounded_background)

        (dialog as BottomSheetDialog).apply {
            setCancelable(false)
            behavior.peekHeight =
                (outsideWindowHeight * 0.3).toInt()  // Minimum height of the BottomSheet is 30% of the root layout (to leave the 70% to the main layout)
        }

        return view
    }


    @SuppressLint("ClickableViewAccessibility")
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        rootLayout.setOnTouchListener(this)

    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        // Get finger position on screen
        val y = event!!.rawY.toInt()

        // Switch on motion event type
        when (event.action and MotionEvent.ACTION_MASK) {
        
            MotionEvent.ACTION_DOWN -> {
                // save default base layout height
                defaultViewHeight = rootLayout.height

                oldY = y
                baseLayoutPosition = rootLayout.y.toInt()
            }
            
            MotionEvent.ACTION_UP -> {
                // If user was doing a scroll up
                if (isScrollingUp) {
                    // Reset baselayout position
                    rootLayout.y = 0f
                    // We are not in scrolling up anymore
                    isScrollingUp = false
                }

                // If user was doing a scroll down
                if (isScrollingDown) {
                    // Reset baselayout position
                    rootLayout.y = 0f
//                     Reset base layout size
                    rootLayout.layoutParams.height = defaultViewHeight
                    rootLayout.requestLayout()
                    // We are not in scrolling down anymore
                    isScrollingDown = false
                }
            }


            MotionEvent.ACTION_MOVE -> {

                if (rootLayout.y <= -100) {
                    return true
                }

                if (!isClosing) {
                    val currentYPosition = rootLayout.y.toInt()

                    // If we scroll up
                    if (oldY > y) {
                        // First time android rise an event for "up" move
                        if (!isScrollingUp) {
                            isScrollingUp = true
                        }

                        rootLayout.y = rootLayout.y + (y - oldY)
                        
                    } else {

                        // First time android rise an event for "down" move
                        if (!isScrollingDown) {
                            isScrollingDown = true
                        }


                        // change position because view anchor is top left corner
                        rootLayout.y = rootLayout.y + (y - oldY)
                        rootLayout.requestLayout()
                    }

                    // Update position
                    oldY = y
                }
            }
        }
        return true
    }
}

fragment_dialog.xml(没什么特别的):

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/dialog_root"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_bottom_sheet_heading"
        android:layout_width="wrap_content"
        android:layout_height="@dimen/dp_56"
        android:layout_marginStart="@dimen/dp_16"
        android:layout_marginEnd="@dimen/dp_16"
        android:gravity="center"
        android:text="@string/bottom_sheet_option_heading"
        android:textColor="@android:color/black"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/tv_btn_add_photo_camera"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_48"
        android:layout_marginStart="@dimen/dp_16"
        android:layout_marginEnd="@dimen/dp_16"
        android:backgroundTint="@android:color/white"
        android:drawableStart="@drawable/ic_camera_alt_black_24dp"
        android:drawableLeft="@drawable/ic_camera_alt_black_24dp"
        android:drawablePadding="@dimen/dp_32"
        android:drawableTint="@color/md_bottom_sheet_text_color"
        android:gravity="start|center_vertical"
        android:text="@string/bottom_sheet_option_camera"
        android:textColor="@color/md_bottom_sheet_text_color"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/tv_btn_add_photo_gallery"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_marginStart="@dimen/dp_16"
        android:layout_marginEnd="@dimen/dp_16"
        android:backgroundTint="@android:color/white"
        android:drawableStart="@drawable/ic_insert_photo_black_24dp"
        android:drawableLeft="@drawable/ic_insert_photo_black_24dp"
        android:drawablePadding="@dimen/dp_32"
        android:drawableTint="@color/md_bottom_sheet_text_color"
        android:gravity="start|center_vertical"
        android:text="@string/bottom_sheet_option_gallery"
        android:textColor="@color/md_bottom_sheet_text_color"
        android:textSize="16sp" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_marginTop="@dimen/md_bottom_sheet_separator_top_margin"
        android:layout_marginBottom="@dimen/dp_8"
        android:background="@color/grayTextColor" />

    <TextView
        android:id="@+id/tv_btn_remove_photo"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_48"
        android:layout_marginStart="@dimen/dp_16"
        android:layout_marginEnd="@dimen/dp_16"
        android:backgroundTint="@android:color/white"
        android:drawableStart="@drawable/ic_delete_black_24dp"
        android:drawableLeft="@drawable/ic_delete_black_24dp"
        android:drawablePadding="@dimen/dp_32"
        android:drawableTint="@color/md_bottom_sheet_text_color"
        android:gravity="start|center_vertical"
        android:text="@string/bottom_sheet_option_remove_photo"
        android:textColor="@color/md_bottom_sheet_text_color"
        android:textSize="16sp" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btn_material"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Material button"
        android:textAppearance="@style/TextAppearance.AppCompat.Medium" />


    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/longText1"
        android:textColor="@color/white"
        android:textSize="22sp" />


</LinearLayout>

并将主布局的根ViewGroup的高度发送到主activity中的对话框:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val root = findViewById<ConstraintLayout>(R.id.root)

        root.viewTreeObserver.addOnGlobalLayoutListener(object :
            OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                root.viewTreeObserver
                    .removeOnGlobalLayoutListener(this)

                val dialogFragment = MyDialogFragment(root.height)
                dialogFragment.show(supportFragmentManager, "dialog_tag")

            }
        })

    }
}

预览: