欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Kotlin Compose Button 實現(xiàn)長按監(jiān)聽并實現(xiàn)動畫效果(完整代碼)

 更新時間:2025年05月23日 15:06:25   作者:zimoyin  
想要實現(xiàn)長按按鈕開始錄音,松開發(fā)送的功能,因此為了實現(xiàn)這些功能就需要自己寫一個 Button 來解決問題,下面小編給大家分享Kotlin Compose Button 實現(xiàn)長按監(jiān)聽并實現(xiàn)動畫效果(完整代碼),感興趣的朋友一起看看吧

想要實現(xiàn)長按按鈕開始錄音,松開發(fā)送的功能。發(fā)現(xiàn) Button 這個控件如果去監(jiān)聽這些按下,松開,長按等事件,發(fā)現(xiàn)是不會觸發(fā)的,究其原因是 Button 已經(jīng)提前消耗了這些事件所以導(dǎo)致,這些監(jiān)聽無法被觸發(fā)。因此為了實現(xiàn)這些功能就需要自己寫一個 Button 來解決問題。

Button 實現(xiàn)原理

在 Jetpack Compose 中,Button 是一個高度封裝的可組合函數(shù)(Composable),其底層是由多個 UI 組件組合而成,關(guān)鍵組成包括:Surface、Text、Row、InteractionSource 等

源碼

@Composable
fun Button(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    shape: Shape = ButtonDefaults.shape,
    colors: ButtonColors = ButtonDefaults.buttonColors(),
    elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
    border: BorderStroke? = null,
    contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
    interactionSource: MutableInteractionSource? = null,
    content: @Composable RowScope.() -> Unit
) {
    @Suppress("NAME_SHADOWING")
    val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
    val containerColor = colors.containerColor(enabled)
    val contentColor = colors.contentColor(enabled)
    val shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp
    Surface(
        onClick = onClick,
        modifier = modifier.semantics { role = Role.Button },
        enabled = enabled,
        shape = shape,
        color = containerColor,
        contentColor = contentColor,
        shadowElevation = shadowElevation,
        border = border,
        interactionSource = interactionSource
    ) {
        ProvideContentColorTextStyle(
            contentColor = contentColor,
            textStyle = MaterialTheme.typography.labelLarge
        ) {
            Row(
                Modifier.defaultMinSize(
                        minWidth = ButtonDefaults.MinWidth,
                        minHeight = ButtonDefaults.MinHeight
                    )
                    .padding(contentPadding),
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically,
                content = content
            )
        }
    }
}

1. Surface 的作用(關(guān)鍵)

Surface 是 Compose 中的通用容器,負責(zé):

  • 提供背景顏色(來自 ButtonColors)
  • 提供 elevation(陰影)
  • 提供點擊行為(通過 onClick)
  • 提供 shape(圓角、裁剪等)
  • 提供 ripple 效果(內(nèi)部自動通過 indication 使用 rememberRipple())
  • 使用 Modifier.clickable 實現(xiàn)交互響應(yīng)

注意:幾乎所有 Material 組件都會使用 Surface 來包裹內(nèi)容,統(tǒng)一管理視覺表現(xiàn)。

2. InteractionSource

  • InteractionSource 是 Compose 中管理用戶交互狀態(tài)的機制(如 pressed、hoveredfocused
  • Button 將其傳入 Surface,用于響應(yīng)和處理 ripple 動畫等
  • MutableInteractionSource 配合,可以觀察組件的狀態(tài)變化

3. ButtonDefaults

ButtonDefaults 是提供默認值的工具類,包含:

  • elevation():返回 ButtonElevation 對象,用于設(shè)置不同狀態(tài)下的陰影高度
  • buttonColors():返回 ButtonColors 對象,用于設(shè)置正常 / 禁用狀態(tài)下的背景與文字顏色
  • ContentPadding:內(nèi)容的默認內(nèi)邊距 4. Content Slot(RowScope.() -> Unit

4. Content Slot(RowScope.() -> Unit)

Buttoncontent 是一個 RowScope 的 lambda,允許你自由組合子組件,如:

Button(onClick = { }) {
    Icon(imageVector = Icons.Default.Add, contentDescription = null)
    Spacer(modifier = Modifier.width(4.dp))
    Text("添加")
}

因為是在 RowScope 中,所以能用 Spacer 等布局函數(shù)在水平排列子項。

關(guān)鍵點說明
Surface提供背景、陰影、圓角、點擊、ripple 效果的統(tǒng)一封裝
InteractionSource用于收集用戶交互狀態(tài)(點擊、懸停等)
ButtonDefaults提供默認顏色、陰影、Padding 等參數(shù)
Row + Text內(nèi)容布局,允許圖標(biāo) + 文本靈活組合
Modifier控制尺寸、形狀、邊距、點擊響應(yīng)等

如果想自定義 Button 的樣式,也可以直接使用 Surface + Row 自己實現(xiàn)一個“按鈕”,只需照著官方的做法組裝即可。

@Suppress("DEPRECATION_ERROR")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Button(
    onClick: () -> Unit = {},
    onLongPress: () -> Unit = {},
    onPressed: () -> Unit = {},
    onReleased: () -> Unit = {},
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    shape: Shape = ButtonDefaults.shape,
    colors: ButtonColors = ButtonDefaults.buttonColors(),
    border: BorderStroke? = null,
    shadowElevation: Dp = 0.dp,
    contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
    content: @Composable RowScope.() -> Unit = { Text("LongButton") }
) {
    val containerColor = colors.containerColor
    val contentColor = colors.contentColor
    Surface(
        modifier = modifier
            .minimumInteractiveComponentSize()
            .pointerInput(enabled) {
                detectTapGestures(
                    onPress = { offset ->
                        onPressed()
                        tryAwaitRelease()
                        onReleased()
                    },
                    onTap = { onClick() },
                    onLongPress = { onLongPress() }
                )
            }
            .semantics { role = Role.Button },
        shape = shape,
        color = containerColor,
        contentColor = contentColor,
        shadowElevation = shadowElevation,
        border = border,
    ) {
        CompositionLocalProvider(
            LocalContentColor provides contentColor,
            LocalTextStyle provides LocalTextStyle.current.merge(MaterialTheme.typography.labelLarge),
        ) {
            Row(
                Modifier
                    .defaultMinSize(ButtonDefaults.MinWidth, ButtonDefaults.MinHeight)
                    .padding(contentPadding),
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically,
                content = content
            )
        }
    }
}

Button 的動畫實現(xiàn)

為了讓按鈕在按下時提供自然的視覺反饋,Compose 通常會使用狀態(tài)驅(qū)動的動畫。最常見的方式是通過 animateColorAsState 來實現(xiàn)顏色的平滑過渡,比如按鈕被按下時背景色或文字顏色稍微變暗,松開時再恢復(fù)。

這個動畫實現(xiàn)的關(guān)鍵點在于:

  • 交互狀態(tài):比如是否按下、是否禁用,可以通過 InteractionSource 結(jié)合 collectIsPressedAsState() 實時監(jiān)聽當(dāng)前狀態(tài)。
  • 根據(jù)狀態(tài)決定目標(biāo)顏色:當(dāng)狀態(tài)變化時(如按下 -> 松開),我們會設(shè)置新的目標(biāo)顏色。
  • 使用動畫驅(qū)動狀態(tài)變化:通過 animateColorAsState() 把顏色變化變成帶過渡效果的狀態(tài)變化,而不是突變。

這種方式符合 Compose 的聲明式編程模型,不需要手動寫動畫過程,而是讓狀態(tài)驅(qū)動 UI 動畫。

下面是按鈕顏色動畫部分的代碼片段,只展示相關(guān)的狀態(tài)監(jiān)聽和動畫邏輯,具體如何應(yīng)用在 UI 中將在后續(xù)實現(xiàn):

@Composable
fun AnimatedButtonColors(
    enabled: Boolean,
    interactionSource: InteractionSource,
    defaultContainerColor: Color,
    pressedContainerColor: Color,
    disabledContainerColor: Color
): State<Color> {
    val isPressed by interactionSource.collectIsPressedAsState()
    val targetColor = when {
        !enabled -> disabledContainerColor
        isPressed -> pressedContainerColor
        else -> defaultContainerColor
    }
    // 返回一個狀態(tài)驅(qū)動的動畫顏色
    val animatedColor by animateColorAsState(targetColor, label = "containerColorAnimation")
    return rememberUpdatedState(animatedColor)
}

值得一提的是,Button 使用的動畫類型為 ripple (漣漪效果)

這段代碼僅負責(zé)計算當(dāng)前的按鈕背景色,并通過動畫使其平滑過渡。它不會直接控制按鈕的點擊或布局邏輯,而是為最終的 UI 提供一個可動畫的顏色狀態(tài)。

后續(xù)可以將這個 animatedColor 應(yīng)用于 Surface 或背景 Modifier 上,完成整體的按鈕外觀動畫。

完整動畫代碼

// 1. 確保 interactionSource 不為空
val interaction = interactionSource ?: remember { MutableInteractionSource() }
// 2. 監(jiān)聽按下狀態(tài)
val isPressed by interaction.collectIsPressedAsState()
// 4. 按狀態(tài)選 target 值
val defaultContainerColor = colors.containerColor
val disabledContainerColor = colors.disabledContainerColor
val defaultContentColor = colors.contentColor
val disabledContentColor = colors.disabledContentColor
val targetContainerColor = when {
    !enabled -> disabledContainerColor
    isPressed -> defaultContainerColor.copy(alpha = 0.85f)
    else -> defaultContainerColor
}
val targetContentColor = when {
    !enabled -> disabledContentColor
    isPressed -> defaultContentColor.copy(alpha = 0.9f)
    else -> defaultContentColor
}
// 5. 動畫
val containerColorAni by animateColorAsState(targetContainerColor, label = "containerColor")
val contentColorAni by animateColorAsState(targetContentColor, label = "contentColor")
// 漣漪效果
// 根據(jù)當(dāng)前環(huán)境選擇是否使用新版 Material3 的 ripple(),還是退回到老版的 rememberRipple() 實現(xiàn)
val ripple = if (LocalUseFallbackRippleImplementation.current) {
    rememberRipple(true, Dp.Unspecified, Color.Unspecified)
} else {
    ripple(true, Dp.Unspecified, Color.Unspecified)
}
// 6. Surface + 手動發(fā) PressInteraction
Surface(
    modifier = modifier
        .minimumInteractiveComponentSize()
        .pointerInput(enabled) {
            detectTapGestures(
                onPress = { offset ->
                    // 發(fā)起 PressInteraction,供 collectIsPressedAsState 監(jiān)聽
                    val press = PressInteraction.Press(offset)
                    val scope = CoroutineScope(coroutineContext)
                    scope.launch {
                        interaction.emit(press)
                    }
                    // 用戶 onPressed
                    onPressed()
                    // 等待手指抬起或取消
                    tryAwaitRelease()
                    // 發(fā) ReleaseInteraction
                    scope.launch {
                        interaction.emit(PressInteraction.Release(press))
                    }
                    // 用戶 onReleased
                    onReleased()
                },
                onTap = { onClick() },
                onLongPress = { onLongPress() }
            )
        }
        .indication(interaction, ripple)
        .semantics { role = Role.Button },
    shape = shape,
    color = containerColorAni,
    contentColor = contentColorAni,
    shadowElevation = shadowElevation,
    border = border,
) {...}

這個 Button 的動畫部分主要體現(xiàn)在按下狀態(tài)下的顏色過渡。它通過 animateColorAsState 來實現(xiàn)背景色和文字顏色的動態(tài)變化。

當(dāng)按鈕被按下時,會使用 interaction.collectIsPressedAsState() 實時監(jiān)聽是否處于 Pressed 狀態(tài),進而動態(tài)計算目標(biāo)顏色(targetContainerColortargetContentColor)。按下狀態(tài)下顏色會降低透明度(背景 alpha = 0.85,文字 alpha = 0.9),形成按壓視覺反饋。

顏色的漸變不是突變的,而是帶有過渡動畫,由 animateColorAsState 自動驅(qū)動。它會在目標(biāo)顏色發(fā)生變化時,通過內(nèi)部的動畫插值器平滑過渡到目標(biāo)值,用戶無需手動控制動畫過程。

使用 by animateColorAsState(...) 得到的是 State<Color> 類型的值,它會在顏色變化時自動重組,使整個按鈕在交互中呈現(xiàn)更自然的過渡效果。

這種方式相比傳統(tǒng)手動實現(xiàn)動畫更簡潔、聲明性更強,也更容易和 Compose 的狀態(tài)系統(tǒng)集成。

完整代碼

// androidx.compose.material3: 1.3.0
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.LocalUseFallbackRippleImplementation
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.coroutines.coroutineContext
@Suppress("DEPRECATION_ERROR")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Button(
    onClick: () -> Unit = {},
    onLongPress: () -> Unit = {},
    onPressed: () -> Unit = {},
    onReleased: () -> Unit = {},
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    shape: Shape = ButtonDefaults.shape,
    colors: ButtonColors = ButtonDefaults.buttonColors(),
    border: BorderStroke? = null,
    shadowElevation: Dp = 0.dp,
    contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
    interactionSource: MutableInteractionSource? = null,
    content: @Composable RowScope.() -> Unit = { Text("LongButton") }
) {
    // 1. 確保 interactionSource 不為空
    val interaction = interactionSource ?: remember { MutableInteractionSource() }
    // 2. 監(jiān)聽按下狀態(tài)
    val isPressed by interaction.collectIsPressedAsState()
    // 4. 按狀態(tài)選 target 值
    val defaultContainerColor = colors.containerColor
    val disabledContainerColor = colors.disabledContainerColor
    val defaultContentColor = colors.contentColor
    val disabledContentColor = colors.disabledContentColor
    val targetContainerColor = when {
        !enabled -> disabledContainerColor
        isPressed -> defaultContainerColor.copy(alpha = 0.85f)
        else -> defaultContainerColor
    }
    val targetContentColor = when {
        !enabled -> disabledContentColor
        isPressed -> defaultContentColor.copy(alpha = 0.9f)
        else -> defaultContentColor
    }
    // 5. 動畫
    val containerColorAni by animateColorAsState(targetContainerColor, label = "containerColor")
    val contentColorAni by animateColorAsState(targetContentColor, label = "contentColor")
    // 漣漪效果
    // 根據(jù)當(dāng)前環(huán)境選擇是否使用新版 Material3 的 ripple(),還是退回到老版的 rememberRipple() 實現(xiàn)
    val ripple = if (LocalUseFallbackRippleImplementation.current) {
        rememberRipple(true, Dp.Unspecified, Color.Unspecified)
    } else {
        ripple(true, Dp.Unspecified, Color.Unspecified)
    }
    // 6. Surface + 手動發(fā) PressInteraction
    Surface(
        modifier = modifier
            .minimumInteractiveComponentSize()
            .pointerInput(enabled) {
                detectTapGestures(
                    onPress = { offset ->
                        // 發(fā)起 PressInteraction,供 collectIsPressedAsState 監(jiān)聽
                        val press = PressInteraction.Press(offset)
                        val scope = CoroutineScope(coroutineContext)
                        scope.launch {
                            interaction.emit(press)
                        }
                        // 用戶 onPressed
                        onPressed()
                        // 等待手指抬起或取消
                        tryAwaitRelease()
                        // 發(fā) ReleaseInteraction
                        scope.launch {
                            interaction.emit(PressInteraction.Release(press))
                        }
                        // 用戶 onReleased
                        onReleased()
                    },
                    onTap = { onClick() },
                    onLongPress = { onLongPress() }
                )
            }
            .indication(interaction, ripple)
            .semantics { role = Role.Button },
        shape = shape,
        color = containerColorAni,
        contentColor = contentColorAni,
        shadowElevation = shadowElevation,
        border = border,
    ) {
        CompositionLocalProvider(
            LocalContentColor provides contentColorAni,
            LocalTextStyle provides LocalTextStyle.current.merge(MaterialTheme.typography.labelLarge),
        ) {
            Row(
                Modifier
                    .defaultMinSize(ButtonDefaults.MinWidth, ButtonDefaults.MinHeight)
                    .padding(contentPadding),
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically,
                content = content
            )
        }
    }
}

到此這篇關(guān)于Kotlin Compose Button 實現(xiàn)長按監(jiān)聽并實現(xiàn)動畫效果的文章就介紹到這了,更多相關(guān)Kotlin Compose Button長按監(jiān)聽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • android實現(xiàn)條目倒計時功能

    android實現(xiàn)條目倒計時功能

    這篇文章主要為大家詳細介紹了android實現(xiàn)條目倒計時功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • 智能指針與弱引用詳解

    智能指針與弱引用詳解

    智能指針有很多實現(xiàn)方式,android 中的sp 句柄類實際上就是google 實現(xiàn)的一種強引用的智能指針。我沒有仔細看android sp 的實現(xiàn)方式,但其基本原理是固定的,現(xiàn)在我們從一個相對簡單的例子來看智能指針的實現(xiàn)
    2013-09-09
  • Android自定義EditText實現(xiàn)登錄界面

    Android自定義EditText實現(xiàn)登錄界面

    這篇文章主要為大家詳細介紹了Android自定義EditText實現(xiàn)登錄界面,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • Android仿微信朋友圈添加圖片的實例代碼

    Android仿微信朋友圈添加圖片的實例代碼

    本篇文章主要介紹了Android仿微信朋友圈添加圖片的實例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • DCloud的native.js調(diào)用系統(tǒng)分享實例Android版代碼

    DCloud的native.js調(diào)用系統(tǒng)分享實例Android版代碼

    本文為大家分享了DCloud的native.js如何調(diào)用系統(tǒng)分享功能Android版的實例代碼,直接拿來就用
    2018-09-09
  • EditText監(jiān)聽方法,實時的判斷輸入多少字符

    EditText監(jiān)聽方法,實時的判斷輸入多少字符

    在EditText提供了一個方法addTextChangedListener實現(xiàn)對輸入文本的監(jiān)控。本文分享了EditText監(jiān)聽方法案例,需要的朋友一起來看下吧
    2016-12-12
  • Android實現(xiàn)倒計時方法匯總

    Android實現(xiàn)倒計時方法匯總

    這篇文章主要為大家詳細總結(jié)了Android實現(xiàn)倒計時的3種方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • Android通知欄微技巧一些需要注意的小細節(jié)

    Android通知欄微技巧一些需要注意的小細節(jié)

    這篇文章主要介紹了Android通知欄微技巧,那些你所沒關(guān)注過的小細節(jié),小編把此文分享到腳本之家平臺,需要的朋友可以參考下
    2018-04-04
  • 安卓(Android)聊天機器人實現(xiàn)代碼分享

    安卓(Android)聊天機器人實現(xiàn)代碼分享

    這是一個安卓智能聊天機器人的源碼,采用了仿微信的風(fēng)格設(shè)計,調(diào)用的是圖靈機器人的API,能夠?qū)崿F(xiàn)智能聊天、講故事、講笑話、查天氣、查公交等豐富的功能
    2015-11-11
  • Kotlin?RecyclerView滾動控件詳解

    Kotlin?RecyclerView滾動控件詳解

    RecyclerView是Android一個更強大的控件,其不僅可以實現(xiàn)和ListView同樣的效果,還有優(yōu)化了ListView中的各種不足。其可以實現(xiàn)數(shù)據(jù)縱向滾動,也可以實現(xiàn)橫向滾動(ListView做不到橫向滾動)。接下來講解RecyclerView的用法
    2022-12-12

最新評論