You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

滑动时ExoPlayer视频超出BottomSheetDialogFragment问题求助

问题:BottomSheetDialogFragment中ExoPlayer滑动时超出显示范围

在BottomSheetDialogFragment中加载包含ExoPlayer组件的现有Fragment时,滑动操作会导致视频组件超出BottomSheetDialogFragment的显示边界。已尝试修改样式、BottomSheetBehavior、嵌套滚动视图等多种方案,均未解决。


相关代码

ReelBottomSheetDialogFragment.kt

import android.app.Dialog
import android.content.DialogInterface
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.WindowManager
import androidx.databinding.DataBindingUtil
import com.caratlane.android.Fragments.ListingPageFragment
import com.caratlane.android.R
import com.caratlane.android.Utils.Constants
import com.caratlane.android.Utils.Constants.ReelsKey.PDP_ORIGIN_KEY
import com.caratlane.android.Utils.Constants.ReelsKey.PRODUCT_SKU
import com.caratlane.android.Utils.Methods
import com.caratlane.android.activity.CLBaseToolbarActivity
import com.caratlane.android.activity.MainActivity
import com.caratlane.android.activity.ShoppingCartActivity
import com.caratlane.android.databinding.LayoutProductDetailBottomSheetBinding
import com.caratlane.android.mvvm.home.view.HomeFragment
import com.caratlane.android.mvvm.listing.view.ListingFragment
import com.caratlane.android.mvvm.loginFeature.model.apimodel.NavigationSource
import com.caratlane.android.mvvm.loginFeature.view.fragments.LoginLandingFragment
import com.caratlane.android.mvvm.reels.view.ReelsFragment
import com.caratlane.android.mvvm.reviewsratings.view.BaseProductDetailFragment
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment


class ReelBottomSheetDialogFragment : BottomSheetDialogFragment(),
    LoginLandingFragment.LogInCallback{
    var binding: LayoutProductDetailBottomSheetBinding? = null
    var onReelsBottomSheetStatusChange: OnBottomSheetStatusChange? = null
    var onPdpBottomSheetStatusChange: OnBottomSheetStatusChange? = null
    private var logInReelListener: LoginLandingFragment.LogInCallback? = null
    var sku: String? = null
    var reelClickedListner : ReelClickedListener? =null

    override fun getTheme(): Int {
        return R.style.AppBottomSheetDialogTheme
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        (activity as CLBaseToolbarActivity?)?.hideToolBar()
        (activity as CLBaseToolbarActivity?)?.hideBackToolbar()
        return if (arguments?.getBoolean(ReelsFragment.OPEN_PDP, false) == true) {
            sku = arguments?.getString(PRODUCT_SKU)
            addProductDetailFragment()
            openFullExpandedBottomSheet()
        } else {
            openLoginFragment()
            openFullExpandedBottomSheet()
        }
    }

    private fun openFullExpandedBottomSheet(): BottomSheetDialog {
        val dialog = BottomSheetDialog(requireContext(), theme)
        dialog.setOnShowListener {
            val bottomSheetDialog = it as BottomSheetDialog
            val parentLayout =
                bottomSheetDialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
            parentLayout?.let { 
                val behaviour = BottomSheetBehavior.from(it)
                behaviour.state = BottomSheetBehavior.STATE_COLLAPSED
                behaviour.addBottomSheetCallback(object :
                    BottomSheetBehavior.BottomSheetCallback() {
                    override fun onStateChanged(bottomSheet: View, newState: Int) {
                    }

                    override fun onSlide(bottomSheet: View, slideOffset: Float) {
                    }
                })
            }
        }
        return dialog
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = DataBindingUtil.inflate(
            LayoutInflater.from(context),
            R.layout.layout_product_detail_bottom_sheet,
            container,
            false
        )
        return binding?.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.viewTreeObserver.addOnGlobalLayoutListener(object :
            ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                view.viewTreeObserver.removeOnGlobalLayoutListener(this)
                dialog?.setOnShowListener {}
            }
        })
    }

    private fun setupFullHeight(bottomSheet: View) {
        val layoutParams = bottomSheet.layoutParams
        layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT
        bottomSheet.layoutParams = layoutParams
    }

    private fun addProductDetailFragment() {
        val productDetailFragment = BaseProductDetailFragment()
        arguments?.putBoolean("isToolbarVisible", false)
        arguments?.putString(PDP_ORIGIN_KEY, arguments?.getString(PDP_ORIGIN_KEY))
        arguments?.putString(
            Constants.ReelsKey.IS_FROM,
            Constants.ReelsKey.REELS_PAGE
        )
        productDetailFragment.arguments = arguments
        productDetailFragment.takeCallback(object : BaseProductDetailFragment.ViewClick{
            override fun onAnyViewClicked() {
                Log.d("bottomsheetdialogfragment","in on click")
                dialog?.dismiss()
            }

            override fun onReelClicked(videoId: Long) {
                Log.d("bottomsheetdialogfragment","reel video id "+videoId.toString())
                reelClickedListner?.onReelClickedFromPdTray(videoId)
            }
        })
        val fragmentManager = childFragmentManager.beginTransaction()
        fragmentManager.replace(R.id.product_detail_container, productDetailFragment, productDetailFragment.tag)
        fragmentManager.commit()
    }

    override fun onResume() {
        super.onResume()
        if (sku != null) {
            onPdpBottomSheetStatusChange?.onPDBottomSheetOpened(sku)
            onReelsBottomSheetStatusChange?.onPDBottomSheetOpened(sku)
        }
    }

    fun setPdpBottomSheetOpenListener(listener: OnBottomSheetStatusChange) {
        onPdpBottomSheetStatusChange = listener
    }

    fun setReelsBottomSheetStatusChangeListener(listener: OnBottomSheetStatusChange){
        onReelsBottomSheetStatusChange = listener
    }

    private fun openLoginFragment() {
        if (!Methods.isActivityAlive(activity)) return
        val loginLandingFragment: LoginLandingFragment = if (activity is MainActivity) {
            (activity as MainActivity).signInSignUpFragment
        } else {
            (activity as ShoppingCartActivity).signInSignUpFragment
        }
        val arguments = Bundle()
        arguments.putSerializable(
            Constants.BUNDLE_EXTRAS.SOURCE_NAV,
            NavigationSource.ReelsPage
        )
        loginLandingFragment.arguments = arguments
        loginLandingFragment.setLoginListener(this)
        childFragmentManager.beginTransaction()
            .add(R.id.product_detail_container, loginLandingFragment, loginLandingFragment.tag)
            .commitAllowingStateLoss()
    }

    fun setLoginListener(listener : LoginLandingFragment.LogInCallback){
        logInReelListener = listener
    }


    override fun onLoginSuccess() {
        logInReelListener?.onLoginSuccess()
        dismiss()
    }

    override fun onLoginBackPressed() {
        dialog?.dismiss()
    }

    override fun onDismiss(dialog: DialogInterface) {
        super.onDismiss(dialog)
        if (sku != null) {
            onPdpBottomSheetStatusChange?.onPDBottomSheetClose(sku)
            onReelsBottomSheetStatusChange?.onPDBottomSheetClose(sku)
        }
    }

    fun setReelClickedListener(listener  : ReelClickedListener){
        reelClickedListner = listener
    }

    interface OnBottomSheetStatusChange {
        fun onPDBottomSheetOpened(sku: String?)
        fun onPDBottomSheetClose(sku: String?)
    }

    interface ReelClickedListener{
        fun onReelClickedFromPdTray(videoId : Long)
    }
}

styles.xml

<!--    Rounded corner BottomSheet-->
<style name="AppBottomSheetDialogTheme" parent="Theme.Design.Light.BottomSheetDialog">
    <item name="android:windowIsFloating">true</item>
    <item name="android:windowSoftInputMode">adjustResize|stateAlwaysVisible</item>
    <item name="bottomSheetStyle">@style/AppModalStyle</item>
    <item name="android:statusBarColor">@android:color/transparent</item>
</style>

layout_product_detail_bottom_sheet.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">


    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:id="@+id/cl_product_detail"
        android:layout_height="match_parent"
        android:layout_width="match_parent">

        <androidx.appcompat.widget.LinearLayoutCompat
            android:id="@+id/ll_product_detail_header"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/dp_10"
                android:layout_marginStart="@dimen/dp_10"
                android:layout_marginBottom="@dimen/dp_10"
                android:gravity="center">

                <LinearLayout
                    android:layout_width="@dimen/_32dp"
                    android:layout_height="@dimen/dp_4"
                    android:layout_gravity="center_horizontal"
                    android:background="@drawable/shape_purple_round_corner"
                    android:gravity="center_horizontal"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent">

                </LinearLayout>


            </androidx.constraintlayout.widget.ConstraintLayout>


            <androidx.appcompat.widget.LinearLayoutCompat
                android:id="@+id/product_detail_container"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

            </androidx.appcompat.widget.LinearLayoutCompat>
        </androidx.appcompat.widget.LinearLayoutCompat>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</layout>

解决方案

1. 给BottomSheet容器添加裁剪属性

在布局的各级容器中添加android:clipChildren="true"和android:clipToPadding="true",强制子View不超出父容器边界:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:id="@+id/cl_product_detail"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="true"
        android:clipToPadding="true">

        <androidx.appcompat.widget.LinearLayoutCompat
            android:id="@+id/ll_product_detail_header"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:clipChildren="true"
            android:clipToPadding="true">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/dp_10"
                android:layout_marginStart="@dimen/dp_10"
                android:layout_marginBottom="@dimen/dp_10"
                android:gravity="center">

                <LinearLayout
                    android:layout_width="@dimen/_32dp"
                    android:layout_height="@dimen/dp_4"
                    android:layout_gravity="center_horizontal"
                    android:background="@drawable/shape_purple_round_corner"
                    android:gravity="center_horizontal"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent">
                </LinearLayout>

            </androidx.constraintlayout.widget.ConstraintLayout>

            <androidx.appcompat.widget.LinearLayoutCompat
                android:id="@+id/product_detail_container"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:clipChildren="true"
                android:clipToPadding="true">
            </androidx.appcompat.widget.LinearLayoutCompat>

        </androidx.appcompat.widget.LinearLayoutCompat>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</layout>

2. 修复样式中的浮动窗口属性

当前样式设置android:windowIsFloating="true"会导致BottomSheet脱离布局约束,改为false:

<style name="AppBottomSheetDialogTheme" parent="Theme.Design.Light.BottomSheetDialog">
    <item name="android:windowIsFloating">false</item>
    <item name="android:windowSoftInputMode">adjustResize|stateAlwaysVisible</item>
    <item name="bottomSheetStyle">@style/AppModalStyle</item>
    <item name="android:statusBarColor">@android:color/transparent</item>
</style>

3. 优化BottomSheetBehavior回调,控制视频状态

在滑动过程中强制保持裁剪,并在BottomSheet状态变化时控制ExoPlayer的播放状态,避免异常:

private fun openFullExpandedBottomSheet(): BottomSheetDialog {
    val dialog = BottomSheetDialog(requireContext(), theme)
    dialog.setOnShowListener {
        val bottomSheetDialog = it as BottomSheetDialog
        val parentLayout = bottomSheetDialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
        parentLayout?.let { 
            val behaviour = BottomSheetBehavior.from(it)
            behaviour.state = BottomSheetBehavior.STATE_COLLAPSED
            // 强制开启裁剪
            parentLayout.clipChildren = true
            parentLayout.clipToPadding = true
            
            behaviour.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
                override fun onStateChanged(bottomSheet: View, newState: Int) {
                    // 状态变化时控制视频播放
                    val pdpFragment = childFragmentManager.findFragmentById(R.id.product_detail_container) as? BaseProductDetailFragment
                    when(newState) {
                        BottomSheetBehavior.STATE_HIDDEN, BottomSheetBehavior.STATE_COLLAPSED -> {
                            pdpFragment?.pauseVideo()
                        }
                        BottomSheetBehavior.STATE_EXPANDED -> {
                            pdpFragment?.resumeVideo()
                        }
                    }
                }

                override fun onSlide(bottomSheet: View, slideOffset: Float) {
                    // 滑动过程中保持裁剪
                    bottomSheet.clipChildren = true
                    bottomSheet.clipToPadding = true
                }
            })
        }
    }
    return dialog
}

需要在BaseProductDetailFragment中实现pauseVideo()和resumeVideo()方法,控制ExoPlayer的播放与暂停。

4. 检查ExoPlayer布局约束

确保包含ExoPlayer的Fragment布局中,PlayerView的父容器设置了正确的约束(如match_parent),无超出边界的margin/padding,避免视频组件本身布局越界。


内容的提问来源于stack exchange,提问作者Nitesh Chauhan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 07:49:55