Jetpack Compose实现的一个优雅的 Toast 组件——简单易用~

news2024/11/15 19:26:29

Toast 是Android中常见的轻量级提示
本文将介绍如何使用Compose技术实现一个Toast组件

不是一个简单的toast

优雅-简洁-动画 才是我的风格
系统原生的Toast默认是在底部弹出,配合kotlin语音的特性,简单封装一下,使用方法非常简洁

inline fun Context.toast(text: CharSequence) = 
    Toast.makeText(this, text, Toast.LENGTH_SHORT).show()

在github上看到一个很棒的实现方式,现在要丢弃原生的Toast
使用Compose组件来实现一个 优雅-简洁-动画 的 Toast

分享一个 🔥我用Compose写了个笔记App,代码开源~里面用到了这个超级好看的Toast

使用方法

val toastState = remember { ToastUIState() }
val scope = rememberCoroutineScope()
ToastUI(toastState)

弹出toast

scope.launch {
    toastState.show("hi")
}

懒得看的同学 可以直接跳到 源码 和 使用方法处

效果图

请添加图片描述

请添加图片描述

教程

布局

@Composable
private fun Toast()
    Surface{
        Row {
            Icon()
            Text("hi")
        }
    }

左边图标 右边跟着文本

显示动画

利用Animatedvisibility可以很轻松实现各种组合动画
弹出效果 :渐渐显示+垂直往下
消失效果 :渐渐消失+垂直往上
将ToastUI放在AnimatedVisibility组件下即可

AnimatedVisibility(
    visible = { it },
    modifier = modifier,
    enter = fadeIn() + slideInVertically(),
    exit = fadeOut() + slideOutVertically(),
) {
    ToastUI("hi")
}

内部过度动画

val progress = remember { Animatable(0f) }

使用动画的函数创建一个浮动值保持器
定义一个进度值 范围是0f-1f
接着
使用Paint绘制一个圆角矩形。

drawRoundRect(
    color = color,
    size = Size(width = fraction, height = size.height),
    cornerRadius = CornerRadius(6.dp.toPx()),
    alpha = 0.1f,
)

animateTo开始动画 从0f-1f animationSpec设置动画时长

LaunchedEffect(animateDuration) {
    progress.animateTo(
            targetValue = 1f,
            animationSpec = tween(
                durationMillis = 3000 //3秒        
            ),
        )
}

现在UI已经基本实现了,接下来的操作就是为了简洁易用

接口

public interface ToastData {
    public val message: String // 提示文本
    public val icon: ImageVector? //图标 
    public val animationDuration: StateFlow<Int?>//动画时长
}

直接用 material3 提供的图标 ,当然可以用drawable,为了简洁而且 material3 提供的图标基本满足大部分场景的使用
请添加图片描述

接口的实现

ToastData接口的实现ToastDataImpl

主要是启动一个协程监听animationDuration,在经过duration时长后cancel当前协程并隐藏Toast;

    @Stable
    private class ToastDataImpl(
      ...
    ) : ToastData {
      
        override suspend fun run(accessibilityManager: AccessibilityManager?) {
          ...
            supervisorScope {
                launch {
                    animationDuration.collectLatest { duration ->
                        val animationScale = coroutineContext.durationScale
                        started = System.currentTimeMillis()
                            // 关闭动画后,只需显示、等待和隐藏即可。
                            val finalDuration = when (animationScale) {
                                0f -> duration.toLong()
                                else -> (duration.toLong() * animationScale).roundToLong()
                            }
                            delay(finalDuration)
                            this@launch.cancel()
             }
        }
    }
}

Toast的UI状态管理

定义了ToastUIState,用于管理Toast的UI状态

@Stable  
class ToastUIState {

    // 使用mutex锁同步访问currentData,避免并发修改导致的问题;
    private val mutex = Mutex()  
    
    // 存储当前显示的Toast
    public var currentData: ToastData? by mutableStateOf(null)  
        private set 
    
    /**
     *show函数返回一个协程,调用方可以对其进行cancel操作,实现对Toast的生命周期控制
     */
    public suspend fun show(
        message: String,
        icon: ImageVector? = null,
    ): Unit = mutex.withLock {  
        
        // 获取mutex锁
        try {  
           
            // 构建ToastDataImpl并启动协程
            return suspendCancellableCoroutine { cont ->
                currentData = ToastDataImpl(
                    message,
                    icon,
                    cont
                )
            } 
        } 
        // 确保finally块执行,currentData被置空
        finally {  
            currentData = null 
        }
    }
}

Toast 触摸暂停 上滑消失

public interface ToastData {
    public val message: String
    public val icon: ImageVector?
    public val animationDuration: StateFlow<Int?>
    public val type: ToastModel.Type?
    public suspend fun run(accessibilityManager: AccessibilityManager?)
    public fun pause() //暂停
    public fun resume() //重新开始
    public fun dismiss() //开始关闭
    public fun dismissed() //完全关闭
}

这个手势交互检测实现了Toast的滑动消失效果,并在超过一定距离时执行onDismissed回调以完全隐藏Toast。

private fun Modifier.toastGesturesDetector(
    onPause: () -> Unit,
    onResume: () -> Unit,
    onDismissed: () -> Unit,
): Modifier = composed {
   // 记录Toast的Y偏移量
    val offsetY = remember { Animatable(0f) }  
    
    // 记录Toast的透明度
    val alpha = remember { Animatable(1f) }
    // 监听手势事件
    pointerInput(Unit) {
        // 计算偏移量衰变比率
        val decay = splineBasedDecay<Float>(this)  
        coroutineScope {
            while (true) {
                awaitPointerEventScope {
                    // Detect a touch down event.
                    val down = awaitFirstDown()
                    onPause()
                    val pointerId = down.id
                     // 记录手指滑动速度
                    val velocityTracker = VelocityTracker()
                    // Stop any ongoing animation.
                     // 停止任何正在进行的动画
                    launch(start = CoroutineStart.UNDISPATCHED) {
                        offsetY.stop()
                        alpha.stop()
                    }
                     // 监听垂直滑动   
                    verticalDrag(pointerId) { change ->
                        onPause()
                       
                        // 根据滑动事件更新动画值
                        val changeY = (offsetY.value + change.positionChange().y).coerceAtMost(0f)
                        launch {
                            offsetY.snapTo(changeY)
                        }
                        // 重置速度跟踪器
                        if (changeY == 0f) {
                            velocityTracker.resetTracking()
                        } else {
                            velocityTracker.addPosition(
                                change.uptimeMillis,
                                change.position,
                            )
                        }
                    }
                    // 滑动结束,准备启动动画
                    onResume()
                   
                    val velocity = velocityTracker.calculateVelocity().y
                    val targetOffsetY = decay.calculateTargetValue(
                        offsetY.value,
                        velocity,
                    )
                    // 动画结束时停止
                    offsetY.updateBounds(
                        lowerBound = -size.height.toFloat() * 3,
                        upperBound = size.height.toFloat(),
                    )
                    launch {
                        if (velocity >= 0 || targetOffsetY.absoluteValue <= size.height) {
                             // 没有足够的速度; 滑回.
                            offsetY.animateTo(
                                targetValue = 0f,
                                initialVelocity = velocity,
                            )
                        } else {
                            // 被滑走
                            launch { offsetY.animateDecay(velocity, decay) }
                            launch {
                                alpha.animateTo(targetValue = 0f, animationSpec = tween(300))
                                onDismissed()
                            }
                        }
                    }
                }
            }
        }
    }
        .offset {
            IntOffset(0, offsetY.value.roundToInt())
        }
        .alpha(alpha.value)
}
Surface(
    modifier = Modifier
          //实现事件监听
        .toastGesturesDetector(onPause, onResume, onDismissed)
) {
    Row {
            Icon()
            Text("hi")
        }
   }

封装固定类型 比如 : 错误/警告

定义了5种类型

data class ToastModel(
    val message: String,
    val type: Type
){
    enum class Type {
        Normal, Success, Info, Warning, Error,
    }
}
//颜色实体类
private data class ColorData(
    val backgroundColor: Color,
    val textColor: Color,
    val iconColor: Color,
    val icon: ImageVector? = null,
)

根据5种类型分别定制对应的颜色

val colorData = when (toastData.type) {
    ToastModel.Type.Normal -> ColorData(
        backgroundColor = WordsFairyTheme.colors.background,
        textColor = WordsFairyTheme.colors.textPrimary,
        iconColor = WordsFairyTheme.colors.textPrimary,
        icon = Icons.Rounded.Notifications,
    )

    ToastModel.Type.Success -> ColorData(
        backgroundColor = WordsFairyTheme.colors.success,
        textColor = WordsFairyTheme.colors.textWhite,
        iconColor = WordsFairyTheme.colors.textWhite,
        icon = Icons.Rounded.Check,
    )
    ...

    else -> ColorData(
        backgroundColor = WordsFairyTheme.colors.dialogBackground,
        textColor = WordsFairyTheme.colors.textPrimary,
        iconColor = WordsFairyTheme.colors.textPrimary,
        icon = Icons.Rounded.Notifications,
    )
}

完整代码

Toast.kt

在Surface(color = WordsFairyTheme.colors.dialogBackground)
换成你喜欢的AppColor,也可以 定义一个color作为参数 实现动态颜色

ezgif.com-video-to-gif.gif

public interface ToastData {
    public val message: String
    public val icon: ImageVector?
    public val animationDuration: StateFlow<Int?>
    public val type: ToastModel.Type?
    public suspend fun run(accessibilityManager: AccessibilityManager?)
    public fun pause()
    public fun resume()
    public fun dismiss()
    public fun dismissed()
}

data class ToastModel(
    val message: String,
    val type: Type
){
    enum class Type {
        Normal, Success, Info, Warning, Error,
    }
}

private data class ColorData(
    val backgroundColor: Color,
    val textColor: Color,
    val iconColor: Color,
    val icon: ImageVector? = null,
)

@Composable
public fun Toast(
    toastData: ToastData,
) {

    val animateDuration by toastData.animationDuration.collectAsState()

    val colorData = when (toastData.type) {
        ToastModel.Type.Normal -> ColorData(
            backgroundColor = WordsFairyTheme.colors.background,
            textColor = WordsFairyTheme.colors.textPrimary,
            iconColor = WordsFairyTheme.colors.textPrimary,
            icon = Icons.Rounded.Notifications,
        )

        ToastModel.Type.Success -> ColorData(
            backgroundColor = WordsFairyTheme.colors.success,
            textColor = WordsFairyTheme.colors.textWhite,
            iconColor = WordsFairyTheme.colors.textWhite,
            icon = Icons.Rounded.Check,
        )

        ToastModel.Type.Info -> ColorData(
            backgroundColor = WordsFairyTheme.colors.info,
            textColor = WordsFairyTheme.colors.textWhite,
            iconColor = WordsFairyTheme.colors.textWhite,
            icon = Icons.Rounded.Info,

            )

        ToastModel.Type.Warning -> ColorData(
            backgroundColor = AppColor.warning,
            textColor = WordsFairyTheme.colors.textWhite,
            iconColor = WordsFairyTheme.colors.textWhite,
            icon = Icons.Rounded.Warning,

            )

        ToastModel.Type.Error -> ColorData(
            backgroundColor = WordsFairyTheme.colors.error,
            textColor = WordsFairyTheme.colors.textWhite,
            iconColor = WordsFairyTheme.colors.textWhite,
            icon = Icons.Rounded.Warning,
        )

        else -> ColorData(
            backgroundColor = WordsFairyTheme.colors.dialogBackground,
            textColor = WordsFairyTheme.colors.textPrimary,
            iconColor = WordsFairyTheme.colors.textPrimary,
            icon = Icons.Rounded.Notifications,
        )
    }
    val icon = toastData.icon ?: colorData.icon
    key(toastData) {
        Toast(
            message = toastData.message,
            icon = icon,
            backgroundColor = colorData.backgroundColor,
            iconColor = colorData.iconColor,
            textColor = colorData.textColor,
            animateDuration = animateDuration,
            onPause = toastData::pause,
            onResume = toastData::resume,
            onDismissed = toastData::dismissed,
        )

    }
}

@Composable
private fun Toast(
    message: String,
    icon: ImageVector?,
    backgroundColor: Color,
    iconColor: Color,
    textColor: Color,
    animateDuration: Int? = 0,
    onPause: () -> Unit = {},
    onResume: () -> Unit = {},
    onDismissed: () -> Unit = {},
) {
    val roundedValue = 26.dp
    Surface(
        modifier = Modifier
            .systemBarsPadding()
            .padding(8.dp)
            .widthIn(max = 520.dp)
            .fillMaxWidth()
            .toastGesturesDetector(onPause, onResume, onDismissed),
        color = backgroundColor,
        shape = RoundedCornerShape(roundedValue),
        tonalElevation = 2.dp,
    ) {
        val progress = remember { Animatable(0f) }
        LaunchedEffect(animateDuration) {
            // Do not run animation when animations are turned off.

            if (coroutineContext.durationScale == 0f) return@LaunchedEffect

            if (animateDuration == null) {
                progress.stop()
            } else {
                progress.animateTo(
                    targetValue = 1f,
                    animationSpec = tween(
                        durationMillis = animateDuration,
                        easing = EaseOut,
                    ),
                )
            }
        }

        val color = LocalContentColor.current
        Row(
            Modifier
                .drawBehind {
                    val fraction = progress.value * size.width
                    drawRoundRect(
                        color = color,
                        size = Size(width = fraction, height = size.height),
                        cornerRadius = CornerRadius(roundedValue.toPx()),
                        alpha = 0.1f,
                    )
                }
                .padding(12.dp),
            horizontalArrangement = Arrangement.spacedBy(8.dp),
        ) {
            if (icon != null) {
                Icon(
                    icon,
                    contentDescription = null,
                    Modifier.size(24.dp),
                    tint = iconColor
                )
            }
            Title(message, color = textColor)
        }
    }
}

private fun Modifier.toastGesturesDetector(
    onPause: () -> Unit,
    onResume: () -> Unit,
    onDismissed: () -> Unit,
): Modifier = composed {
    val offsetY = remember { Animatable(0f) }
    val alpha = remember { Animatable(1f) }

    pointerInput(Unit) {
        val decay = splineBasedDecay<Float>(this)
        coroutineScope {
            while (true) {
                awaitPointerEventScope {
                    // Detect a touch down event.
                    val down = awaitFirstDown()
                    onPause()
                    val pointerId = down.id

                    val velocityTracker = VelocityTracker()
                    // Stop any ongoing animation.
                    launch(start = CoroutineStart.UNDISPATCHED) {
                        offsetY.stop()
                        alpha.stop()
                    }

                    verticalDrag(pointerId) { change ->
                        onPause()
                        // Update the animation value with touch events.
                        val changeY = (offsetY.value + change.positionChange().y).coerceAtMost(0f)
                        launch {
                            offsetY.snapTo(changeY)
                        }
                        if (changeY == 0f) {
                            velocityTracker.resetTracking()
                        } else {
                            velocityTracker.addPosition(
                                change.uptimeMillis,
                                change.position,
                            )
                        }
                    }

                    onResume()
                    // No longer receiving touch events. Prepare the animation.
                    val velocity = velocityTracker.calculateVelocity().y
                    val targetOffsetY = decay.calculateTargetValue(
                        offsetY.value,
                        velocity,
                    )
                    // The animation stops when it reaches the bounds.
                    offsetY.updateBounds(
                        lowerBound = -size.height.toFloat() * 3,
                        upperBound = size.height.toFloat(),
                    )
                    launch {
                        if (velocity >= 0 || targetOffsetY.absoluteValue <= size.height) {
                            // Not enough velocity; Slide back.
                            offsetY.animateTo(
                                targetValue = 0f,
                                initialVelocity = velocity,
                            )
                        } else {
                            // The element was swiped away.
                            launch { offsetY.animateDecay(velocity, decay) }
                            launch {
                                alpha.animateTo(targetValue = 0f, animationSpec = tween(300))
                                onDismissed()
                            }
                        }
                    }
                }
            }
        }
    }
        .offset {
            IntOffset(0, offsetY.value.roundToInt())
        }
        .alpha(alpha.value)
}

ToastUI.kt

@Stable
class ToastUIState {
    private val mutex = Mutex()

    public var currentData: ToastData? by mutableStateOf(null)
        private set

    public suspend fun show(
        message: String,
        icon: ImageVector? = null,
    ): Unit = mutex.withLock {
        try {
            return suspendCancellableCoroutine { cont ->
                currentData = ToastDataImpl(
                    message,
                    icon,
                    cont,
                )
            }
        } finally {
            currentData = null
        }
    }

    public suspend fun show(
        toastModel: ToastModel
    ): Unit = mutex.withLock {
        try {
            return suspendCancellableCoroutine { cont ->
                currentData = ToastDataImpl(
                    toastModel.message,
                    null,
                    cont,
                    toastModel.type
                )
            }
        } finally {
            currentData = null
        }
    }



    @Stable
    private class ToastDataImpl(
        override val message: String,
        override val icon: ImageVector?,
        private val continuation: CancellableContinuation<Unit>,
        override val type: ToastModel.Type? = ToastModel.Type.Normal,
    ) : ToastData {
        private var elapsed = 0L
        private var started = 0L
        private var duration = 0L
        private val _state = MutableStateFlow<Int?>(null)
        override val animationDuration: StateFlow<Int?> = _state.asStateFlow()

        override suspend fun run(accessibilityManager: AccessibilityManager?) {
            duration = durationTimeout(
                hasIcon = icon != null,
                accessibilityManager = accessibilityManager,
            )

            // Accessibility decided to show forever
            // Let's await explicit dismiss, do not run animation.
            if (duration == Long.MAX_VALUE) {
                delay(duration)
                return
            }

            resume()
            supervisorScope {
                launch {
                    animationDuration.collectLatest { duration ->
                        val animationScale = coroutineContext.durationScale
                        if (duration != null) {
                            started = System.currentTimeMillis()
                            // 关闭动画后,只需显示、等待和隐藏即可。
                            val finalDuration = when (animationScale) {
                                0f -> duration.toLong()
                                else -> (duration.toLong() * animationScale).roundToLong()
                            }
                            delay(finalDuration)
                            this@launch.cancel()
                        } else {
                            elapsed += System.currentTimeMillis() - started
                            delay(Long.MAX_VALUE)
                        }
                    }
                }
            }
        }

        override fun pause() {
            _state.value = null
        }

        override fun resume() {
            val remains = (duration - elapsed).toInt()
            if (remains > 0) {
                _state.value = remains
            } else {
                dismiss()
            }
        }

        override fun dismiss() {
            _state.value = 0
        }

        override fun dismissed() {
            if (continuation.isActive) {
                continuation.resume(Unit)
            }
        }
    }

}

@OptIn(ExperimentalAnimationApi::class)
@Composable
public fun ToastUI(
    hostState: ToastUIState,
    modifier: Modifier = Modifier,
    toast: @Composable (ToastData) -> Unit = { Toast(it) },
) {
    val accessibilityManager = LocalAccessibilityManager.current
    val currentData = hostState.currentData ?: return
    //震动
    val feedback = LocalHapticFeedback.current
    key(currentData) {
        var state by remember { mutableStateOf(false) }
        val transition = updateTransition(targetState = state, label = "toast")

        LaunchedEffect(Unit) {
            state = true
            currentData.run(accessibilityManager)
            state = false
            feedback.vibration()
        }

        transition.AnimatedVisibility(
            visible = { it },
            modifier = modifier,
            enter = fadeIn() + slideInVertically(),
            exit = fadeOut() + slideOutVertically(),
        ) {
            toast(currentData)
        }

        // Await dismiss animation and dismiss the Toast completely.
        // This animation workaround instead of nulling the toast data is to prevent
        // relaunching another Toast when the dismiss animation has not completed yet.
        LaunchedEffect(state, transition.currentState, transition.isRunning) {
            if (!state && !transition.currentState && !transition.isRunning) {
                currentData.dismissed()
                feedback.vibration()

            }
        }
    }
}

internal fun durationTimeout(
    hasIcon: Boolean,
    accessibilityManager: AccessibilityManager?,
): Long {
    val timeout = 3000L
    if (accessibilityManager == null) return timeout
    return accessibilityManager.calculateRecommendedTimeoutMillis(
        originalTimeoutMillis = timeout,
        containsIcons = hasIcon,
        containsText = true,
        containsControls = false,
    )
}

internal val CoroutineContext.durationScale: Float
    get() {
        val scale = this[MotionDurationScale]?.scaleFactor ?: 1f
        check(scale >= 0f)
        return scale
    }

使用方法

val toastState = remember { ToastUIState() }
val scope = rememberCoroutineScope()
Column {
    ToastUI(toastState)

    Button() {
        scope.launch {
            toastState.show("hi") //纯文本
            toastState.show("hi",Icons.Rounded.Notifications) //带图标 
            toastState.show(ToastModel(ToastModel("hi", ToastModel.Type.Normal))
            toastState.show(ToastModel(ToastModel("hi", ToastModel.Type.Success))
            toastState.show(ToastModel(ToastModel("hi", ToastModel.Type.Info))
        }
    }
}

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

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

相关文章

CodeForces..好数列.[简单].[数学规律]

题目描述&#xff1a; 题目解读&#xff1a; 给定n&#xff0c;k 数组a是由0和1组成的&#xff0c;长度为n的数组&#xff0c;数组满足&#xff1a; a的前i个元素中至少有i/k个等于1&#xff1b; a的后i个元素中至少有i/k个等于1&#xff1b; 输入n,k 输出满足条件的数组的…

【JAVA】学生信息管理系统

目录 前言 一、环境搭建 二、功能实现 1.学生信息类的创建 2.学生信息的添加功能 3.学生信息的删除功能 4.学生信息的修改功能 5.学生信息的查看功能 三、主类的调用 1.界面的搭建 2.学生端和教师端 3.系统和功能的选择 总结 前言 JAVA实现的学生信息管理…

8.6 socket套接字及TCP的实现框架

socket套接字 目录 socket套接字 体系结构的两种形式 几种常见的网络编程接口 socket套接字 socket常用API介绍 socket套接字 三元组【IP地址&#xff0c;端口&#xff0c;协议】 地址族结构体 套接字类型 TCP通信的实现过程 体系结构的两种形式 网络的体系结构 (N…

保护云环境:云渗透测试和安全策略探究

随着云计算技术的快速发展&#xff0c;越来越多的组织将他们的数据和应用程序迁移到云端。然而&#xff0c;与此同时&#xff0c;云安全也面临着新的挑战。云渗透测试是一种评估云环境安全性的方法&#xff0c;它帮助组织发现并解决可能存在的漏洞和弱点。在本文中&#xff0c;…

docker - 部署java/python项目

目录 1、docker - 部署 java 项目 1. 创建 mysql 容器 2. 验证mysql&#xff0c;dbserver 连接 mysql &#xff0c;服务器ip:3306 ,账户&#xff1a;root 密码&#xff1a;123456 3. 创建tomcat容器 4. 验证&#xff1a;http://ip:80 5. 开发项目war包&#xff0c;放到to…

【MOOC 作业】第2章 应用层

不是标答也不是参考答案 仅从个人理解出发去做题 1、(20分) 什么是持久性连接&#xff1f;什么是非持久性连接&#xff1f;持久性连接与非持久性连接区别如何&#xff1f; 持久性连接&#xff1a;每个请求/响应对都经同一个 TCP 连接发送。非持久性连接&#xff1a;每个请求/…

【1401. 圆和矩形是否有重叠】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个以 (radius, xCenter, yCenter) 表示的圆和一个与坐标轴平行的矩形 (x1, y1, x2, y2) &#xff0c;其中 (x1, y1) 是矩形左下角的坐标&#xff0c;而 (x2, y2) 是右上角的坐标。 如果圆和矩…

灰狼优化算法GWO,优化VMD,适应度函数为最小样本熵或最小包络熵(可自行选择,代码已集成好,很方便修改)包含MATLAB源代码

近期评论区有小伙伴私信需要灰狼优化算法GWO法化VMD的&#xff0c;所以打算再写一篇。 与之前的文章不同&#xff0c;这篇文章作者考虑到&#xff0c;大家有可能会以最小样本熵或最小包络熵为适应度函数的&#xff0c;在这个程序中将会直接把样本熵和包络熵集成&#xff0c;在…

了解Facebook的算法如何工作,提高您的帖子曝光率

Facebook是全球最大的社交媒体平台之一&#xff0c;每天有数亿用户在上面发布内容、互动交流。然而&#xff0c;由于用户数量巨大&#xff0c;每天产生的信息量也是相当惊人的。 为了让用户看到最有用和最相关的内容&#xff0c;Facebook采用了一种复杂的算法来过滤和排序用户…

迅为RK3568开发板Buildroot 系统设置状态栏

Weston 支持在 weston.ini 配置文件的 shell 段设置状态栏的背景色、位置,以及在 launcher 段设置快捷启动程序&#xff0c;修改文件系统的/etc/xdg/weston/weston.ini 文件&#xff0c;如下所示&#xff1a; [shell] # 颜色格式为 ARGB8888 panel-color0xff002244 # top|…

【新星计划回顾】第八篇学习-多表联表查询(完结)

&#x1f3c6;&#x1f3c6;时间过的真快&#xff0c;这是导师回顾新星计划学习的第八篇文章&#xff01; 在学习过程中&#xff0c;学员们也咨询了很多问题&#xff0c;我把一些问题整理以文章形式总结分享下。 最近这段时间非常忙&#xff0c;虽然导师首次参与新星计划活动已…

[内核笔记1]内核文件结构与缓存——inode和对应描述

由来&#xff1a;公司内部外网记录日志的方式现在都是通过Nginx模块收到数据发送到系统消息队列&#xff0c;然后由另外一个进程来从消息队列读取然后写回磁盘这样的操作&#xff0c;尽量的减少Nginx的阻塞。 但是由于System/V消息队列在使用前需要规定消息长度&#xff0c;且…

从古欧洲的四大族群变迁梳理欧洲两千年历史脉络

我们看今天的欧洲&#xff0c;不难发现有几种完全不同的生活方式和行为习惯&#xff0c;以北欧三国再加英德与荷兰等国人为代表&#xff0c;他们信奉新教&#xff0c;勤劳&#xff0c;做事一丝不苟&#xff0c;颇为绅士&#xff0c;所以瑞典、英国、德国创造出了极其辉煌的工业…

canvas判断画笔是否铺满区域

判断画布形状是否填满和百分比 <body> <canvas id"myCanvas" width"300" height"150" style"border:1px solid #d3d3d3;"> </canvas> <script> var cdocument.getElementById("myCanvas"); var ctx…

智慧水务调度系统-智慧水务大数据可视化管理系统

平台概述 智慧水务调度系统平台&#xff08;智慧水务大数据可视化管理系统平台&#xff09;是以物联感知技术、大数据、智能控制、云计算、人工智能、数字孪生、AI算法、虚拟现实技术为核心&#xff0c;以监测仪表、通讯网络、数据库系统、数据中台、模型软件、前台展示、智慧…

数据库模型设计案例分享(GaussDB版)

目录 一、前言 二、应用系统数据库设计的基本流程 三、数据库模型设计 1、概念结构设计阶段 2、逻辑结构设计阶段 3、物理结构设计阶段 四、小结 一、前言 GaussDB数据库是一款企业级分布式数据库&#xff0c;支持集中式和分布式两种部署形态。它面向金融互联网交易和政…

面试官:说下简历中提到的优化……

作者&#xff1a;张力尹 先来点废话&#xff1a; 听说最近 Android 岗位变多了&#xff0c;你去面试了么&#xff1f; 面试官&#xff1a;你简历中提到了卡顿优化&#xff0c;做了哪些优化呢&#xff0c;展开说说。 你&#xff1a;哦&#xff0c;脑子飞速闪过网上的文章&#x…

Python模块pytest全局初始化和局部初始化前置后置操作

在 pytest 中&#xff0c;我们可以通过 conftest.py 文件来实现全局的前置和后置操作。 conftest.py 文件是一个特殊的 pytest 文件&#xff0c;它可以定义一些 fixture 和钩子函数&#xff0c;这些 fixture 和钩子函数可以在当前目录及其子目录下的所有测试文件中使用。 下面…

VR全景如何制作,可以用在哪些领域?

引言&#xff1a; 虚拟现实&#xff08;VR&#xff09;全景制作正在以惊人的速度改变着我们的感官体验。通过VR全景技术&#xff0c;我们可以身临其境地探索虚拟世界&#xff0c;感受前所未有的沉浸式体验。 一&#xff0e;如何进行VR全景制作 1.什么是VR全景制作&#xff1f…

【雕爷学编程】Arduino动手做(127)---2004A LCD液晶屏模块

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…