• BottomSheetDialogFragment大量踩坑-自适应高度和最大高度和滚动问题等等


    固定展开

    基本上,大部分情况我需要的BottomSheetDialog是固定的,而不是可以延展的。
    那么,可以在onCreateView的函数体里面:

    dialog.asOrNull<BottomSheetDialog>()?.behavior?.let { behavior->
        behavior.skipCollapsed = true
        behavior.state = BottomSheetBehavior.STATE_EXPANDED
    }
    
    inline fun <reified Obj> Any?.asOrNull(): Obj? {
        return if (this is Obj) {
            this
        } else {
            null
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    含有EditText

    如果内容是一个EditText,不能设置沉浸式,否则键盘无法弹出。即如下相关代码不能设置到dialogFragment上:

    WindowCompat.setDecorFitsSystemWindows
    WindowCompat.getInsetsController....isAppearanceLightStatusBars 和 isAppearanceLightNavigationBars
    statusBarColor = Color.TRANSPARENT
    
    • 1
    • 2
    • 3

    自定义Toast的显示父控件

    因为android有不少自定义类似ios的toast方式,从顶部下弹一个toast的自定义View。大概率是通过寻找DecorView的方式实现。
    那么, 对于从DialogFragment里面弹起的我们需要寻找一个合理的父控件。

        override fun findToastViewGroup(): ViewGroup? {
        	//rootView是你在onCreateView里面创建的我们自身的根布局。
            rootView?.let { tdv->
                val design_bottom_sheet = tdv.parent.asOrNull<ViewGroup>()
                design_bottom_sheet?.let { dbs->
                    val coordinator = dbs.parent.asOrNull<ViewGroup>() //coordinator
                    return coordinator?.parent.asOrNull() //container
                }
            }
            return null
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    无法在dismiss之前得到监听

    研究了源码,确实无法保证系统的拖动关闭dismiss在我们想要结束我们的一些逻辑之后,再让他dismiss。
    因此:

    public class BeforeDismissBottomSheetDialog extends BottomSheetDialog {
        public interface BeforeDismissCallback {
            void onBeforeDismiss(BeforeDismissBottomSheetDialog dialog);
        }
    
        private BeforeDismissCallback beforeDismissCallback;
    
        public void setBeforeDismissCallback(BeforeDismissCallback beforeDismissCallback) {
            this.beforeDismissCallback = beforeDismissCallback;
        }
    
        public BeforeDismissBottomSheetDialog(@NonNull Context context) {
            super(context);
        }
    
        public BeforeDismissBottomSheetDialog(@NonNull Context context, int theme) {
            super(context, theme);
        }
    
        protected BeforeDismissBottomSheetDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) {
            super(context, cancelable, cancelListener);
        }
    
        @Override
        public void dismiss() {
            if (beforeDismissCallback != null) {
                beforeDismissCallback.onBeforeDismiss(this);
            }
            super.dismiss();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    然后,再在onCreateDialog中:

        <style name="CustomBottomSheetStyle" parent="Widget.Material3.BottomSheet.Modal">
            <item name="shapeAppearance">@null</item>
        </style>
     
        <style name="MyBottomSheetDialogTheme" parent="Theme.Material3.Light.BottomSheetDialog">
            <item name="bottomSheetStyle">@style/CustomBottomSheetStyle</item>
            <item name="enableEdgeToEdge">true</item>
            <item name="android:windowTranslucentNavigation">true</item>
            <item name="android:navigationBarColor">#ffffff</item>
        </style>
    
        override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
            val sheetDialog = BeforeDismissBottomSheetDialog(requireContext(), R.style.MyBottomSheetDialogTheme)
            createdDialog = sheetDialog
            //看情况设定不允许延展 sheetDialog.behavior.skipCollapsed = true
            //看情况设定不允许延展 sheetDialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
    
            sheetDialog.setBeforeDismissCallback { //能在关闭之前干完想干的事情了。
                onDismissBlock?.invoke(this)
                onDismissBlock = null
            }
    
            sheetDialog.setOnShowListener { // 通过这个来判断内容的显示是最万无一失的。
                onShownBlock?.invoke(this)
                onShownBlock = null
            }
            return sheetDialog
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    另外值得一提的是,由于是Fragment特性,onStart,onCreateDialog, onViewCreated,外加一堆的dialog的onShow等逻辑,均可能产生真实的内容并没有显示。setOnShowListener是最保险的。

    自适应高度和限定最大高度

    生命周期为:
    onCreateDialog -> 内部创建mDialog -> onCreateView -> onViewCreated
    所以想要拿到dialog则可以在onCreateView阶段。

    自适应高度,我们只需要给布局的高度设定为wrap_content即可。
    但是限定最大高度,一直是个问题,经过各种资料和实践如下最为稳妥,最好是放在onCreateView里面。因为maxHeight的设定,并不会执行什么刷新渲染的逻辑,而在这里设定,是还没有渲染之前。

    	override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    	///.....
    	//activity的来源,也可以是你全局的top activity
    			val screenTriple = activity?.window?.getScreenSizeWithStatusAndNavHeight(1)
                val maxHeight:Int = screenTriple?.first?.y ?: Int.MAX_VALUE
                val height = this.yourParseHeight ?: 0 //如果你传入的yourParseHeight存在则优先使用
                fixHeight = min(height, maxHeight)
                if (fixHeight > 0) {
                    binding.root.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, fixHeight)
                } else {
                    dialog.asOrNull<BottomSheetDialog>()?.behavior?.let { behavior->
                        behavior.maxHeight = screenTriple?.first?.y ?: WindowManager.LayoutParams.MATCH_PARENT //不太可能走到?:后面去。
                    }
                }
    }
    
    fun Window.getScreenSizeWithStatusAndNavHeight(displayMode:Int, portrait:Boolean = true): Triple<Point, Int, Int> {
        val point = Point()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            val metrics = windowManager.currentWindowMetrics
            val bounds = metrics.bounds
            point.x = bounds.width()
            point.y = bounds.height()
            if (displayMode > 0) {
                val windowInsets = metrics.windowInsets
                val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars() or WindowInsets.Type.displayCutout())
                //需要减去状态栏和导航栏高度
                if (portrait) {
                    point.y -= insets.top
                    if(displayMode == 2) point.y -= insets.bottom
                    return Triple(point, insets.top, insets.bottom)
                } else {
                    point.x = bounds.width() - insets.left
                    if(displayMode == 2) point.y -= insets.right
                    return Triple(point, insets.left, insets.right)
                }
            }
            return Triple(point, 0, 0)
        } else {
            if (displayMode == 0) {
                windowManager.defaultDisplay.getSize(point)
            } else {
                windowManager.defaultDisplay?.getRealSize(point) //todo 低版本没有实现 mode=1的情况。
            }
            return Triple(point, 0, 0) //todo 低版本没有实现 mode=1的情况。
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    含有滚动内容

    主要就是滑动能够往上滑动,但是往下滑动就会出现滑动冲突。到底是BottomSheetDialog自身往下拉还是RecyclerView的内部下拉发生冲突。
    我尝试了使用NestedScrollView效果不理想,最终通通改成recyclerView。哪怕是一些图文结合,或者长文字,都可以分割为多个item(通过viewHolder的布局也是wrap高度来处理)使用RecyclerView来组合实现滑动。
    这样做的原因是:
    通常我们想要在Dialog上显示较高可滑动的东西,并非要它的性能和回收item机制。而且我们需要自适应内容的高度。所以RecyclerView大概率需要设置height为wrap_content。众所周知,RecyclerView设置wrap其实就是放弃了他的item复用机制了。这是题外话。

    另外,千万不要给它设置如下属性,否则会出现滑动冲突。

    android:nestedScrollingEnabled="false"
    android:overScrollMode="never"
    
    • 1
    • 2
  • 相关阅读:
    在 Python 中实现 DBSCAN
    【电商API接口的应用:电商数据分析入门】初识Web API(一)
    算法通关村第十七关:青铜挑战-贪心其实很简单
    面试:Sqlite的线程、进程安全性
    leetcode 3. 无重复字符的最长子串
    将Excel数据导入到SQL Server数据库
    【C语言】循环结构程序设计 (详细讲解)
    订单超时未支付自动取消8种实现方案
    手工实现一个字符串内容替换方法来替换replace
    y111.第六章 微服务、服务网格及Envoy实战 -- Envoy网格安全(二二)
  • 原文地址:https://blog.csdn.net/jzlhll123/article/details/138159730