滑动时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
相关产品推荐
相关产品推荐

