android -------- Jetpack Compose基础使用
Posted 切切歆语
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android -------- Jetpack Compose基础使用相关的知识,希望对你有一定的参考价值。
前面学习了一哈 Jetpack Compose 这玩意,分享一哈 Compose 中布局的目标 布局系统的 Jetpack Compose 实现有两个主要目标:一是实现高性能,二是让开发者能够轻松编写自定义布局。 在 Compose 中,通过避免多次测量布局子级可实现高性能。
图1:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetComeposeTheme {
Surface(color = MaterialTheme.colors.background) {
//Greeting("android1")
Test()
// UserCard()
}
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
JetComeposeTheme {
Greeting("Android")
}
}
@Composable
fun GreetingColor(name: String) {
Surface(color = Color.Yellow) {
Text(text = "Hello $name !")
}
}
@Composable
fun Test() {
Column(modifier = Modifier.padding(16.dp)) {
Image(
painter = painterResource(id = R.drawable.header),
contentDescription = null,
modifier = Modifier
.height(180.dp)
.fillMaxWidth()
.clip(shape = RoundedCornerShape(4.dp)),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.height(16.dp))
Text(
"A day wandering through the sandhills in Shark Fin Cove, and a few of the sights I saw",
style = typography.h6,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Text(
"Compose Test",
style = typography.body2
)
Text("zhangqie 2021", style = typography.body2)
Spacer(modifier = Modifier.height(18.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
painter = painterResource(id = R.mipmap.logo_round),
contentDescription = "头像",
modifier = Modifier
.requiredSize(65.dp)
.height(56.dp)
)
Spacer(modifier = Modifier.width(15.dp))
Column {
Text(text = "我是名字")
Text(text = "我是简介")
}
}
}
}
图2:
class Demo2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AnimationCodelabTheme {
Home()
}
}
}
}
private enum class TabPage {
Home, Work
}
@Composable
fun Home() {
// String resources.
val allTasks = stringArrayResource(R.array.tasks)
val allTopics = stringArrayResource(R.array.topics).toList()
// The currently selected tab.
var tabPage by remember { mutableStateOf(TabPage.Home) }
// True if the whether data is currently loading.
var weatherLoading by remember { mutableStateOf(false) }
// Holds all the tasks currently shown on the task list.
val tasks = remember { mutableStateListOf(*allTasks) }
// Holds the topic that is currently expanded to show its body.
var expandedTopic by remember { mutableStateOf<String?>(null) }
// True if the message about the edit feature is shown.
var editMessageShown by remember { mutableStateOf(false) }
// Simulates loading weather data. This takes 3 seconds.
suspend fun loadWeather() {
if (!weatherLoading) {
weatherLoading = true
delay(3000L)
weatherLoading = false
}
}
// Shows the message about edit feature.
suspend fun showEditMessage() {
if (!editMessageShown) {
editMessageShown = true
delay(3000L)
editMessageShown = false
}
}
// Load the weather at the initial composition.
LaunchedEffect(Unit) {
loadWeather()
}
val lazyListState = rememberLazyListState()
// The background color. The value is changed by the current tab.
// TODO 1: Animate this color change.
val backgroundColor = if (tabPage == TabPage.Home) Purple100 else Green300
// The coroutine scope for event handlers calling suspend functions.
val coroutineScope = rememberCoroutineScope()
Scaffold(
topBar = {
HomeTabBar(
backgroundColor = backgroundColor,
tabPage = tabPage,
onTabSelected = { tabPage = it }
)
},
backgroundColor = backgroundColor,
floatingActionButton = {
HomeFloatingActionButton(
extended = lazyListState.isScrollingUp(),
onClick = {
coroutineScope.launch {
showEditMessage()
}
}
)
}
) {
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 32.dp),
state = lazyListState
) {
// Weather
item { Header(title = stringResource(R.string.weather)) }
item { Spacer(modifier = Modifier.height(16.dp)) }
item {
Surface(
modifier = Modifier.fillMaxWidth(),
elevation = 2.dp
) {
if (weatherLoading) {
LoadingRow()
} else {
WeatherRow(onRefresh = {
coroutineScope.launch {
loadWeather()
}
})
}
}
}
item { Spacer(modifier = Modifier.height(32.dp)) }
// Topics
item { Header(title = stringResource(R.string.topics)) }
item { Spacer(modifier = Modifier.height(16.dp)) }
items(allTopics) { topic ->
TopicRow(
topic = topic,
expanded = expandedTopic == topic,
onClick = {
expandedTopic = if (expandedTopic == topic) null else topic
}
)
}
item { Spacer(modifier = Modifier.height(32.dp)) }
// Tasks
item { Header(title = stringResource(R.string.tasks)) }
item { Spacer(modifier = Modifier.height(16.dp)) }
if (tasks.isEmpty()) {
item {
TextButton(onClick = { tasks.clear(); tasks.addAll(allTasks) }) {
Text(stringResource(R.string.add_tasks))
}
}
}
items(count = tasks.size) { i ->
val task = tasks.getOrNull(i)
if (task != null) {
key(task) {
TaskRow(
task = task,
onRemove = { tasks.remove(task) }
)
}
}
}
}
EditMessage(editMessageShown)
}
}
/**
* Shows the floating action button.
*
* @param extended Whether the tab should be shown in its expanded state.
*/
// AnimatedVisibility is currently an experimental API in Compose Animation.
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun HomeFloatingActionButton(
extended: Boolean,
onClick: () -> Unit
) {
// Use `FloatingActionButton` rather than `ExtendedFloatingActionButton` for full control on
// how it should animate.
FloatingActionButton(onClick = onClick) {
Row(
modifier = Modifier.padding(horizontal = 16.dp)
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = null
)
// Toggle the visibility of the content with animation.
// TODO 2-1: Animate this visibility change.
if (extended) {
Text(
text = stringResource(R.string.edit),
modifier = Modifier
.padding(start = 8.dp, top = 3.dp)
)
}
}
}
}
/**
* Shows a message that the edit feature is not available.
*/
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun EditMessage(shown: Boolean) {
AnimatedVisibility(
visible = shown
) {
Surface(
modifier = Modifier.fillMaxWidth(),
color = MaterialTheme.colors.secondary,
elevation = 4.dp
) {
Text(
text = stringResource(R.string.edit_message),
modifier = Modifier.padding(16.dp)
)
}
}
}
/**
* Returns whether the lazy list is currently scrolling up.
*/
@Composable
private fun LazyListState.isScrollingUp(): Boolean {
var previousIndex by remember(this) { mutableStateOf(firstVisibleItemIndex) }
var previousScrollOffset by remember(this) { mutableStateOf(firstVisibleItemScrollOffset) }
return remember(this) {
derivedStateOf {
if (previousIndex != firstVisibleItemIndex) {
previousIndex > firstVisibleItemIndex
} else {
previousScrollOffset >= firstVisibleItemScrollOffset
}.also {
previousIndex = firstVisibleItemIndex
previousScrollOffset = firstVisibleItemScrollOffset
}
}
}.value
}
/**
* Shows the header label.
*
* @param title The title to be shown.
*/
@Composable
private fun Header(
title: String
) {
Text(
text = title,
modifier = Modifier.semantics { heading() },
style = MaterialTheme.typography.h5
)
}
/**
* Shows a row for one topic.
*
* @param topic The topic title.
* @param expanded Whether the row should be shown expanded with the topic body.
* @param onClick Called when the row is clicked.
*/
@Composable
private fun TopicRow(topic: String, expanded: Boolean, onClick: () -> Unit) {
TopicRowSpacer(visible = expanded)
Surface(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick),
elevation = 2.dp
) {
// TODO 3: Animate the size change of the content.
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Row {
Icon(
imageVector = Icons.Default.Info,
contentDescription = null
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = topic,
style = MaterialTheme.typography.body1
)
}
if (expanded) {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.lorem_ipsum),
textAlign = TextAlign.Justify
)
}
}
}
TopicRowSpacer(visible = expanded)
}
/**
* Shows a separator for topics.
*/
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun TopicRowSpacer(visible: Boolean) {
AnimatedVisibility(visible = visible) {
Spacer(modifier = Modifier.height(8.dp))
}
}
/**
* Shows the bar that holds 2 tabs.
*
* @param backgroundColor The background color for the bar.
* @param tabPage The [TabPage] that is currently selected.
* @param onTabSelected Called when the tab is switched.
*/
@Composable
private fun HomeTabBar(
backgroundColor: Color,
tabPage: TabPage,
onTabSelected: (tabPage: TabPage) -> Unit
) {
TabRow(
selectedTabIndex = tabPage.ordinal,
backgroundColor = backgroundColor,
indicator = { tabPositions ->
HomeTabIndicator(tabPositions, tabPage)
}
) {
HomeTab(
icon = Icons.Default.Home,
title = stringResource(R.string.home),
onClick = { onTabSelected(TabPage.Home) }
)
HomeTab(
icon = Icons.Default.AccountBox,
title = stringResource(R.string.work),
onClick = { onTabSelected(TabPage.Work) }
)
}
}
/**
* Shows an indicator for the tab.
*
* @param tabPositions The list of [TabPosition]s from a [TabRow].
* @param tabPage The [TabPage] that is currently selected.
*/
@Composable
private fun HomeTabIndicator(
tabPositions: List<TabPosition>,
tabPage: TabPage
) {
// TODO 4: Animate these value changes.
val indicatorLeft = tabPositions[tabPage.ordinal].left
val indicatorRight = tabPositions[tabPage.ordinal].right
val color = if (tabPage == TabPage.Home) Purple700 else Green800
Box(
Modifier
.fillMaxSize()
.wrapContentSize(align = Alignment.BottomStart)
.offset(x = indicatorLeft)
.width(indicatorRight - indicatorLeft)
.padding(4.dp)
.fillMaxSize()
.border(
BorderStroke(2.dp, color),
RoundedCornerShape(4.dp)
)
)
}
/**
* Shows a tab.
*
* @param icon The icon to be shown on this tab.
* @param title The title to be shown on this tab.
* @param onClick Called when this tab is clicked.
* @param modifier The [Modifier].
*/
@Composable
private fun HomeTab(
icon: ImageVector,
title: String,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.clickable(onClick = onClick)
.padding(16.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null
)
Spacer(modifier = Modifier.width(16.dp))
Text(text = title)
}
}
/**
* Shows the weather.
*
* @param onRefresh Called when the refresh icon button is clicked.
*/
@Composable
private fun WeatherRow(
onRefresh: () -> Unit
) {
Row(
modifier = Modifier
.heightIn(min = 64.dp)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(Amber600)
)
Spacer(modifier = Modifier.width(16.dp))
Text(text = stringResource(R.string.temperature), fontSize = 24.sp)
Spacer(modifier = Modifier.weight(1f))
IconButton(onClick = onRefresh) {
Icon(
imageVector = Icons.Default.Refresh,
contentDescription = stringResource(R.string.refresh)
)
}
}
}
/**
* Shows the loading state of the weather.
*/
@Composable
private fun LoadingRow() {
// TODO 5: Animate this value between 0f and 1f, then back to 0f repeatedly.
val alpha = 1f
Row(
modifier = Modifier
.heightIn(min = 64.dp)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(Color.LightGray.copy(alpha = alpha))
)
Spacer(modifier = Modifier.width(16.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.height(32.dp)
.background(Color.LightGray.copy(alpha = alpha))
)
}
}
/**
*
* @param task The task description.
* @param onRemove Called when the task is swiped away and removed.
*/
@Composable
private fun TaskRow(task: String, onRemove: () -> Unit) {
Surface(
modifier = Modifier
.fillMaxWidth()
.swipeToDismiss(onRemove),
elevation = 2.dp
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = task,
style = MaterialTheme.typography.body1
)
}
}
}
/**
* The modified element can be horizontally swiped away.
*
* @param onDismissed Called when the element is swiped to the edge of the screen.
*/
private fun Modifier.swipeToDismiss(
onDismissed: () -> Unit
): Modifier = composed {
// TODO 6-1: Create an Animatable instance for the offset of the swiped element.
pointerInput(Unit) {
// Used to calculate a settling position of a fling animation.
val decay = splineBasedDecay<Float>(this)
// Wrap in a coroutine scope to use suspend functions for touch events and animation.
coroutineScope {
while (true) {
// Wait for a touch down event.
val pointerId = awaitPointerEventScope { awaitFirstDown().id }
// TODO 6-2: Touch detected; the animation should be stopped.
// Prepare for drag events and record velocity of a fling.
val velocityTracker = VelocityTracker()
// Wait for drag events.
awaitPointerEventScope {
horizontalDrag(pointerId) { change ->
launch {
// TODO 6-3: Apply the drag change to the Animatable offset.
}
// Record the velocity of the drag.
velocityTracker.addPosition(change.uptimeMillis, change.position)
}
}
val velocity = velocityTracker.calculateVelocity().x
launch {
}
}
}
}
.offset {
// TODO 6-7: Use the animating offset value here.
IntOffset(0, 0)
}
}
@Preview
@Composable
private fun PreviewHomeTabBar() {
HomeTabBar(
backgroundColor = Purple100,
tabPage = TabPage.Home,
onTabSelected = {}
)
}
@Preview
@Composable
private fun PreviewHome() {
AnimationCodelabTheme {
Home()
}
}
官网学习文档:
Jetpack Compose | Android Developers
官方布局基础
https://developer.android.google.cn/jetpack/compose/layouts/basic
以上是关于android -------- Jetpack Compose基础使用的主要内容,如果未能解决你的问题,请参考以下文章
Android Jetpack架构组件带你了解Android Jetpack
Android Jetpack架构组件——什么是Jetpack?
Android Jetpack架构组件——什么是Jetpack?