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

Jetpack?Compose重寫TopAppBar實(shí)現(xiàn)標(biāo)題多行折疊詳解

 更新時(shí)間:2022年11月17日 14:10:53   作者:九狼  
這篇文章主要為大家介紹了Jetpack?Compose重寫TopAppBar實(shí)現(xiàn)標(biāo)題多行折疊示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

想用composes實(shí)現(xiàn)類似掘金的文章詳細(xì)頁面的標(biāo)題欄

上滑隱藏標(biāo)題后標(biāo)題欄顯示標(biāo)題

compose.material3下的TopAppBar不能嵌套滾動

MediumTopAppBar

便使用了MediumTopAppBar一開始用著沒什么問題,但是標(biāo)題字?jǐn)?shù)多了,MediumTopAppBar就不支持了,最多就兩行,進(jìn)入源碼一看就明白了

@ExperimentalMaterial3Api
@Composable
fun MediumTopAppBar(
   ...
) {
    TwoRowsTopAppBar(
       ...
    )
}

TwoRowsTopAppBar 官方就是告訴你我就兩行,要是不服你就自己寫,自己寫就自己寫,當(dāng)然我才不自己寫呢,直接抄,把TwoRowsTopAppBarcopy過來改改就行,開始想著改TextmaxLines就行,后來才發(fā)現(xiàn)TwoRowsTopAppBar是用最大heignt限制的

閱讀源碼

理解源碼可以知道MediumTopAppBar布局可以分為兩塊

上標(biāo)題欄(TopAppBa) 和下標(biāo)題(bottomTitle)分別設(shè)置了固定高度

布局高度
上標(biāo)題欄122.dp
下標(biāo)題64.dp

這個(gè)就是TwoRowsTopAppBar命名的TwoRows的原因

高度是固定在我們改不了

核心

首先限制嵌套滑動的Y軸最大的偏移量也就是高度,目的就是僅隱藏底部標(biāo)題區(qū)域并保留頂部標(biāo)題
手指上滑后計(jì)算上滑偏移量

//官方源碼
SideEffect {
    if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
        scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
    }
}

接著scrollBehavior.state.collapsedFraction獲取折疊高度百分比(0.0表示完全展開,1.0表示完全折疊)

在利用三階貝塞爾曲線+百分比設(shè)置titleText的Alpha值實(shí)現(xiàn)滑動漸顯效果

最后實(shí)現(xiàn)自定義布局,下標(biāo)題的高度-上滑偏移量實(shí)現(xiàn)折疊標(biāo)題 并且利用Alpha顯示上標(biāo)題

Column {
   //上標(biāo)題
    TopAppBarLayout(
       ...
    )
    //下標(biāo)題
    TopAppBarLayout(
      ...
        heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
            ?: 0f)
      ...
    )
}
......
val layoutHeight = heightPx.roundToInt()
layout(constraints.maxWidth, layoutHeight) {
    // Title
    titlePlaceable.placeRelative(...)
}

解決方法

先計(jì)算下布局高度

var bottomLayoutViewSize: IntSize by remember { mutableStateOf(IntSize(0,0)) }
val bottomLayoutBox = @Composable {
    Box(
        modifier= Modifier.onSizeChanged { bottomLayoutViewSize = it },
        content = bottomLayout
    )
}

保留上標(biāo)題的固定高度,動態(tài)計(jì)算最大高度

LocalDensity.current.run {
    maxHeightPx = 上布局的高度 + 下布局的高度
}

重寫TopAppBarLayout

為下布局重寫TopAppBarLayout,去除里面的無用代碼

使用方法和MediumTopAppBar一樣,只不過

title變成了topLayoutbottomLayout兩個(gè)Composable

為了方便實(shí)現(xiàn)不同的字體風(fēng)格其他布局,可以像掘金一樣顯示頭像關(guān)注

KnowledgeTopAppBar(
    topLayout = {
        Text(
            modifier = Modifier.padding(6.dp),
            text = "九狼JIULANG",
            color = CustomTheme.colors.textPrimary,
            fontSize = 21.sp,
            maxLines = 1,
            overflow = TextOverflow.Ellipsis,
            fontWeight = FontWeight.Bold
        )
    },
    bottomLayout = {
        Text(
            modifier = Modifier.padding(vertical = 6.dp, horizontal = 12.dp),
            text = "關(guān)注 點(diǎn)贊 ",
            color = CustomTheme.colors.textPrimary,
            fontSize = 19.sp,
            fontWeight = FontWeight.Bold
        )
    },
    navigationIcon = {
  },
    actions = {
    },
    scrollBehavior = scrollBehavior
)

完整代碼

import androidx.compose.animation.core.*
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.text.TextStyle
import com.jiulang.wordsfairy.ui.theme.CustomTheme
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToInt
import androidx.compose.ui.layout.*
import androidx.compose.ui.unit.*
import com.google.accompanist.insets.statusBarsPadding
@ExperimentalMaterial3Api
@Composable
fun KnowledgeTopAppBar(
    modifier: Modifier = Modifier,
    titleBottomPadding: Dp = 28.dp,
    navigationIcon: @Composable () -> Unit,
    actions: @Composable RowScope.() -> Unit,
    topLayout: @Composable () -> Unit,
    bottomLayout: @Composable BoxScope.() -> Unit,
    pinnedHeight: Dp = 46.0.dp,
    scrollBehavior: TopAppBarScrollBehavior
){
    val pinnedHeightPx: Float
    val maxHeightPx: Float
    val titleBottomPaddingPx: Int
    var bottomLayoutViewSize: IntSize by remember { mutableStateOf(IntSize(0,0)) }
    //計(jì)算布局高度
    val bottomLayoutBox = @Composable {
        Box(
            modifier= Modifier.onSizeChanged { bottomLayoutViewSize = it },
            content = bottomLayout
        )
    }
    LocalDensity.current.run {
        pinnedHeightPx = pinnedHeight.toPx()
        maxHeightPx = bottomLayoutViewSize.height.toFloat() +pinnedHeightPx
        titleBottomPaddingPx = titleBottomPadding.roundToPx()
    }
    // 設(shè)置應(yīng)用程序欄的高度偏移限制以僅隱藏底部標(biāo)題區(qū)域并保留頂部標(biāo)題
    // 折疊時(shí)可見。
    SideEffect {
        if (scrollBehavior.state.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
            scrollBehavior.state.heightOffsetLimit = pinnedHeightPx - maxHeightPx
        }
    }
    val colorTransitionFraction = scrollBehavior.state.collapsedFraction
    val appBarContainerColor by rememberUpdatedState(CustomTheme.colors.statusBarColor)
    val actionsRow = @Composable {
        Row(
            horizontalArrangement = Arrangement.End,
            verticalAlignment = Alignment.CenterVertically,
            content = actions
        )
    }
    val topLayoutAlpha = CubicBezierEasing(.8f, 0f, .8f, .15f).transform(colorTransitionFraction)
    val bottomLayoutAlpha = 1f - colorTransitionFraction
    // Hide the top row title semantics when its alpha value goes below 0.5 threshold.
    // Hide the bottom row title semantics when the top title semantics are active.
    val hideTopRowSemantics = colorTransitionFraction < 0.5f
    val hideBottomRowSemantics = !hideTopRowSemantics
    // Set up support for resizing the top app bar when vertically dragging the bar itself.
    val appBarDragModifier = if (!scrollBehavior.isPinned) {
        Modifier.draggable(
            orientation = Orientation.Vertical,
            state = rememberDraggableState { delta ->
                scrollBehavior.state.heightOffset = scrollBehavior.state.heightOffset + delta
            },
            onDragStopped = { velocity ->
                settleAppBar(
                    scrollBehavior.state,
                    velocity,
                    scrollBehavior.flingAnimationSpec,
                    scrollBehavior.snapAnimationSpec
                )
            }
        )
    } else {
        Modifier
    }
    Surface(modifier = modifier.then(appBarDragModifier), color = appBarContainerColor) {
        Column {
            TopAppBarLayout(
                modifier = Modifier
                    .statusBarsPadding()
                    // 在填充后剪輯,這樣不會在插入?yún)^(qū)域上顯示標(biāo)題
                    .clipToBounds(),
                heightPx = pinnedHeightPx,
                navigationIconContentColor =
                CustomTheme.colors.mainColor,
                actionIconContentColor =
                CustomTheme.colors.mainColor,
                title = topLayout,
                titleTextStyle = TextStyle.Default,
                titleAlpha = topLayoutAlpha,
                titleVerticalArrangement = Arrangement.Center,
                titleHorizontalArrangement = Arrangement.Start,
                titleBottomPadding = 0,
                hideTitleSemantics = hideTopRowSemantics,
                navigationIcon = navigationIcon,
                actions = actionsRow,
            )
            KnowledgeTitleLayout(
                modifier = Modifier.clipToBounds(),
                heightPx =  maxHeightPx - pinnedHeightPx + scrollBehavior.state.heightOffset,
                title = bottomLayoutBox,
                titleTextStyle = TextStyle.Default,
                titleAlpha = bottomLayoutAlpha,
                titleVerticalArrangement = Arrangement.Bottom,
                titleHorizontalArrangement = Arrangement.Start,
                titleBottomPadding = titleBottomPaddingPx,
                hideTitleSemantics = hideBottomRowSemantics,
            )
        }
    }
}
@OptIn(ExperimentalMaterial3Api::class)
private suspend fun settleAppBar(
    state: TopAppBarState,
    velocity: Float,
    flingAnimationSpec: DecayAnimationSpec<Float>?,
    snapAnimationSpec: AnimationSpec<Float>?
): Velocity {
    //檢查應(yīng)用程序欄是否完全折疊/展開。如果是,則無需結(jié)算應(yīng)用程序欄,
    //然后返回零速度。
    //請注意,由于collapsedFraction的浮點(diǎn)精度,不用檢查 0f
    if (state.collapsedFraction < 0.01f || state.collapsedFraction == 1f) {
        return Velocity.Zero
    }
    var remainingVelocity = velocity
    //如果有一個(gè)初始速度是在前一次用戶投擲后留下的,則設(shè)置動畫以
    // 繼續(xù)運(yùn)動以展開或折疊應(yīng)用程序欄。
    if (flingAnimationSpec != null && abs(velocity) > 1f) {
        var lastValue = 0f
        AnimationState(
            initialValue = 0f,
            initialVelocity = velocity,
        )
            .animateDecay(flingAnimationSpec) {
                val delta = value - lastValue
                val initialHeightOffset = state.heightOffset
                state.heightOffset = initialHeightOffset + delta
                val consumed = abs(initialHeightOffset - state.heightOffset)
                lastValue = value
                remainingVelocity = this.velocity
                // 避免舍入錯誤,如果有任何內(nèi)容未被使用,則停止
                if (abs(delta - consumed) > 0.5f) this.cancelAnimation()
            }
    }
    // 如果提供了動畫規(guī)格,則捕捉。
    if (snapAnimationSpec != null) {
        if (state.heightOffset < 0 &&
            state.heightOffset > state.heightOffsetLimit
        ) {
            AnimationState(initialValue = state.heightOffset).animateTo(
                if (state.collapsedFraction < 0.5f) {
                    0f
                } else {
                    state.heightOffsetLimit
                },
                animationSpec = snapAnimationSpec
            ) { state.heightOffset = value }
        }
    }
    return Velocity(0f, remainingVelocity)
}
@Composable
private fun TopAppBarLayout(
    modifier: Modifier,
    heightPx: Float,
    navigationIconContentColor: Color,
    actionIconContentColor: Color,
    title: @Composable () -> Unit,
    titleTextStyle: TextStyle,
    titleAlpha: Float,
    titleVerticalArrangement: Arrangement.Vertical,
    titleHorizontalArrangement: Arrangement.Horizontal,
    titleBottomPadding: Int,
    hideTitleSemantics: Boolean,
    navigationIcon: @Composable () -> Unit,
    actions: @Composable () -> Unit,
) {
    Layout(
        {
            Box(
                Modifier
                    .layoutId("navigationIcon")
                    .padding(start = TopAppBarHorizontalPadding)
            ) {
                CompositionLocalProvider(
                    LocalContentColor provides navigationIconContentColor,
                    content = navigationIcon
                )
            }
            Box(
                Modifier
                    .layoutId("title")
                    .padding(horizontal = TopAppBarHorizontalPadding)
                    .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics { } else Modifier)
                    .graphicsLayer(alpha = titleAlpha)
            ) {
                ProvideTextStyle(value = titleTextStyle) {
                    CompositionLocalProvider(
                        content = title
                    )
                }
            }
            Box(
                Modifier
                    .layoutId("actionIcons")
                    .padding(end = TopAppBarHorizontalPadding)
            ) {
                CompositionLocalProvider(
                    LocalContentColor provides actionIconContentColor,
                    content = actions
                )
            }
        },
        modifier = modifier
    ) { measurables, constraints ->
        val navigationIconPlaceable =
            measurables.first { it.layoutId == "navigationIcon" }
                .measure(constraints.copy(minWidth = 0))
        val actionIconsPlaceable =
            measurables.first { it.layoutId == "actionIcons" }
                .measure(constraints.copy(minWidth = 0))
        val maxTitleWidth = if (constraints.maxWidth == Constraints.Infinity) {
            constraints.maxWidth
        } else {
            (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width)
                .coerceAtLeast(0)
        }
        val titlePlaceable =
            measurables.first { it.layoutId == "title" }
                .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth))
        // Locate the title's baseline.
        val titleBaseline =
            if (titlePlaceable[LastBaseline] != AlignmentLine.Unspecified) {
                titlePlaceable[LastBaseline]
            } else {
                0
            }
        val layoutHeight = heightPx.roundToInt()
        layout(constraints.maxWidth, layoutHeight) {
            // Navigation icon
            navigationIconPlaceable.placeRelative(
                x = 0,
                y = (layoutHeight - navigationIconPlaceable.height) / 2
            )
            // Title
            titlePlaceable.placeRelative(
                x = when (titleHorizontalArrangement) {
                    Arrangement.Center -> (constraints.maxWidth - titlePlaceable.width) / 2
                    Arrangement.End ->
                        constraints.maxWidth - titlePlaceable.width - actionIconsPlaceable.width
                    // Arrangement.Start.
                    // An TopAppBarTitleInset will make sure the title is offset in case the
                    // navigation icon is missing.
                    else -> max(12.dp.roundToPx(), navigationIconPlaceable.width)
                },
                y = when (titleVerticalArrangement) {
                    Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2
                    // Apply bottom padding from the title's baseline only when the Arrangement is
                    // "Bottom".
                    Arrangement.Bottom ->
                        if (titleBottomPadding == 0) layoutHeight - titlePlaceable.height
                        else layoutHeight - titlePlaceable.height - max(
                            0,
                            titleBottomPadding - titlePlaceable.height + titleBaseline
                        )
                    // Arrangement.Top
                    else -> 0
                }
            )
            // Action icons
            actionIconsPlaceable.placeRelative(
                x = constraints.maxWidth - actionIconsPlaceable.width,
                y = (layoutHeight - actionIconsPlaceable.height) / 2
            )
        }
    }
}
@Composable
private fun KnowledgeTitleLayout(
    modifier: Modifier,
    heightPx: Float,
    title: @Composable () -> Unit,
    titleTextStyle: TextStyle,
    titleAlpha: Float,
    titleVerticalArrangement: Arrangement.Vertical,
    titleHorizontalArrangement: Arrangement.Horizontal,
    titleBottomPadding: Int,
    hideTitleSemantics: Boolean,
) {
    Layout(
        {
            Box(
                Modifier
                    .layoutId("title")
                    .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics { } else Modifier)
                    .graphicsLayer(alpha = titleAlpha)
            ) {
                ProvideTextStyle(value = titleTextStyle) {
                    CompositionLocalProvider(
                        content = title
                    )
                }
            }
        },
        modifier = modifier
    ) { measurables, constraints ->
        val maxTitleWidth =  constraints.maxWidth
        val titlePlaceable =
            measurables.first { it.layoutId == "title" }
                .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth))
        val layoutHeight =heightPx.roundToInt()
        layout(maxTitleWidth, layoutHeight) {
            // Title
            titlePlaceable.placeRelative(
                x = when (titleHorizontalArrangement) {
                    Arrangement.Center -> (constraints.maxWidth - titlePlaceable.width) / 2
                    Arrangement.End ->
                        constraints.maxWidth - titlePlaceable.width
                    else -> max(0.dp.roundToPx(), 0.dp.roundToPx())
                },
                y = when (titleVerticalArrangement) {
                    Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2
                    // Apply bottom padding from the title's baseline only when the Arrangement is
                    // "Bottom".
                    Arrangement.Bottom ->
                        if (titleBottomPadding == 0) layoutHeight - titlePlaceable.height
                        else layoutHeight - titlePlaceable.height - max(
                            0,
                            titleBottomPadding - titlePlaceable.height
                        )
                    // Arrangement.Top
                    else -> 0
                }
            )
        }
    }
}
private val TopAppBarHorizontalPadding = 4.dp

以上就是Jetpack Compose重寫TopAppBar實(shí)現(xiàn)標(biāo)題多行折疊詳解的詳細(xì)內(nèi)容,更多關(guān)于Jetpack Compose TopAppBar的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • android surfaceView實(shí)現(xiàn)播放視頻功能

    android surfaceView實(shí)現(xiàn)播放視頻功能

    這篇文章主要為大家詳細(xì)介紹了android surfaceView實(shí)現(xiàn)播放視頻功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • Android手機(jī)管理工具類詳解

    Android手機(jī)管理工具類詳解

    這篇文章主要為大家詳細(xì)介紹了Android手機(jī)管理工具類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • Android實(shí)現(xiàn)標(biāo)題上顯示隱藏進(jìn)度條效果

    Android實(shí)現(xiàn)標(biāo)題上顯示隱藏進(jìn)度條效果

    這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)標(biāo)題上顯示隱藏進(jìn)度條效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • Android矢量圖之VectorDrawable類自由填充色彩

    Android矢量圖之VectorDrawable類自由填充色彩

    這篇文章主要介紹了Android矢量圖之VectorDrawable類自由填充色彩的相關(guān)資料,感興趣的小伙伴們可以參考一下
    2016-05-05
  • android app跳轉(zhuǎn)應(yīng)用商店實(shí)現(xiàn)步驟

    android app跳轉(zhuǎn)應(yīng)用商店實(shí)現(xiàn)步驟

    這篇文章主要為大家介紹了android app跳轉(zhuǎn)應(yīng)用商店實(shí)現(xiàn)步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • 基于Android代碼實(shí)現(xiàn)常用布局

    基于Android代碼實(shí)現(xiàn)常用布局

    大家在日常中經(jīng)常見到用xml文件實(shí)現(xiàn)android常用布局,但是大家知道如何用代碼實(shí)現(xiàn)呢?使用代碼實(shí)現(xiàn)可以幫助我們學(xué)習(xí)sdk api,所以小編把我日常整理些關(guān)于android常用布局代碼實(shí)現(xiàn)分享給大家
    2015-11-11
  • Android中JSON的4種解析方式使用和對比

    Android中JSON的4種解析方式使用和對比

    本文主要介紹了Android中JSON的4種解析方式使用和對比,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧<BR>
    2022-06-06
  • Android指紋識別認(rèn)識和基本使用詳解

    Android指紋識別認(rèn)識和基本使用詳解

    這篇文章主要為大家詳細(xì)介紹了Android指紋識別認(rèn)識和基本的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-07-07
  • Android中的緩存與文件存儲目錄詳解

    Android中的緩存與文件存儲目錄詳解

    在Android應(yīng)用開發(fā)中,合理管理應(yīng)用的數(shù)據(jù)存儲至關(guān)重要,本文將詳細(xì)介紹cacheDir, filesDir, externalCacheDir, 以及getExternalFilesDir(Environment.DIRECTORY_PICTURES)這幾個(gè)目錄的用途和區(qū)別,感興趣的朋友跟隨小編一起看看吧
    2024-07-07
  • Android使用ViewPager實(shí)現(xiàn)自動輪播

    Android使用ViewPager實(shí)現(xiàn)自動輪播

    這篇文章主要介紹了Android使用ViewPager實(shí)現(xiàn)自動輪播的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-07-07

最新評論