Jetpack Compose中的Accompanist

accompanist是Jetpack Compose官方提供的一个辅助工具库,以提供那些在Jetpack Compose sdk中目前还没有的功能API。

权限

依赖配置:

repositories {
   
    mavenCentral()
}

dependencies {
   
    implementation "com.google.accompanist:accompanist-permissions:0.28.0"
}
单个权限申请

例如,我们需要获取相机权限,可以通过rememberPermissionState(Manifest.permission.CAMERA)创建一个 PermissionState对象,然后通过PermissionState.status.isGranted判断权限是否已获取,并通过调用permissionState.launchPermissionRequest()来申请权限。

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 别忘了在清单文件中添加权限声明 -->
    <uses-permission android:name="android.permission.CAMERA"/>
    ....
</manifest>
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun PermissionExample() {
   
    // Camera permission state
    val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA)

    if (cameraPermissionState.status.isGranted) {
   
        Text("Camera permission Granted")
    } else {
   
        Column(horizontalAlignment = Alignment.CenterHorizontally) {
   
            val textToShow = if (cameraPermissionState.status.shouldShowRationale) {
   
                // 如果用户之前选择了拒绝该权限,应当向用户解释为什么应用程序需要这个权限
                "未获取相机授权将导致该功能无法正常使用。"
            } else {
   
                // 首次请求授权
                "该功能需要使用相机权限,请点击授权。"
            }
            Text(textToShow)
            Spacer(Modifier.height(8.dp))
            Button(onClick = {
    cameraPermissionState.launchPermissionRequest() }) {
   
                Text("请求权限")
            }
        }
    }
}

在这里插入图片描述

多个权限申请

类似的,通过rememberMultiplePermissionsState获取到 PermissionsState之后, 通过调用permissionsState.launchMultiplePermissionRequest()来请求权限。

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
	<!-- 别忘了在清单文件中添加权限声明 -->
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
 	...
</manifest>

@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun MultiplePermissionsExample() {
   
    val multiplePermissionsState = rememberMultiplePermissionsState(
        listOf(
            android.Manifest.permission.READ_EXTERNAL_STORAGE,
            android.Manifest.permission.CAMERA,
        )
    )
    if (multiplePermissionsState.allPermissionsGranted) {
   
        Text("相机和读写文件权限已授权!")
    } else {
   
        Column(modifier = Modifier.padding(10.dp)) {
   
            Text(
                getTextToShowGivenPermissions(
                    multiplePermissionsState.revokedPermissions, // 被拒绝/撤销的权限列表
                    multiplePermissionsState.shouldShowRationale
                ),
                fontSize = 16.sp
            )
            Spacer(Modifier.height(8.dp))
            Button(onClick = {
    multiplePermissionsState.launchMultiplePermissionRequest() }) {
   
                Text("请求权限")
            }
            multiplePermissionsState.permissions.forEach {
   
                Divider()
                Text(text = "权限名:${
     it.permission} \n " +
                        "授权状态:${
     it.status.isGranted} \n " +
                        "需要解释:${
     it.status.shouldShowRationale}", fontSize = 16.sp)
            }
            Divider()
        }
    }
}

@OptIn(ExperimentalPermissionsApi::class)
private fun getTextToShowGivenPermissions(
    permissions: List<PermissionState>,
    shouldShowRationale: Boolean
): String {
   
    val size = permissions.size
    if (size == 0) return ""
    val textToShow = StringBuilder().apply {
    append("以下权限:") }
    for (i in permissions.indices) {
   
        textToShow.append(permissions[i].permission).apply {
   
            if (i == size - 1) append(" ") else append(", ")
        }
    }
    textToShow.append(
        if (shouldShowRationale) {
   
            " 需要被授权,以保证应用功能正常使用."
        } else {
   
            " 被拒绝使用. 应用功能将不能正常使用."
        }
    )
    return textToShow.toString()
}

在这里插入图片描述

以上代码请求了两个权限,所以运行后系统会分别弹出两次授权弹窗。

定位权限申请:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
</manifest>
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun LocationPermissionsExample() {
   
    val locationPermissionsState = rememberMultiplePermissionsState(
        listOf(
            android.Manifest.permission.ACCESS_COARSE_LOCATION,
            android.Manifest.permission.ACCESS_FINE_LOCATION,
        )
    )
    if (locationPermissionsState.allPermissionsGranted) {
   
        Text("定位权限已授权")
    } else {
   
        Column(horizontalAlignment = Alignment.CenterHorizontally) {
   
            val textToShow = if (locationPermissionsState.shouldShowRationale) {
   
                // 两个权限都被拒绝
                "无法获取定位权限将导致应用功能无法正常使用"
            } else {
   
                // 首次授权
                "该功能需要定位授权"
            }
            Text(text = textToShow)
            Spacer(Modifier.height(8.dp))
            Button(onClick = {
    locationPermissionsState.launchMultiplePermissionRequest() }) {
   
                Text("请求授权")
            }
        }
    }
}

注意:定位权限在 Android 10 以后就被拆分为前台权限Manifest.permission.ACCESS_FINE_LOCATION和后台权限Manifest.permission.ACCESS_BACKGROUND_LOCATION,如果要申请后台权限,首先minSdk配置必须是29以上(也就是Android 10.0,不过这一点很多公司应该不会选择,因为兼容的手机版本高了)且在 Android 11 后两个权限不能同时申请,也就是说要先请求前台权限之后才能申请后台权限。

SystemUiController

该库可以设置应用顶部状态栏和底部导航栏的颜色。

dependencies {
   
    implementation "com.google.accompanist:accompanist-systemuicontroller:0.28.0"
}

例如,可以设置状态栏和导航栏的颜色随着手机系统设置的主题改变而变化

@Composable
fun MyComposeApplicationTheme(
    isDarkTheme: Boolean = isSystemInDarkTheme(),
    // Dynamic color is available on Android 12+
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
   
    val colorScheme = when {
   
        // Android 12以上支持动态主题颜色(可以跟随系统桌面壁纸的主色调自动获取主题颜色)
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
   
            val context = LocalContext.current
            if (isDarkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }
        isDarkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    // 修改状态栏和导航栏颜色
    val systemUiController = rememberSystemUiController()
    SideEffect {
   
        // setStatusBarColor() and setNavigationBarColor() also exist
        systemUiController.setSystemBarsColor(
            color = if(isDarkTheme) Color.Black else Color.White,
        )
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}

在这里插入图片描述
也可以设置icons的颜色

// Remember a SystemUiController
val systemUiController = rememberSystemUiController()
val useDarkIcons = !isSystemInDarkTheme() 
DisposableEffect(systemUiController, useDarkIcons) {
   
    // Update all of the system bar colors to be transparent, and use
    // dark icons if we're in light theme
    systemUiController.setSystemBarsColor(
        color = Color.Transparent,
        darkIcons = useDarkIcons
    )  
}

此外可以使用 systemUiController.setStatusBarColor()systemUiController.setNavigationBarColor() 分别设置状态栏和导航栏的颜色。

如果需要其他组件跟随系统主题颜色变化,最好使用MaterialTheme.colorScheme中的颜色属性。

Pager

对标传统View中的ViewPager组件。

dependencies {
   
    implementation "com.google.accompanist:accompanist-pager:0.28.0"
}
HorizontalPager
@OptIn(ExperimentalPagerApi::class)
@Composable
fun HorizontalPagerExample() {
   
    HorizontalPager(count = 10) {
    page ->
        Box(Modifier.background(colors[page % colors.size]).fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
   
            Text(text = "Page: $page",color = Color.White,fontSize = 22.sp)
        }
    }
}

在这里插入图片描述
在模拟器中运行的时候,有时会出现卡住在中间的情况,不知道是不是模拟器的原因:
在这里插入图片描述
如果想跳转到指定页面,可以使用 pagerState.scrollToPage(index) 或者pagerState.animateScrollToPage(index) 这两个挂起方法:

@OptIn(ExperimentalPagerApi::class)
@Composable
fun HorizontalPagerExample2() {
   
    val scope = rememberCoroutineScope()
    val pagerState = rememberPagerState()

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
   
        HorizontalPager(
            count = 10,
            state = pagerState,
            modifier = Modifier.height(300.dp)
        ) {
    page ->
            Box(Modifier.background(colors[page % colors.size]).fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
   
                Text(text = "Page: $page", color = Color.White, fontSize = 22.sp)
            }
        }
        Button(onClick = {
    scope.launch {
    pagerState.animateScrollToPage(2) } }) {
   
            Text(text = "跳转到第3页")
        }
    }
}
VerticalPager

使用类似HorizontalPager

@OptIn(ExperimentalPagerApi::class)
@Composable
fun VerticalPagerExample() {
   
    VerticalPager(count = 10) {
    page ->
        Box(Modifier.background(colors[page % colors.size]).fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
   
            Text(text = "Page: $page",color = Color.White,fontSize = 22.sp)
        }
    }
}

HorizontalPagerVerticalPager 背后是基于 LazyRowLazyColumn 实现的,不在当前屏幕显示的页面会从容器中移除。

contentPadding

HorizontalPagerVerticalPager 支持设置 contentPadding , 如果设置start padding,则当前页的开头会显示上一页的部分内容,如果设置horizontal padding,则当前页的开头和结尾会分别显示上一页和下一页的部分内容。

@OptIn(ExperimentalPagerApi::class)
@Composable
fun HorizontalPagerExample() {
   
    HorizontalPager(
        count = 10,
        contentPadding = PaddingValues(start = 64.dp),
    ) {
    page ->
        Box(Modifier.background(colors[page % colors.size]).fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
   
            Text(text = "Page: $page", color = Color.White, fontSize = 22.sp)
        }
    }
}

在这里插入图片描述

@OptIn(ExperimentalPagerApi::class)
@Composable
fun HorizontalPagerExample() {
   
    HorizontalPager(
        count = 10,
        contentPadding = PaddingValues(horizontal = 64.dp),
    ) {
    page ->
        Box(Modifier.background(colors[page % colors.size]).fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
   
            Text(text = "Page: $page", color = Color.White, fontSize = 22.sp)
        }
    }
}

在这里插入图片描述

item滚动效果

Pager的作用域内允许应用轻松引用currentPagecurrentPageOffset 这些值来计算动画效果。官方提供了一个calculateCurrentOffsetForPage()扩展函数来计算给定页面的偏移量:

@OptIn(ExperimentalPagerApi::class)
@Composable
fun ItemScrollEffect() {
   
    HorizontalPager(count = 10) {
    page ->
        Card(
            Modifier.graphicsLayer {
   
                    // 计算当前页面距离滚动位置的绝对偏移量,然后根据偏移量来计算效果
                    val pageOffset = calculateCurrentOffsetForPage(page).absoluteValue

                    // We animate the scaleX + scaleY, between 85% and 100%
                    lerp(
                        start = 0.85f,
                        stop = 1f,
                        fraction = 1f - pageOffset.coerceIn(0f, 1f)
                    ).also {
    scale ->
                        scaleX = scale
                        scaleY = scale
                    }

                    // We animate the alpha, between 50% and 100%
                    alpha = lerp(
                        start = 0.5f,
                        stop = 1f,
                        fraction = 1f - pageOffset.coerceIn(0f, 1f)
                    )
                }
        ) {
   
            Box(Modifier
                .background(colors[page % colors.size])
                .fillMaxWidth(0.85f).height(500.dp),
                contentAlignment = Alignment.Center
            ) {
   
                Text(text = "Page: $page", color = Color.White, fontSize = 22.sp)
            }
        }
    }
}

在这里插入图片描述

注:上面代码中使用到的函数lerp需要单独添加一个依赖库androidx.compose.ui:ui-util

监听页面切换
val pagerState = rememberPagerState()

LaunchedEffect(pagerState
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值