Jetpack Compose重写TopAppBar实现标题多行折叠详解

目录
  • 前言
  • MediumTopAppBar
    • 阅读源码
      • 核心
  • 解决方法
  • 重写TopAppBarLayout
  • 完整代码

前言

想用composes实现类似掘金的文章详细页面的标题栏

上滑隐藏标题后标题栏显示标题

compose.material3下的TopAppBar不能嵌套滚动

MediumTopAppBar

便使用了MediumTopAppBar一开始用着没什么问题,但是标题字数多了,MediumTopAppBar就不支持了,最多就两行,进入源码一看就明白了

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

TwoRowsTopAppBar 官方就是告诉你我就两行,要是不服你就自己写,自己写就自己写,当然我才不自己写呢,直接抄,把TwoRowsTopAppBarcopy过来改改就行,开始想着改TextmaxLines就行,后来才发现TwoRowsTopAppBar是用最大heignt限制的

阅读源码

理解源码可以知道MediumTopAppBar布局可以分为两块

上标题栏(TopAppBa) 和下标题(bottomTitle)分别设置了固定高度

布局 高度
上标题栏 122.dp
下标题 64.dp

这个就是TwoRowsTopAppBar命名的TwoRows的原因

高度是固定在我们改不了

核心

首先限制嵌套滑动的Y轴最大的偏移量也就是高度,目的就是仅隐藏底部标题区域并保留顶部标题
手指上滑后计算上滑偏移量

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

接着scrollBehavior.state.collapsedFraction获取折叠高度百分比(0.0表示完全展开,1.0表示完全折叠)

在利用三阶贝塞尔曲线+百分比设置titleText的Alpha值实现滑动渐显效果

最后实现自定义布局,下标题的高度-上滑偏移量实现折叠标题 并且利用Alpha显示上标题

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

解决方法

先计算下布局高度

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

保留上标题的固定高度,动态计算最大高度

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

重写TopAppBarLayout

为下布局重写TopAppBarLayout,去除里面的无用代码

使用方法和MediumTopAppBar一样,只不过

title变成了topLayoutbottomLayout两个Composable

为了方便实现不同的字体风格其他布局,可以像掘金一样显示头像关注

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 = "关注 点赞 ",
            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)) }
    //计算布局高度
    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()
    }
    // 设置应用程序栏的高度偏移限制以仅隐藏底部标题区域并保留顶部标题
    // 折叠时可见。
    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()
                    // 在填充后剪辑,这样不会在插入区域上显示标题
                    .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 {
    //检查应用程序栏是否完全折叠/展开。如果是,则无需结算应用程序栏,
    //然后返回零速度。
    //请注意,由于collapsedFraction的浮点精度,不用检查 0f
    if (state.collapsedFraction < 0.01f || state.collapsedFraction == 1f) {
        return Velocity.Zero
    }
    var remainingVelocity = velocity
    //如果有一个初始速度是在前一次用户投掷后留下的,则设置动画以
    // 继续运动以展开或折叠应用程序栏。
    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
                // 避免舍入错误,如果有任何内容未被使用,则停止
                if (abs(delta - consumed) > 0.5f) this.cancelAnimation()
            }
    }
    // 如果提供了动画规格,则捕捉。
    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实现标题多行折叠详解的详细内容,更多关于Jetpack Compose TopAppBar的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android Jetpack Compose开发实用小技巧

    目录 前言 实用小技巧 如何移除View点击阴影 Text文本如何垂直居中 如何移除Button的点击阴影 Dialog宽度如何全屏 如何提升编码效率 前言 在Compose开发的过程中,我们会经常遇到一些看起来很简单却不知道如何处理的小问题,比如去除点击阴影.Dialog全屏等问题,本文记录了这些常见小问题的处理方式.如有更好方案欢迎大佬们交流探讨- 实用小技巧 如何移除View点击阴影 这里的View指的是除了Button系列的之外,如Button.TextButton等,也就是自身没有on

  • Jetpack Compose自定义动画与Animatable详解

    目录 AnimationSpec 1.spring 2.tween 3.keyframes 4.repeatable 5.snap Animatable 本篇主要是自定义动画与Animatable. AnimationSpec 上一篇中,出现了多次animationSpec属性,它是用来自定义动画规范的.例如: fun Modifier.animateContentSize( animationSpec: FiniteAnimationSpec<IntSize> = spring(), fin

  • Jetpack Compose惯性衰减动画AnimateDecay详解

    目录 什么是惯性衰减动画 惯性衰减动画 使用要点 block 监听 什么是惯性衰减动画 比如说我们玩微信的时候 手指一拉,微信的列表就会惯性滑动 ,这个滑动的速率当然是越来越慢的,最终停止, 这个其实就是惯性衰减动画的典型例子 那这个例子和animateTo 有啥区别呢? 一个速率变慢的动画 ,听起来似乎 我们用animateTo 设置一些参数也可以实现 其实这里最大的区别就是 animateTo 你是需要设置目标值的,也就是动画结束的那一刻 某个view属性的值 你必须明确指定 而所谓的惯性衰

  • Jetpack Compose DropdownMenu手指跟随点击显示

    目录 引言 效果图 实现方法 1使用DropdownMenu的offset参数 2Modifier.offset 获取到点击的位置 Box创建用于监听点击事件修饰符 DropdownMenu外层的Box()设置偏移量 完整代码 使用方法 引言 DropdownMenu显示时默认会避开点击的view 通常默认显示在左下方 本篇文章教你实现跟随手指按下位置显示 效果图 实现方法 首先要获取到点击的位置之后计算偏移量 先分析两种offset参数 1使用DropdownMenu的offset参数 获取到

  • Jetpack Compose 分步指南教程详解

    目录 前言 可组合函数 显示简单文本 将样式应用于文本 使用 TextField 进行输入 在 Android Studio 中预览 预览参数 Column Scrollable Column Lazy Column Box Button Card Clickable Image Alert Dialog Material AppBar Material BottomNavigation Material Checkbox Material ProgressBar Material Slider

  • Jetpack Compose 双指拖拽实现详解

    目录 Modifier.offset graphicsLayer Modifier.pointerInput PointerInputScope.detectTransformGestures 逻辑解释 定义4个变量 传入graphicsLayer里面 监听手势 完整代码 效果图 Modifier.offset Compose遇到一个浏览图片的功能,双指放大和缩小 Modifier的offset可以偏移内容.偏移量可以是正的,也可以是非正的.应用偏移只会更改内容的位置,而不会影响其大小测量. o

  • Jetpack Compose重写TopAppBar实现标题多行折叠详解

    目录 前言 MediumTopAppBar 阅读源码 核心 解决方法 重写TopAppBarLayout 完整代码 前言 想用composes实现类似掘金的文章详细页面的标题栏 上滑隐藏标题后标题栏显示标题 compose.material3下的TopAppBar不能嵌套滚动 MediumTopAppBar 便使用了MediumTopAppBar一开始用着没什么问题,但是标题字数多了,MediumTopAppBar就不支持了,最多就两行,进入源码一看就明白了 @ExperimentalMater

  • Java导出excel时合并同一列中相同内容的行思路详解

    一.有时候导出Excel时需要按类别导出,一大类下好几个小类,小类下又有好几个小小类,就像下图: 要实现这个也不难, 思路如下:按照大类来循环,如上就是按照张江校区.徐汇校区.临港校区三个大类循环,然后再处理小类,因为本例小小类不涉及合并,所以只涉及处理小类,如果需要处理小小类,还需要在处理一下,具体实现原理同小类: 每次循环时记录下此次循环的房屋类型和上次循环的房屋类型,两者相同时,要合并的结束行++,否者,说明这个房屋类型已经循环完毕(前提是各类型都按顺序order by 了,保证相同类型相

  • 对Python中DataFrame选择某列值为XX的行实例详解

    如下所示: #-*-coding:utf8-*- import pandas as pd all_data=pd.read_csv("E:/协和问答系统/SenLiu/熵测试数据.csv") #获取某一列值为xx的行的候选列数据 print(all_data) feature_data=all_data.iloc[:,[0,-1]][all_data[all_data.T.index[0]]=='青年'] print(feature_data) 实验结果如下: "C:\Pro

  • 对pandas通过索引提取dataframe的行方法详解

    一.假设有这样一个原始dataframe 二.提取索引 (已经做了一些操作将Age为NaN的行提取出来并合并为一个dataframe,这里提取的是该dataframe的索引,道理和操作是相似的,提取的代码没有贴上去是为了不显得太繁杂让读者看着繁琐) >>> index = unknown_age_Mr.index.tolist() #记得转换为list格式 三.提取索引对应的原始dataframe的行 使用iloc函数将数据块提取出 >>> age_df.iloc[in

  • Django model重写save方法及update踩坑详解

    一个非常实用的小方法 试想一下,Django中如果我们想对保存进数据库的数据做校验,有哪些实现的方法? 我们可以在view中去处理,每当view接收请求,就对提交的数据做校验,校验不通过直接返回错误,不写数据库,校验通过再调用create或update方法写入数据库 以上方式比较简单,容易理解,但随之又带来了麻烦,我们需在所有接收数据的地方都要去校验,那么有没有更加优雅的方式呢?如果你看过我之前的文章『Django使用Signals监测model字段变化发送通知』]就能想到可以通过signals

  • Javascript异步流程控制之串行执行详解

    这篇文章主要讲一下如何串行执行一组异步任务,例如有下面几个任务,在这里我们用setTimeout模拟一个异步任务: let taskA = () => setTimeout(() => console.log('run task A'), 100); let taskB = () => setTimeout(() => console.log('run task B'), 50); let taskC = () => setTimeout(() => console.l

  • 使用maven创建普通项目命令行程序详解

    目录 引言 下载并配置Maven maven项目的构建 通过Maven配置mysql.myBtais.myBtais-plus maven命令工具 引言 maven是一个软件项目管理工具,其核心是基于项目对象模型(POM project object model),即就是通过对其生成的pom.xml进行配置来管理项目的构建,报告和文档的.其功能包括两部分,一是对项目依赖jar包的管理,二是提供项目的构建.打包.测试等命令 下载并配置Maven 参考地址:百度 maven项目的构建 打开idea,

  • 如何更改Dialog的标题与按钮颜色详解

    前言 本文主要给大家介绍了如何更改Dialog的标题与按钮颜色的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. android.support.v7.app.AlertDialog 在这个类中第一行就定义了如下变量: final AlertController mAlert; AlertDialog的功能的具体实现都在这个AlertController内部封装. 修改按钮颜色 1. AlertDialog.getButton public Button getButt

  • PHP 命令行参数详解及应用

    复制代码 代码如下: # 不带参数的执行格式 php安装目录/bin/php scriptname.php # 带参数的执行格式 php安装目录/bin/php scriptname.php [参数1] [参数2] ..... 在scriptname.php通过$argv和$argc访问参数 # $argv(正式写法$_SERVER['argv'])数组保存着传递的全部参数,需要注意的是第一个参数$argv[0] / $_SERVER['argv'][0] 为执行脚本的名称如, scriptna

  • python的pytest框架之命令行参数详解(上)

    前言 pytest是一款强大的python自动化测试工具,可以胜任各种类型或者级别的软件测试工作.pytest提供了丰富的功能,包括assert重写,第三方插件,以及其他测试工具无法比拟的fixture模型.pytest是一个软件测试框架,是一款命令行工具,可以自动找到测试用例执行,并且回报测试结果.有丰富的基础库,可以大幅度提高用户编写测试用例的效率.具备扩展性,用户可以自己编写插件,或者安装第三方提供的插件.可以很容易地与其他工具集成到一起使用.比如持续集成,web自动化测试等. 下面列举了

随机推荐