史上最完美的Android沉浸式状态导航栏攻略

这篇具有很好参考价值的文章主要介绍了史上最完美的Android沉浸式状态导航栏攻略。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

最近我在小破站开发一款新App,叫高能链。我是一个完美主义者,所以不管对架构还是UI,我都是比较抠细节的,在状态栏和导航栏沉浸式这一块,我还是踩了挺多坑,费了挺多精力的。这次我将我踩坑,适配各机型总结出来的史上最完美的Android沉浸式状态导航栏攻略分享给大家,大家也可以去 高能链官网 下载体验一下我们的App,实际感受一下沉浸式状态导航栏的效果(登录,实名等账号相关页面由于不是我开发的,就没有适配沉浸式导航栏啦,嘻嘻)

注:此攻略只针对 Android 5.0 及以上机型,即 minSdkVersion >= 21

实际效果

在开始攻略之前,我们先看看完美的沉浸式状态导航栏效果

传统三键式导航栏

史上最完美的Android沉浸式状态导航栏攻略

史上最完美的Android沉浸式状态导航栏攻略

全面屏导航条

史上最完美的Android沉浸式状态导航栏攻略

史上最完美的Android沉浸式状态导航栏攻略

理论分析

在上具体实现代码之前,我们先分析一下,实现沉浸式状态导航栏需要几步

  1. 状态栏导航栏底色透明

  2. 根据当前页面的背景色,给状态栏字体和导航栏按钮(或导航条)设置亮色或暗色

  3. 状态栏导航栏设置透明后,我们页面的布局会延伸到原本状态栏导航栏的位置,这时候需要一些手段将我们需要显示的正文内容回缩到其正确的显示范围内

    这里我给大家提供以下几种思路,大家可以根据实际情况自行选择:

    • 设置fitsSystemWindows属性
    • 根据状态栏导航栏的高度,给根布局设置相应的paddingToppaddingBottom
    • 根据状态栏导航栏的高度,给需要移位的控件设置相应的marginTopmarginBottom
    • 在顶部和底部增加两个占位的View,高度分别设置成状态栏和导航栏的高度
    • 针对滑动视图,巧用clipChildrenclipToPadding属性(可参照高能链藏品详情页样式)

沉浸式状态栏

思路说完了,我们现在开始进入实战,沉浸式状态栏比较简单,没什么坑

状态栏透明

首先第一步,我们需要将状态栏的背景设置为透明,这里我直接放代码

fun transparentStatusBar(window: Window) {
    window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
    var systemUiVisibility = window.decorView.systemUiVisibility
    systemUiVisibility =
        systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
    window.decorView.systemUiVisibility = systemUiVisibility
    window.statusBarColor = Color.TRANSPARENT

    //设置状态栏文字颜色
    setStatusBarTextColor(window, NightMode.isNightMode(window.context))
}

首先,我们需要将FLAG_TRANSLUCENT_STATUS这个windowFlag换成FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,否则状态栏不会完全透明,会有一个半透明的灰色蒙层

FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS这个flag表示系统Bar的背景将交给当前window绘制

SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN这个flag表示Activity全屏显示,但状态栏不会被隐藏,依然可见

SYSTEM_UI_FLAG_LAYOUT_STABLE这个flag表示保持整个View稳定,使View不会因为系统UI的变化而重新layout

SYSTEM_UI_FLAG_LAYOUT_FULLSCREENSYSTEM_UI_FLAG_LAYOUT_STABLE这两个flag通常是一起使用的,我们设置这两个flag,然后再将statusBarColor设置为透明,就达成了状态栏背景透明的效果

状态栏文字颜色

接着我们就该设置状态栏文字颜色了,细心的小伙伴们应该已经注意到了,我在transparentStatusBar方法的末尾加了一个setStatusBarTextColor的方法调用,一般情况下,如果是日间模式,页面背景通常都是亮色,所以此时状态栏文字颜色设置为黑色比较合理,而在夜间模式下,页面背景通常都是暗色,此时状态栏文字颜色设置为白色比较合理,对应代码如下

fun setStatusBarTextColor(window: Window, light: Boolean) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        var systemUiVisibility = window.decorView.systemUiVisibility
        systemUiVisibility = if (light) { //白色文字
            systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
        } else { //黑色文字
            systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
        }
        window.decorView.systemUiVisibility = systemUiVisibility
    }
}

Android 8.0以上才支持导航栏文字颜色的修改,SYSTEM_UI_FLAG_LIGHT_STATUS_BAR这个flag表示亮色状态栏,即黑色状态栏文字,所以如果希望状态栏文字为黑色,就设置这个flag,如果希望状态栏文字为白色,就将这个flagsystemUiVisibility中剔除

可能有小伙伴不太了解kotlin中的位运算,kotlin中的orandinv分别对应着或、与、取反运算

所以

systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()

翻译成java即为

systemUiVisibility & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR

在原生系统上,这么设置就可以成功设置状态栏文字颜色,但我发现,在某些系统上,这样设置后的效果是不可预期的,譬如MIUI系统的状态栏文字颜色似乎是根据状态栏背景颜色自适应的,且日间模式和黑夜模式下的自适应策略还略有不同。不过在大多数情况下,它自适应的颜色都是正常的,我们就按照我们希望的结果设置就可以了。

矫正显示区域

fitsSystemWindows

矫正状态栏显示区域最简单的办法就是设置fitsSystemWindows属性,设置了该属性的View的所有padding属性都将失效,并且系统会自动为其添加paddingTop(设置了透明状态栏的情况下)和paddingBottom(设置了透明导航栏的情况下)

我个人是不用这种方式的,首先它会覆盖你设置的padding,其次,如果你同时设置了透明状态栏和透明导航栏,这个属性没有办法分开来处理,很不灵活

获取状态栏高度

除了fitsSystemWindows这种方法外,其他的方法都得依靠获取状态栏高度了,这里直接上代码

fun getStatusBarHeight(context: Context): Int {
    val resId = context.resources.getIdentifier(
        "status_bar_height", "dimen", "android"
    )
    return context.resources.getDimensionPixelSize(resId)
}

状态栏不像导航栏那样多变,所以直接这样获取高度就可以了,导航栏的高度飘忽不定才是真正的噩梦

这里再给两个设置View marginpadding的工具方法吧,帮助大家快速使用

fun fixStatusBarMargin(vararg views: View) {
    views.forEach { view ->
        (view.layoutParams as? ViewGroup.MarginLayoutParams)?.let { lp ->
            lp.topMargin = lp.topMargin + getStatusBarHeight(view.context)
            view.requestLayout()
        }
    }
}

fun paddingByStatusBar(view: View) {
    view.setPadding(
        view.paddingLeft,
        view.paddingTop + getStatusBarHeight(view.context),
        view.paddingRight,
        view.paddingBottom
    )
}

沉浸式导航栏

沉浸式导航栏相比沉浸式状态栏坑会多很多,具体原因我们后面再说

导航栏透明

和沉浸式状态栏一样,第一步我们需要将导航栏的背景设置为透明

fun transparentNavigationBar(window: Window) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        window.isNavigationBarContrastEnforced = false
    }
    window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
    var systemUiVisibility = window.decorView.systemUiVisibility
    systemUiVisibility =
        systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
    window.decorView.systemUiVisibility = systemUiVisibility
    window.navigationBarColor = Color.TRANSPARENT

    //设置导航栏按钮或导航条颜色
    setNavigationBarBtnColor(window, NightMode.isNightMode(window.context))
}

Android 10以上,当设置了导航栏栏背景为透明时,isNavigationBarContrastEnforced如果为true,则系统会自动绘制一个半透明背景来提供对比度,所以我们要将这个属性设为false

ps:状态栏其实也有对应的属性isStatusBarContrastEnforced,只不过这个属性默认即为false,我们不需要特意去设置

导航栏按钮或导航条颜色

和设置状态栏文字颜色一样,我这里就不多介绍了

fun setNavigationBarBtnColor(window: Window, light: Boolean) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        var systemUiVisibility = window.decorView.systemUiVisibility
        systemUiVisibility = if (light) { //白色按钮
            systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
        } else { //黑色按钮
            systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
        }
        window.decorView.systemUiVisibility = systemUiVisibility
    }
}

矫正显示区域

fitsSystemWindows

和状态栏使用一样,我就不重复说明了

获取导航栏高度

自从全面屏手势开始流行,导航栏也从原先的三键式,变成了三键式、导航条、全隐藏这三种情况,这三种情况下的高度也是互不相同的

三键式和导航条这两种情况我们都可以通过android.R.dimen.navigation_bar_height这个资源获取到准确高度,但现在很多系统都支持隐藏导航栏的功能,在这种情况下,虽然实际导航栏的高度应该是0,但是通过资源获取到的高度却为三键式或导航条的高度,这就给我们沉浸式导航栏的适配带来了很大困难

经过我的各种尝试,我发现只有一种方式可以准确的获取到当前导航栏的高度,那就是WindowInsets,至于WindowInsets是什么我就不多介绍了,我们直接看代码

/**
* 仅当view attach window后生效
*/
private fun getRealNavigationBarHeight(view: View): Int {
    val insets = ViewCompat.getRootWindowInsets(view)
        ?.getInsets(WindowInsetsCompat.Type.navigationBars())
    //WindowInsets为null则默认通过资源获取高度
    return insets?.bottom ?: getNavigationBarHeight(view.context)
}

这里需要注意到我在方法上写的注释,只有当ViewWindow attach 后,才能获得到WindowInsets,否则为null,所以我一开始的想法是先检查View是否 attach 了Window,如果有的话则直接调用getRealNavigationBarHeight方法,如果没有的话,调用View.addOnAttachStateChangeListener方法,当出发attach回调后,再调用getRealNavigationBarHeight方法获取高度

这种方式在大部分情况下运行良好,但在我一次无意中切换了系统夜间模式后发现,获取到的导航栏高度变成了0,并且这还是一个偶现的问题,于是我尝试使用View.setOnApplyWindowInsetsListener,监听WindowInsets的变化发现,这个回调有可能会触发多次,在触发多次的情况下,前几次的值都为0,只有最后一次的值为真正的导航栏高度

于是我准备用View.setOnApplyWindowInsetsListener代替View.addOnAttachStateChangeListener,但毕竟一个是setListener,一个是addListener,setListener有可能会把之前设置好的Listener覆盖,或者被别的Listener覆盖掉,再考虑到之后会提到的底部Dialog沉浸式导航栏适配的问题,我折中了一下,决定只对Activity下的rootView设置回调

以下是完整代码

private class NavigationViewInfo(
    val hostRef: WeakReference<View>,
    val viewRef: WeakReference<View>,
    val rawBottom: Int,
    val onNavHeightChangeListener: (View, Int, Int) -> Unit
)

private val navigationViewInfoList = mutableListOf<NavigationViewInfo>()

private val onApplyWindowInsetsListener = View.OnApplyWindowInsetsListener { v, insets ->
    val windowInsetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets, v)
    val navHeight =
        windowInsetsCompat.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
    val it = navigationViewInfoList.iterator()
    while (it.hasNext()) {
        val info = it.next()
        val host = info.hostRef.get()
        val view = info.viewRef.get()
        if (host == null || view == null) {
            it.remove()
            continue
        }

        if (host == v) {
            info.onNavHeightChangeListener(view, info.rawBottom, navHeight)
        }
    }
    insets
}

private val actionMarginNavigation: (View, Int, Int) -> Unit =
    { view, rawBottomMargin, navHeight ->
        (view.layoutParams as? ViewGroup.MarginLayoutParams)?.let {
            it.bottomMargin = rawBottomMargin + navHeight
            view.requestLayout()
        }
    }

private val actionPaddingNavigation: (View, Int, Int) -> Unit =
    { view, rawBottomPadding, navHeight ->
        view.setPadding(
            view.paddingLeft,
            view.paddingTop,
            view.paddingRight,
            rawBottomPadding + navHeight
        )
    }

fun fixNavBarMargin(vararg views: View) {
    views.forEach {
        fixSingleNavBarMargin(it)
    }
}

private fun fixSingleNavBarMargin(view: View) {
    val lp = view.layoutParams as? ViewGroup.MarginLayoutParams ?: return
    val rawBottomMargin = lp.bottomMargin

    val viewForCalculate = getViewForCalculate(view)

    if (viewForCalculate.isAttachedToWindow) {
        val realNavigationBarHeight = getRealNavigationBarHeight(viewForCalculate)
        lp.bottomMargin = rawBottomMargin + realNavigationBarHeight
        view.requestLayout()
    }
    
	//isAttachedToWindow方法并不能保证此时的WindowInsets是正确的,仍然需要添加监听
    val hostRef = WeakReference(viewForCalculate)
    val viewRef = WeakReference(view)
    val info = NavigationViewInfo(hostRef, viewRef, rawBottomMargin, actionMarginNavigation)
    navigationViewInfoList.add(info)
    viewForCalculate.setOnApplyWindowInsetsListener(onApplyWindowInsetsListener)
}

fun paddingByNavBar(view: View) {
    val rawBottomPadding = view.paddingBottom

    val viewForCalculate = getViewForCalculate(view)

    if (viewForCalculate.isAttachedToWindow) {
        val realNavigationBarHeight = getRealNavigationBarHeight(viewForCalculate)
        view.setPadding(
            view.paddingLeft,
            view.paddingTop,
            view.paddingRight,
            rawBottomPadding + realNavigationBarHeight
        )
    }

	//isAttachedToWindow方法并不能保证此时的WindowInsets是正确的,仍然需要添加监听
    val hostRef = WeakReference(viewForCalculate)
    val viewRef = WeakReference(view)
    val info =
        NavigationViewInfo(hostRef, viewRef, rawBottomPadding, actionPaddingNavigation)
    navigationViewInfoList.add(info)
    viewForCalculate.setOnApplyWindowInsetsListener(onApplyWindowInsetsListener)
}

/**
* Dialog下的View在低版本机型中获取到的WindowInsets值有误,
* 所以尝试去获得Activity的contentView,通过Activity的contentView获取WindowInsets
*/
@SuppressLint("ContextCast")
private fun getViewForCalculate(view: View): View {
    return (view.context as? ContextWrapper)?.let {
        return@let (it.baseContext as? Activity)?.findViewById<View>(android.R.id.content)?.rootView
    } ?: view.rootView
}

/**
* 仅当view attach window后生效
*/
private fun getRealNavigationBarHeight(view: View): Int {
    val insets = ViewCompat.getRootWindowInsets(view)
        ?.getInsets(WindowInsetsCompat.Type.navigationBars())
    return insets?.bottom ?: getNavigationBarHeight(view.context)
}

我简单解释一下这段代码:为所有需要沉浸的页面的根View设置同一个回调,并将待适配导航栏高度的View添加到列表中,当WindowInsets回调触发后,遍历这个列表,判断触发回调的Viewhost是否与待适配导航栏高度的View对应,对应的话则处理View适配导航栏高度

这里需要注意,WindowInsets的分发其实是在dispatchAttachedToWindow之后的,所以isAttachedToWindow方法并不能保证此时的WindowInsets是正确的,具体可以去看ViewRootImpl中的源码,关键方法:dispatchApplyInsets,这里判断isAttachedToWindow并设置高度是为了防止出现View已经完全布局完成,之后再也不会触发OnApplyWindowInsets的情况

这里我也测试了内存泄漏情况,确认无内存泄漏,大家可以放心食用

底部Dialog适配沉浸式

底部Dialog适配沉浸式要比正常的Activity更麻烦一些,主要问题也是集中在沉浸式导航栏上

获取导航栏高度

仔细的小伙伴们可以已经注意到了我在沉浸式导航栏获取高度那里代码中的注释,Dialog下的View在低版本机型(经测试,Android 9一下就会有这个问题)中获取到的WindowInsets值有误,所以尝试去获得ActivitycontentView,通过ActivitycontentView获取WindowInsets

LayoutParams导致的异常

在某些系统上(比如MIUI),当我window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)时,沉浸式会出现问题,状态栏会被蒙层盖住,Dialog底部的内容也会被一个莫名其妙的东西遮挡住

我的解决方案是,window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT),然后布局最外层全部占满,内部留一个底部容器

<!-- dialog_pangu_bottom_wrapper -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent">

    <FrameLayout
        android:id="@+id/pangu_bottom_dialog_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:clickable="true"
        android:focusable="true" />

</FrameLayout>

然后在代码中重写setContentView方法

private var canceledOnTouchOutside = true

override fun setContentView(layoutResID: Int) {
    setContentView(
        LayoutInflater.from(context).inflate(layoutResID, null, false)
    )
}

override fun setContentView(view: View) {
    setContentView(
        view,
        ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        )
    )
}

override fun setContentView(view: View, params: ViewGroup.LayoutParams?) {
    val root =
        LayoutInflater.from(context).inflate(R.layout.dialog_pangu_bottom_wrapper, null, false)
    root.setOnClickListener {
        if (canceledOnTouchOutside) {
            dismiss()
        }
    }
    val container = root.findViewById<ViewGroup>(R.id.pangu_bottom_dialog_container)
    container.addView(view, params)

    super.setContentView(
        root,
        ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )
    )
}

override fun setCanceledOnTouchOutside(cancel: Boolean) {
    super.setCanceledOnTouchOutside(cancel)
    canceledOnTouchOutside = cancel
}

这样的话视觉效果就和普通的底部Dialog一样了,为了进一步减小底部Dialog显示隐藏动画之间的差异,我将动画插值器从linear_interpolator换成了decelerate_interpolatoraccelerate_interpolator

<!-- dialog_enter_from_bottom_to_top -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:fromYDelta="100%"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:toYDelta="0" />
<!-- dialog_exit_from_top_to_bottom -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:fromYDelta="0"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:toYDelta="100%" />

尾声

自此,目前沉浸式遇到的问题全部都解决了,如果以后发现了什么新的问题,我会在这篇文章中补充说明,如果还有什么不明白的地方可以评论,我考虑要不要拿几个具体的场景实战讲解,各位看官老爷麻烦点个赞收个藏不迷路😄文章来源地址https://www.toymoban.com/news/detail-425283.html

到了这里,关于史上最完美的Android沉浸式状态导航栏攻略的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【UI篇】Android 沉浸式状态栏的那些事

    此篇文章介绍的内容都是考虑到5.0 版本以上。5.0以下的不做介绍。演示的代码皆为kotlin实现 首先一般设置是包括两方面的,第一是设置 window.decorView.systemUiVisibility ,第二是设置 window.addFlags() 这些方法都有一些常量的设置。但是在API 30 版本之后又新出了一个 WindowInsetsContro

    2024年01月25日
    浏览(79)
  • Android 沉浸式状态栏的实现以及布局重叠的适配(Kotlin)

    本文章分以下内容: 沉浸式状态栏的实现 沉浸式导航栏的实现 适配实现沉浸式状态栏后导致的布局偏移 其他常见问题 该方法用于表示内容是否超出至状态栏和底部导航栏显示,false表明超出。(这也就我们实现沉浸式后需要做适配的原因) 注意:当使用setDecorFitsSystemWind

    2024年02月04日
    浏览(47)
  • 【Android】获取导航栏、状态栏高度

    或者 或者 系统的各种数据定义位于SDK的xml文件中: android-30/data/res/values/dimens.xml 通过key可以获取对应的值。 导航栏和状态栏源码相似 调用 updateColorViews 更新背景色的方法,还有如下位置: WindowInsets 可以翻译为窗口附加物,一般是指一个界面中,不由开发者直接控制的部分

    2024年02月12日
    浏览(45)
  • Android WindowInsetsController 设置状态栏、导航栏

    有 hide() ,也有 show() 在更早的版本中,使用 ViewCompat.getWindowInsetsController() 获取 WindowInsetsControllerCompat 实例 而现在推荐使用 WindowCompat.etInsetsController() 获取 WindowInsetsControllerCompat 实例 底部的三个按键就是导航栏 (navigation bar): back / home / recent 。 高版本系统,recent,可能没有图

    2024年02月15日
    浏览(44)
  • C++:史上最坑小游戏

    史上最坑小游戏,先别使用,往后看! 破解: 输入1~4的破解方法(二次开机)或: 输入5,6:的破解方法(一旦运行此程序,只有按下Windows开始键(其实我也不知道它叫什么,就是那个按一下就能显示开始窗口的键)然后按 cmd就会弹出一个窗口,根平时代码运行时的窗口一

    2024年01月21日
    浏览(38)
  • 史上最简单的Kafka安装教程

     解压apache-zookeeper-3.8.0-bin.tar.gz到指定目录,复制conf目录下zoo_sample.cfg到zoo.cfg,并修改配置。 进入bin目录,启动zookeeper 解压kafka_2.12-3.0.0.tgz到指定目录。 进入到config目录,修改server.properties配置 advertised.listeners才是真正的对外代理地址,listeners的作用不是对外提供服务代理,

    2024年02月07日
    浏览(52)
  • MySQL Workbench 操作详解(史上最细)

    右键新建的数据库BMI,设置为此次连接的默认数据库,接下来的所有操作都将在这个数据库下进行 将bmi下拉单展开,点击Table,右键创建Table: 给Table命名,添加Column,设置Column的Datatype,PrimaryKey等属性。点击Apply后,Workbench仍会自动生成SQL语句,再次点击Apply,成功创建新表

    2023年04月08日
    浏览(44)
  • Android 显示、隐藏状态栏和导航栏

    控制状态栏显示,Activity的主题中配置全屏属性 控制状态栏显示,在setContentView之前设置全屏的flag 复制代码 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 控制状态栏显示,在任何位置通过添加和移除全屏的flag 控制状态栏和导航栏显示,

    2024年02月08日
    浏览(42)
  • 史上最详细的八大排序详解!(建议收藏)

    🚀write in front🚀 📜所属专栏:初阶数据结构 🛰️博客主页:睿睿的博客主页 🛰️代码仓库:🎉VS2022_C语言仓库 🎡您的点赞、关注、收藏、评论,是对我最大的激励和支持!!! 关注我,关注我,关注我 , 你们将会看到更多的优质内容!!   从今天开始,我们就进入

    2023年04月20日
    浏览(51)
  • Java对接微信支付(史上最详细)

    本文将介绍如何使用Java对接微信支付,包括获取支付参数、支付回调处理等步骤。本文适用于已经熟悉微信支付基本原理的读者。 JDK 1.8 Maven Spring Boot 2.x 微信支付开发文档 为了进行支付,我们需要先获取微信支付的参数信息,包括appid、商户id、支付密钥等。 配置文件 我们

    2024年02月15日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包