Jetpack Compose 中适配不同的屏幕尺寸

news2025/8/7 19:14:12

窗口大小分类

Compose 将 Android 设备的屏幕尺寸分为三类:

  • Compact: 小屏幕,一般就是手机设备,屏幕宽度 < 600dp
  • Medium:中等屏幕,大号的板砖手机如折叠屏或平板的竖屏,600dp < 屏幕宽度 < 840dp
  • Expanded:展开屏幕,平板或平板电脑等,屏幕宽度 > 840dp

在这里插入图片描述

它是以某个维度来划分的,如上图是以宽度作为划分点,当然也可以按照高度来作为划分点:

在这里插入图片描述

但由于垂直滚动的普遍存在,可用宽度通常比可用高度更重要;因此,宽度窗口大小类别很可能与应用的界面更相关。所以大部分开发者只需要根据宽度调整应用即可。

窗口大小类别不适用于“isTablet-type”逻辑,而是由应用可用的窗口大小决定(无论运行应用的设备是什么类型);这有两个重大影响:

  • 实体设备不能保证特定的窗口大小类别。应用可用的屏幕空间可能会与设备的屏幕尺寸不同,这有很多原因。在移动设备上,分屏模式可以在多个应用之间拆分屏幕。在 Chrome 操作系统中,Android 应用可以呈现在可任意调整大小的自由式窗口中。可折叠设备可以有两个大小不同的屏幕,分别可通过折叠或展开设备使用。

  • 窗口大小类别在应用的整个生命周期内可能会发生变化。当应用处于运行状态时,设备更改屏幕方向、进行多任务处理和折叠/展开可能会改变可用的屏幕空间量。因此,窗口大小类别是动态的,应用的界面应相应地调整。

基于 Compose 的应用可以通过 calculateWindowSizeClass() 函数来当前窗口的分类,它使用 material3-window-size-class 库计算 WindowSizeClass,需要添加依赖:

implementation "androidx.compose.material3:material3-window-size-class:1.0.0"

调用示例代码:

import androidx.activity.compose.setContent
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass

class MyActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // 计算Activity当前窗口的窗口大小类别
            // 如果窗口大小改变了,例如当设备旋转时,calculateSizeClass返回的值也会改变。
            val windowSizeClass = calculateWindowSizeClass(this)
            MyApp(windowSizeClass)
        }
    }
}

@Composable
fun MyApp(windowSizeClass: WindowSizeClass) {
    // 根据不同的窗口大小类别,进行不同的视图展示逻辑
    when(windowSizeClass.widthSizeClass) {
        WindowWidthSizeClass.Compact -> Text("当前是 Compact 屏幕")
        WindowWidthSizeClass.Medium -> Text("当前是 Medium 屏幕")
        WindowWidthSizeClass.Expanded -> Text("当前是 Expanded 屏幕")
    }
}

在非 Compose 的应用中,也可以判断窗口大小类别,但是要麻烦一点:

enum class WindowSizeClass { COMPACT, MEDIUM, EXPANDED }

class MainActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // ...

        // Replace with a known container that you can safely add a
        // view to where it won't affect the layout and the view
        // won't be replaced.
        val container: ViewGroup = binding.container

        // Add a utility view to the container to hook into
        // View.onConfigurationChanged. This is required for all
        // activities, even those that don't handle configuration
        // changes. We also can't use Activity.onConfigurationChanged,
        // since there are situations where that won't be called when
        // the configuration changes. View.onConfigurationChanged is
        // called in those scenarios.
        container.addView(object : View(this) {
            override fun onConfigurationChanged(newConfig: Configuration?) {
                super.onConfigurationChanged(newConfig)
                computeWindowSizeClasses()
            }
        })

        computeWindowSizeClasses()
    }
    // 非 Compose 应用中的使用方法
    enum class WindowSize { COMPACT, MEDIUM, EXPANDED }
    private fun computeWindowSizeClasses() {
        val metrics = WindowMetricsCalculator.getOrCreate()
            .computeCurrentWindowMetrics(this)

        val widthDp = metrics.bounds.width() / Resources.getSystem().displayMetrics.density
        val widthWindowSizeClass = when {
            widthDp < 600f -> WindowSize.COMPACT
            widthDp < 840f -> WindowSize.MEDIUM
            else -> WindowSize.EXPANDED
        }

        val heightDp = metrics.bounds.height() / Resources.getSystem().displayMetrics.density
        val heightWindowSizeClass = when {
            heightDp < 480f -> WindowSize.COMPACT
            heightDp < 900f -> WindowSize.MEDIUM
            else -> WindowSize.EXPANDED
        }
        // Use widthWindowSizeClass and heightWindowSizeClass.
    }
}

这是通过 Jetpack WindowManager 库提供的能力,需要单独的添加依赖:

implementation "androidx.window:window:1.0.0"

在应用中观察窗口大小类别之后,就可以开始根据当前的窗口大小类别来改变布局了。

下面是一个简单的例子,它在屏幕的窗口类型是 Compact 的时候展示一个单独的列表,而在其他情况下展示两个并排的列表:

@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
class WindowSizeActivity: ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val windowSizeClass = calculateWindowSizeClass(this)
            WindowSizeExample(windowSizeClass)
        } 
    }
}
@Composable
fun WindowSizeExample(windowSizeClass: WindowSizeClass) {
    if (windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact) {
        ListScreen()
    } else {
        TwoListScreen()
    }
}

@Composable
fun ListScreen() {
    LazyColumn(modifier = Modifier.fillMaxSize()) {
        // List 1
        items(10) {
            SimpleText( "Item $it",Color.Cyan)
        }
        // List 2
        items(10) {
            SimpleText( "Item $it",Color.Magenta)
        }
    }
}

@Composable
fun TwoListScreen() {
    Row {
        LazyColumn(modifier = Modifier.fillMaxWidth().weight(1f)) {
            // List 1
            items(10) {
                SimpleText( "Item $it",Color.Cyan)
            }
        }
        LazyColumn(modifier = Modifier.fillMaxWidth().weight(1f)) {
            items(10) {
                SimpleText( "Item $it",Color.Magenta)
            }
        }
    }
}

@Composable
fun SimpleText(text: String, bgColor: Color = Color.White) {
    Text(
        text = text,
        fontSize = 25.sp,
        modifier = Modifier
            .fillMaxWidth()
            .background(bgColor)
            .padding(16.dp)
    )
}

运行效果:

在这里插入图片描述

以显式方式对屏幕级可组合项布局进行大幅调整

使用 Compose 布置整个应用时,应用级和屏幕级可组合项会占用分配给应用进行渲染的所有空间。在应用设计的这个层面上,可能有必要更改屏幕的整体布局以充分利用屏幕空间。

关键术语

  • 应用级可组合项:单个根可组合项,它会占用分配给应用的所有空间,并包含所有其他可组合项。
  • 屏幕级可组合项:应用级可组合项中包含的一种可组合项,会占用分配给应用的所有空间。在应用中导航时,每个屏幕级可组合项通常代表一个特定目的地。
  • 个别可组合项:所有其他可组合项。可以是各个元素、可重复使用的内容组,或者是在屏幕级可组合项中托管的可组合项。

避免根据物理硬件值来确定布局。您可能会想根据固定的值来确定布局(如设备是平板电脑吗?物理屏幕是否有特定的宽高比?)不过,这些问题的答案对于确定界面可使用的空间可能没什么价值。

在这里插入图片描述

在平板电脑上,应用可能会在多窗口模式下运行,这意味着,该应用可能会与另一个应用分屏显示。在 Chrome 操作系统中,应用可能会位于可调整大小的窗口中。甚至可能会有多个物理屏幕,例如可折叠设备。在所有这些情况下,物理屏幕尺寸都与决定如何显示内容无关。

相反,您应该根据分配给应用的实际屏幕区域来决定如何显示,例如 Jetpack WindowManager 库提供的当前窗口指标或使用 material3-window-size-class 库提供的 WindowSizeClass。您可以将这些 Size 类作为状态进行传递,也可以执行其他逻辑来创建派生状态以传递给嵌套可组合项。

在这里插入图片描述

遵循此方法可提高应用的灵活性,因为它将在以上所有场景中都能正常运行。让布局能够自动适应可用的屏幕空间,也可以减少为支持 Chrome 操作系统等平台以及平板电脑和可折叠设备等设备类型而需要进行的特殊处理。

应该尽量避免下面这样的方式,这些都是在传统 View 体系开发当中非常糟糕的解决方案:

在这里插入图片描述

而应根据窗口大小,将相同的数据相同的基本组件以不同的方式重新展示:

在这里插入图片描述

例如下面的卡片在 Compact 类别的屏幕中显示正常,但是在 MediumExpanded 的类别的屏幕中显示异常,底部文字被截断:

在这里插入图片描述

它的代码可能长下面这样:

在这里插入图片描述

如果我们要对其进行修复的话,首先可以尝试使用的是 Modifier.vertialScroll() 修饰符,它可以使组件自身变得可以纵向滚动,这在横屏模式下表现良好:

在这里插入图片描述

当切换到竖屏时,我们可以为Image组件添加一个weight修饰符,这样当其余内容都显示完毕后,Image会利用剩余的空间完整的显示:

在这里插入图片描述

我们可以有更好的方案。当窗口大小是 Expended 类别的情况下,屏幕有足够的空间来展示所有内容,因此可以将组件并排排列,而无需放在一个 Column 组件中:

在这里插入图片描述

根据不同的具体需求决定采用何种适配方案

在 Compose 中关于“屏幕适配”这件事,有多种方案可以来实现,例如,你可以使用 BoxWithConstraint 组件,也可以使用自定义布局,还可以跟据前面介绍的 calculateWindowSizeClass() 函数来计算 WindowSizeClass 作为可组合屏幕的选择依据。

但是到底应该使用哪一种,可能需要考虑从不同的需求出发点来做出选择:

  • WindowSizeClass:窗口级别的布局改变,倾向于在更大的窗口空间展示更多的内容
  • BoxWithConstraint:展示的内容类型根据组件尺寸而变化,侧重点在内容类型跟随尺寸变化
  • CustomLayout:自定义不同的排列方式,可以是根据宽高尺寸来做决定,但也可以是根据其它条件,在任何情况下都可自主决定。

在这里插入图片描述

折叠屏

可折叠屏幕的设备对于应用开发者来说,主要是能够判断屏幕发生折叠的状态,当折叠状态发生改变的时候去调整布局展示。

在这里插入图片描述

要判断屏幕折叠状态,依然是利用 Jetpack WindowManager 库 提供的能力,可以通过 WindowInfoTracker 的相关 API 来获取。

以下是判断折叠状态的示例代码:

@OptIn(ExperimentalLifecycleComposeApi::class)
class WindowSizeActivity: ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val devicePosture = getDevicePosture().collectAsStateWithLifecycle().value
            PlayerScreen(devicePosture)
        } 
    }
    
    private fun getDevicePosture(): StateFlow<DevicePosture> {
        return WindowInfoTracker.getOrCreate(this)
            .windowLayoutInfo(this)
            .flowWithLifecycle(this.lifecycle)
            .map { windowLayoutInfo ->
                val foldingFeature = windowLayoutInfo.displayFeatures
                    .filterIsInstance<FoldingFeature>().firstOrNull()
                if(foldingFeature != null && isTableTopPosture(foldingFeature)) {
                    DevicePosture.TableTopPosture
                } else {
                    DevicePosture.NormalPosture
                }
            }.stateIn(
                scope = lifecycleScope,
                started = SharingStarted.Eagerly,
                initialValue = DevicePosture.NormalPosture
            )
    }
    
    private fun isTableTopPosture(foldingFeature: FoldingFeature): Boolean {
        return foldingFeature.state == FoldingFeature.State.HALF_OPENED &&
                foldingFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
    }
}
enum class DevicePosture { NormalPosture, TableTopPosture}
@Composable
fun PlayerScreen(devicePosture: DevicePosture) {
    if (devicePosture == DevicePosture.TableTopPosture) {
        PlayerContentTableTop()
    } else {
        PlayerContentRegular()
    }
}

其中 foldingFeature.state == FoldingFeature.State.HALF_OPENED && foldingFeature.orientation == FoldingFeature.Orientation.HORIZONTAL 表示屏幕是半开且屏幕方向是水平方向,即折叠状态。

TwoPane

WindowSizeClass 不只在应用级可组合项可用,在屏幕级可组合项 (某个全屏的Composable)中也可以使用:

在这里插入图片描述

如果是视频播放这样的屏幕级可组合项 ,一般都是固定的模式,屏幕的上半部分显示播放器,屏幕的下半部分显示评论列表等。对于这种场景 Jetpack Compose 提供了一个专门的组件来处理:TwoPane。不过它是在 Accompanist 库中提供的,使用它需要单独添加依赖:

dependencies {
    implementation "com.google.accompanist:accompanist-adaptive:<version>"
}

TwoPane的使用跟 Scaffold 类似,它提供了两个固定的槽位可供填充:

在这里插入图片描述
这两个槽位的默认位置由TwoPaneStrategy驱动,它可以决定将两个槽位水平或垂直排列,并可配置它们之间的间隔。

其中 strategy 参数就是你需要根据 WindowSizeClass 来决定如何显示槽位策略的地方,该库内置了两种策略可供选择:HorizontalTwoPaneStrategyVerticalTwoPaneStrategy,它们允许配置一些偏移量或空间的占位百分比。当没有折叠时,TwoPane 将使用提供的默认策略。它还需要一个displayFeatures 参数,该参数可以通过该库提供的 calculateDisplayFeatures(activity) 来获取。

FlowLayout

下图展示了一种可能出现的场景,在不同尺寸的屏幕中,底部的标签出现了显示不完整的情况。

在这里插入图片描述

它的代码可能长下面这样:

在这里插入图片描述
在这里插入图片描述

使用 FlowLayout 中的 FlowRow 组件可以轻松解决这种场景:

在这里插入图片描述
在这里插入图片描述

由于 FlowLayout 是流式的布局,当一行显示不完整时,它会自动换行,因此它可以完美兼容所有各种尺寸的屏幕,我们甚至都不需要根据WindowSizeClass做什么判断。但是请注意它能解决的问题只是局部的个别可组合项级别,也就是对应的具体的某个元素单元,如果是应用级屏幕级的最好还是需要使用WindowSizeClass来判断。

FlowLayout 也是 Accompanist 库提供的能力之一,该组件的使用示例在之前的文章 Jetpack Compose中的Accompanist 中有提到过,感兴趣的话可以查看,或者可以直接参考官方文档。

BottomNavigation 和 NavigationRail 在不同屏幕尺寸导航

BottomNavigationNavigationRail 这两个都是在 NavHost 使用之外的场景,其中 BottomNavigation 主要用于 Scaffold 脚手架,之前在 Jetpack Compose中的导航路由 中有提到过。

从窗口分类的角度来考虑, BottomNavigation 主要用于 Compact 类型的屏幕:

在这里插入图片描述

NavigationRail 主要用于 MediumExpanded 两种类型的屏幕:

在这里插入图片描述

这两种导航类型的使用方式类似,最终都需要用 NavHost 来包装导航配置:

在这里插入图片描述

为了避免重复的写 NavHost ,可以考虑使用 movableContentOf, 它可以在不同导航之间移动时保持相同的实例状态:

在这里插入图片描述

movableContent lambda 在每次执行时都会保留内部的状态,它能在组合之间移动状态。

在这里插入图片描述

在这里插入图片描述

然后可以根据 WindowSizeClass 的结果选择在 Compat 类型的屏幕展示 BottomBarLayout,而在 MediumExpanded 类型的屏幕展示 NavigationRailLayout

在这里插入图片描述

列表在不同屏幕尺寸导航

对于列表,在 Compact 类型的屏幕,也就是正常手机模式下,一般我们就只有一个列表,但是对于 Expanded 类型的屏幕场景下,情况有所不同:

在这里插入图片描述

Expanded 类型的屏幕下,由于屏幕有足够的空间,所以完全可以同时展示包含列表和详情的 Composable 页面。而在其他情况下只展示列表或者详情二者 Composable 之一即可。

在这里插入图片描述

因此对于列表以及详情视图,在考虑适配不同屏幕尺寸的情况下,总共需要提供三个可组合项,例如:

/* Displays a list of items. */
@Composable
fun ListOfItems(
    onItemSelected: (String) -> Unit,
) { /*...*/ }

/* Displays the detail for an item. */
@Composable
fun ItemDetail(
    selectedItemId: String? = null,
) { /*...*/ }

/* Displays a list and the detail for an item side by side. */
@Composable
fun ListAndDetail(
    selectedItemId: String? = null,
    onItemSelected: (String) -> Unit,
) {
  Row {
    ListOfItems(onItemSelected = onItemSelected)
    ItemDetail(selectedItemId = selectedItemId)
  }
}

ListDetailRoute(导航目的地)决定了要发出三个可组合项中的哪一个:ListAndDetail 适用于较大窗口;ListOfItemsItemDetail 适用于较小窗口,具体取决于是否已选择列表项。

最后 ListDetailRoute需要在包含在 NavHost 中进行配置,例如:

NavHost(navController = navController, startDestination = "listDetailRoute") {
  composable("listDetailRoute") {
    ListDetailRoute(isExpandedWindowSize = isExpandedWindowSize,
                    selectedItemId = selectedItemId)
  }
  /*...*/
}

其中 isExpandedWindowSize 参数可以通过前面提到的计算 WindowSizeClass 相关的API来得到。

selectedItemId 参数可由在所有窗口大小下保留状态的 ViewModel 提供。当用户从列表中选择一项时,selectedItemId 状态变量会更新:

class ListDetailViewModel : ViewModel() {

  data class ListDetailUiState(
      val selectedItemId: String? = null,
  )

  private val viewModelState = MutableStateFlow(ListDetailUiState())

  fun onItemSelected(itemId: String) {
    viewModelState.update {
      it.copy(selectedItemId = itemId)
    }
  }
}

val listDetailViewModel = ListDetailViewModel()

@Composable
fun ListDetailRoute(
    isExpandedWindowSize: Boolean = false,
    selectedItemId: String?,
    onItemSelected: (String) -> Unit = { listDetailViewModel.onItemSelected(it) },
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      onItemSelected = onItemSelected,
      /*...*/
    )
  } else {
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
    } else {
      ListOfItems(
        onItemSelected = onItemSelected,
        /*...*/
      )
    }
  }
}

当列表详情可组合项占据整个应用窗口时,ListDetailRoute 还包含自定义 BackHandler 以便在返回列表时更新 selectedItemId 的状态:

class ListDetailViewModel : ViewModel() {

  data class ListDetailUiState(
      val selectedItemId: String? = null,
  )

  private val viewModelState = MutableStateFlow(ListDetailUiState())

  fun onItemSelected(itemId: String) {
    viewModelState.update {
      it.copy(selectedItemId = itemId)
    }
  }

  fun onItemBackPress() {
    viewModelState.update {
      it.copy(selectedItemId = null)
    }
  }
}

val listDetailViewModel = ListDetailViewModel()

@Composable
fun ListDetailRoute(
    isExpandedWindowSize: Boolean = false,
    selectedItemId: String?,
    onItemSelected: (String) -> Unit = { listDetailViewModel.onItemSelected(it) },
    onItemBackPress: () -> Unit = { listDetailViewModel.onItemBackPress() },
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      onItemSelected = onItemSelected,
      /*...*/
    )
  } else {
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
      BackHandler {
        onItemBackPress()
      }
    } else {
      ListOfItems(
        onItemSelected = onItemSelected,
        /*...*/
      )
    }
  }
}

对于全屏显示的列表详情页面,它可以拥有自己独立的 NavHost 配置:

在这里插入图片描述

这些里面配置的子详情导航页应该只在详情页面中自己使用,如果外部想访问某个子详情页,请使用深度链接(可参考Jetpack Compose中的导航路由)。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/393990.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

swagger关闭/v2/api-docs仍然可以访问漏洞

今天接到安全团队的说swagger有未授权访问漏洞&#xff0c;即使在swagger关闭的情况下http://127.0.0.1:8086/agcloud/v2/api-docs?group%E7%94%A8%E6%88%B7%E5%85%B3%E8%81%94%E4%BF%A1%E6%81%AF%E6%A8%A1%E5%9D%97仍然还能访问。 看了下原来是有写一个拦截器 registry.addI…

图表控件TeeChart for .NET系列教程六:将数据添加到系列中(使用系列)

TeeChart for .NET是优秀的工业4.0 WinForm图表控件&#xff0c;官方独家授权汉化&#xff0c;集功能全面、性能稳定、价格实惠等优势于一体。TeeChart for .NET 中文版还可让您在使用和学习上没有任何语言障碍&#xff0c;至少可以节省30%的开发时间。 TeeChart for .NET最新…

BFD协议原理

BFD协议原理引入背景不使用BFD带来的问题OSPF感知慢VRRP产生次优路径BFD技术简介BFD会话建立方式和检测机制BFD会话建立过程BFD工作流程BFD的单臂回声BFD默认参数以及调整方法总结引入背景 随着网络应用的广泛部署&#xff0c;网络发生中断可能影响业务正常运行并造成重大损失…

Git开发常用指令及其使用场景

目录前言一、Git安装1.1 官网安装二、配置Git工具2.1 用户信息配置2.2 查看配置三、初始化仓库3.1 创建仓库四、常用命令4.1 git clone拉取远端仓库4.2 git分支操作4.2.1 查看分支4.2.2 分支操作4.3 撤回操作4.3.1 撤回删错的分支4.3.2 撤回提交4.3 拉取代码操作4.4 贮存操作五…

ChatGPT也有犯晕的时候

前面测试 ChatGPT 进行写代码、优化代码、解释代码、一般问答都表现的很好。偷个懒&#xff0c;用ChatGPT 帮我写段生物信息代码如果 ChatGPT 给出的的代码不太完善&#xff0c;如何请他一步步改好&#xff1f;代码看不懂&#xff1f;ChatGPT 帮你解释&#xff0c;详细到爆&…

详解Redis的主从同步原理

前言 Redis为了保证服务高可用&#xff0c;其中一种实现就是主从模式&#xff0c;即一个Redis服务端作为主节点&#xff0c;若干个Redis服务端作为主节点的从节点&#xff0c;从而实现即使某个服务端不可用时&#xff0c;也不会影响Redis服务的正常使用。本篇文章将对主从模式…

asp.net网站读取app.config配置内容

目录1、情况说明1.1 使用场景1.2 基本概念2、优化1、情况说明 1.1 使用场景 创建了一个网站&#xff08;项目A&#xff09;&#xff0c;又创建了一个访问数据的项目B。项目A因为是个网站&#xff0c;所有会有web.config文件。项目B是一个读取数据库的类库&#xff0c;会有一个…

小樽C++ 单章④ 字符数组与字符串

目录 一、字符与数组 1.求字符数组的长度 2.查找单词 二、字符串与数组 2.1 字符串倒序输出 2.2 字符串比较 2.3 大写字母输出 ​编辑 三、字符串常用函数 一、初始化字符串&#xff1a; 二、字符串操作&#xff1a;(增删改查) 三、截取与替换字符串 四、替换字符串…

从0开始学python -48

Python MySQL - mysql-connector 驱动 MySQL 是最流行的关系型数据库管理系统&#xff0c;如果你不熟悉 MySQL&#xff0c;可以先学习 MySQL 教程。 本章节我们为大家介绍使用 mysql-connector 来连接使用 MySQL&#xff0c; mysql-connector 是 MySQL 官方提供的驱动器。 我…

DoubleAccumulator 源码详解

DoubleAccumulator 简介 这个类是新增的并发统计工具&#xff0c;可以多线程安全计数。 他的构造方法有两个参数&#xff0c;分别是统计方法和初始值。所以具体的统计是加减乘除是由传入的操作方法决定的。 public DoubleAccumulator(DoubleBinaryOperator accumulatorFunct…

​力扣解法汇总1599. 经营摩天轮的最大利润

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;力扣 描述&#xff1a; 你正在经营一座摩天轮&#xff0c;该摩天轮共有 4 个座舱 &#xff0c;每个座舱…

Kubernetes调度之Pod亲和性

Kubernetes调度中的Pod亲和性abstract.pngPod亲和性节点亲和性&#xff0c;是基于节点的标签对Pod进行调度。而Pod亲和性则可以实现基于已经在节点上运行Pod的标签来约束新Pod可以调度到的节点。具体地&#xff0c;如果X上已经运行了一个或多个满足规则Y的Pod&#xff0c;则这个…

在ubuntu上(docker虚拟环境)部署完laravel的环境后如何运行一个基础的laravel项目

先测试laravel有没有安装成功 laravel如果报laravel command not found&#xff0c;先测试是否安装成功 find / -name laravel出现结果&#xff1a; 说明已经安装成功只是没有配环境变量 要么进这些文件夹测试那个路径下有真的laravel可执行文件&#xff0c;要么每个分别配置…

MCP2515国产替代DP2515带有SPI 接口的独立CAN 控制器

DP2515是一款独立控制器局域网络&#xff08;Controller AreaNetwork&#xff0c; CAN&#xff09;协议控制器&#xff0c;完全支持CAN V2.0B 技术规范。该器件能发送和接收标准和扩展数据帧以及远程帧。DP2515自带的两个验收屏蔽寄存器和六个验收滤波寄存器可以过滤掉不想要的…

图像处理--基于像素层面

python 图像锐化 图像锐化可以使图像的边缘更加清晰&#xff0c;增强图像的细节。常见的图像锐化算法有拉普拉斯算子、Sobel算子、Prewitt算子等。下面是使用拉普拉斯算子实现图像锐化的Python代码&#xff1a; import cv2 import numpy as npdef laplacian_sharpen(img, ksi…

MySQL日志文件

文章目录1.MySQL中的日志文件2.bin log的作用3.redo log的作用4.bin log和redo log的区别&#xff08;1&#xff09;存储的内容&#xff08;2&#xff09;功能&#xff08;3&#xff09;写入时间&#xff08;4&#xff09;写入方式5.两阶段提交6.undo log的作用1.MySQL中的日志…

springcloud3 fegin实现服务调用1

一 Fegin的作用 1.1 fegin的作用 fegin是一个声明式的web服务客户端&#xff0c;让编写web服务器客户端变得非常容易&#xff0c;只需创建一个接口并在接口中添加FeginClients注解即可。 Fegin的使用方式&#xff1a;使用fegin的注解定义接口&#xff0c;调用这个接口&#…

BI软件工具也有ChatGPT

ChatGPT最近大火&#xff0c;朋友圈、聊天群啊到处都在分享它、讨论它。我也凑了个热闹&#xff0c;先和它聊了一下孩子学习上的困惑&#xff0c;然后用它给孩子出了一套易错题型的练习题&#xff0c;缓解了我做为熊孩子家长的压力。ChatGET能做的可不止这些&#xff0c;还能写…

MTK平台开发入门到精通(休眠唤醒篇)休眠唤醒LPM框架

文章目录 一、lpm驱动源码分析二、设备属性调试文件沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇文章将介绍 lpm 驱动源码分析。 mtk 平台下,其默认的 lpm 机制的源码位置:drivers/misc/mediatek/lpm/ 一、lpm驱动源码分析 目录:drivers/misc/mediatek/lpm/…

aardio 编程语言

今天看到一篇文章《独自坚持 17 年&#xff0c;aardio 作者&#xff1a;“因妻子患癌&#xff0c;再无精力维护项目”》&#xff0c;才知道有个这个项目&#xff0c;也算是很有情怀的一个开发者&#xff0c;对程序有着真挚的热忱&#xff0c;aardio &#x1f50a; 专注于桌面软…