前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Jetpack Compose实现的一个优雅的 Toast 组件——简单易用~

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

原创
作者头像
九狼
发布2023-08-06 10:05:59
9020
发布2023-08-06 10:05:59
举报
文章被收录于专栏:Jetpack ComposeJetpack Compose

Toast 是Android中常见的轻量级提示\

本文将介绍如何使用Compose技术实现一个Toast组件

不是一个简单的toast

优雅-简洁-动画 才是我的风格 \

系统原生的Toast默认是在底部弹出,配合kotlin语音的特性,简单封装一下,使用方法非常简洁

代码语言:text
复制
inline fun Context.toast(text: CharSequence) = 

    Toast.makeText(this, text, Toast.LENGTH\_SHORT).show()

在github上看到一个很棒的实现方式,现在要丢弃原生的Toast\

使用Compose组件来实现一个 **优雅-简洁-动画** 的 Toast

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

使用方法

代码语言:text
复制
val toastState = remember { ToastUIState() }

val scope = rememberCoroutineScope()

ToastUI(toastState)

弹出toast

代码语言:text
复制
scope.launch {

    toastState.show("hi")

}

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

效果图

教程

布局

代码语言:text
复制
@Composable

private fun Toast()

    Surface{

        Row {

            Icon()

            Text("hi")

        }

    }

左边**图标** 右边跟着**文本**

显示动画

利用Animatedvisibility可以很轻松实现各种组合动画\

弹出效果 :渐渐**显示**+垂直往下\

消失效果 :渐渐**消失**+垂直往上\

将ToastUI放在AnimatedVisibility组件下即可

代码语言:text
复制
AnimatedVisibility(

    visible = { it },

    modifier = modifier,

    enter = fadeIn() + slideInVertically(),

    exit = fadeOut() + slideOutVertically(),

) {

    ToastUI("hi")

}

内部过度动画

代码语言:text
复制
val progress = remember { Animatable(0f) }

使用动画的函数创建一个浮动值保持器\

定义一个进度值 范围是0f-1f\

接着\

使用Paint绘制一个圆角矩形。

代码语言:text
复制
drawRoundRect(

    color = color,

    size = Size(width = fraction, height = size.height),

    cornerRadius = CornerRadius(6.dp.toPx()),

    alpha = 0.1f,

)

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

代码语言:text
复制
LaunchedEffect(animateDuration) {

    progress.animateTo(

            targetValue = 1f,

            animationSpec = tween(

                durationMillis = 3000 //3秒        

            ),

        )

}

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

接口

代码语言:text
复制
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;

代码语言:text
复制
    @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状态

代码语言:text
复制
@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 触摸暂停 上滑消失

代码语言:text
复制
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。

代码语言:text
复制
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)

}
代码语言:text
复制
Surface(

    modifier = Modifier

          //实现事件监听

        .toastGesturesDetector(onPause, onResume, onDismissed)

) {

    Row {

            Icon()

            Text("hi")

        }

   }

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

定义了5种类型

代码语言:text
复制
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种类型分别定制对应的颜色

代码语言:text
复制
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作为参数 实现动态颜色

代码语言:text
复制
import androidx.compose.animation.core.Animatable

import androidx.compose.animation.core.EaseOut

import androidx.compose.animation.core.calculateTargetValue

import androidx.compose.animation.core.tween

import androidx.compose.animation.splineBasedDecay

import androidx.compose.foundation.gestures.awaitFirstDown

import androidx.compose.foundation.gestures.verticalDrag

import androidx.compose.foundation.layout.Arrangement

import androidx.compose.foundation.layout.Row

import androidx.compose.foundation.layout.fillMaxWidth

import androidx.compose.foundation.layout.offset

import androidx.compose.foundation.layout.padding

import androidx.compose.foundation.layout.size

import androidx.compose.foundation.layout.systemBarsPadding

import androidx.compose.foundation.layout.widthIn

import androidx.compose.foundation.shape.RoundedCornerShape

import androidx.compose.material.icons.Icons

import androidx.compose.material.icons.rounded.Check

import androidx.compose.material.icons.rounded.Info

import androidx.compose.material.icons.rounded.Notifications

import androidx.compose.material.icons.rounded.Warning

import androidx.compose.material3.Icon

import androidx.compose.material3.LocalContentColor

import androidx.compose.material3.Surface

import androidx.compose.runtime.Composable

import androidx.compose.runtime.LaunchedEffect

import androidx.compose.runtime.collectAsState

import androidx.compose.runtime.getValue

import androidx.compose.runtime.key

import androidx.compose.runtime.remember

import androidx.compose.ui.Modifier

import androidx.compose.ui.composed

import androidx.compose.ui.draw.alpha

import androidx.compose.ui.draw.drawBehind

import androidx.compose.ui.geometry.CornerRadius

import androidx.compose.ui.geometry.Size

import androidx.compose.ui.graphics.Color

import androidx.compose.ui.graphics.vector.ImageVector

import androidx.compose.ui.input.pointer.pointerInput

import androidx.compose.ui.input.pointer.positionChange

import androidx.compose.ui.input.pointer.util.VelocityTracker

import androidx.compose.ui.platform.AccessibilityManager

import androidx.compose.ui.unit.IntOffset

import androidx.compose.ui.unit.dp

import com.wordsfairy.note.ui.theme.AppColor

import com.wordsfairy.note.ui.theme.WordsFairyTheme

import com.wordsfairy.note.ui.widgets.Title

import kotlin.math.absoluteValue

import kotlin.math.roundToInt

import kotlinx.coroutines.CoroutineStart

import kotlinx.coroutines.coroutineScope

import kotlinx.coroutines.flow.StateFlow

import kotlinx.coroutines.launch



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)

}

ToastUIState.kt

代码语言:text
复制
import androidx.compose.animation.AnimatedVisibility

import androidx.compose.animation.ExperimentalAnimationApi

import androidx.compose.animation.core.updateTransition

import androidx.compose.animation.fadeIn

import androidx.compose.animation.fadeOut

import androidx.compose.animation.slideInVertically

import androidx.compose.animation.slideOutVertically

import androidx.compose.runtime.Composable

import androidx.compose.runtime.LaunchedEffect

import androidx.compose.runtime.Stable

import androidx.compose.runtime.getValue

import androidx.compose.runtime.key

import androidx.compose.runtime.mutableStateOf

import androidx.compose.runtime.remember

import androidx.compose.runtime.setValue

import androidx.compose.ui.Modifier

import androidx.compose.ui.MotionDurationScale

import androidx.compose.ui.graphics.vector.ImageVector

import androidx.compose.ui.platform.AccessibilityManager

import androidx.compose.ui.platform.LocalAccessibilityManager

import androidx.compose.ui.platform.LocalHapticFeedback

import com.wordsfairy.note.constants.EventBus

import com.wordsfairy.note.ext.flowbus.postEventValue

import com.wordsfairy.note.ui.common.vibration

import kotlin.coroutines.resume

import kotlin.math.roundToLong

import kotlinx.coroutines.CancellableContinuation

import kotlinx.coroutines.cancel

import kotlinx.coroutines.delay

import kotlinx.coroutines.flow.MutableStateFlow

import kotlinx.coroutines.flow.StateFlow

import kotlinx.coroutines.flow.asStateFlow

import kotlinx.coroutines.flow.collectLatest

import kotlinx.coroutines.launch

import kotlinx.coroutines.supervisorScope

import kotlinx.coroutines.suspendCancellableCoroutine

import kotlinx.coroutines.sync.Mutex

import kotlinx.coroutines.sync.withLock

import kotlin.coroutines.CoroutineContext

@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

    }

使用方法

代码语言:text
复制
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))

        }

    }

}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 不是一个简单的toast
    • 使用方法
    • 效果图
      • 教程
        • 布局
        • 显示动画
        • 内部过度动画
        • 接口
        • 接口的实现
        • Toast的UI状态管理
        • Toast 触摸暂停 上滑消失
      • 封装固定类型 比如 : 错误/警告
      • 完整代码
        • Toast.kt
          • ToastUIState.kt
            • 使用方法
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档