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
    }
}

含有EditText

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

WindowCompat.setDecorFitsSystemWindows
WindowCompat.getInsetsController....isAppearanceLightStatusBars 和 isAppearanceLightNavigationBars
statusBarColor = Color.TRANSPARENT

自定义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
    }

无法在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();
    }
}

然后,再在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
    }

另外值得一提的是,由于是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的情况。
    }
}

含有滚动内容

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

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

android:nestedScrollingEnabled="false"
android:overScrollMode="never"
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值