APP包含的功能:图片列表,图片检查,图片共享,图片下载,设置壁纸,设置
图片列表
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
@Composable
fun <T> SwipeStaggeredGridRefresh(
limitRefresh: Boolean = true,
limitLoadMore: Boolean = true,
refreshingUI: @Composable () -> Unit = {},
loadMoreUI: @Composable () -> Unit,
emptyLayout: @Composable () -> Unit,
contentUI: @Composable () -> Unit = {},
items: List<T>?,
refreshing: Boolean,
onRefresh: () -> Unit,
loading: Boolean,
onLoad: () -> Unit,
modifier: Modifier = Modifier.background(color = MaterialTheme.colorScheme.background),
listState: LazyStaggeredGridState = rememberLazyStaggeredGridState(),
contentPadding: PaddingValues = PaddingValues(
start = 10.dp,
top = 0.dp,
end = 10.dp,
bottom = 0.dp
),
verticalItemSpacing: Dp = 10.dp,
horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(10.dp),
key: ((index: Int, item: T) -> Any)? = null,
contentType: (index: Int, item: T) -> Any? = { _, _ -> null },
itemContent: @Composable LazyStaggeredGridItemScope.(index: Int, item: T) -> Unit
) {
val pullRefreshState = rememberPullRefreshState(refreshing, { onRefresh() })
if (items.isNullOrEmpty()) {
if (!refreshing) {
emptyLayout()
}
} else {
Box(Modifier.pullRefresh(pullRefreshState)) {
LazyVerticalStaggeredGrid(
modifier = modifier.navigationBarsPadding(),
state = listState,
columns = StaggeredGridCells.Fixed(2),
contentPadding = contentPadding,
verticalItemSpacing = verticalItemSpacing,
horizontalArrangement = horizontalArrangement,
) {
item {
contentUI()
}
itemsIndexed(items, key = key, contentType = contentType) { index, item ->
itemContent(index, item)
LaunchedEffect(items.size) {
if (!refreshing && limitLoadMore && items.size - index < 2) {
onLoad()
}
}
}
if (!refreshing && loading && limitLoadMore) {
item(span = FullLine) {
loadMoreUI()
}
}
}
if (limitRefresh && !loading) {
PullRefreshIndicator(
refreshing = refreshing,
state = pullRefreshState,
backgroundColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.primary,
modifier = Modifier.align(Alignment.TopCenter),
)
}
}
}
}
图片加载
@Composable
fun <T> PhotoItem(item: T, imgUrl: String, click: (T) -> Unit) {
val configuration = LocalConfiguration.current
AsyncImage(
modifier = Modifier
.fillMaxWidth()
.clickable { click(item) }
.clip(RoundedCornerShape(10.dp))
.heightIn(max = configuration.screenHeightDp.dp * 0.5f),
model = ImageRequest.Builder(LocalContext.current)
.data(imgUrl)
.crossfade(true)
.build(),
contentDescription = null,
contentScale = ContentScale.FillWidth,
)
}
图片概况
顶部APPBar
TopAppBar(
title = {
Text(
text = title,
maxLines = 1,
softWrap = false,
textAlign = TextAlign.Start,
overflow = TextOverflow.Ellipsis,
color = Color(0xFFFFFFFF),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(start = 5.dp, end = 10.dp)
)
},
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
tint = Color(0xFFFFFFFF)
)
}
},
actions = {
DownLoadButton(
tint = Color(0xFFFFFFFF),
onClick = downLoad
)
WallpaperButton(
tint = Color(0xFFFFFFFF),
onClick = wallpaper
)
ShareButton(
tint = Color(0xFFFFFFFF),
onClick = share
)
},
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color(0x00FFFFFF),
)
)
图片扩大缩小
@Composable
private fun Image(
imgUrl: String,
onLoading: () -> Unit,
onSuccess: () -> Unit,
drawable: (Drawable) -> Unit
) {
/**
* 缩放份额
*/
var scale by remember { mutableStateOf(1f) }
/**
* 监听手势状况变换
*/
val state = rememberTransformableState(onTransformation = { zoomChange, panChange, _ ->
scale = (zoomChange * scale).coerceAtLeast(1f)
scale = if (scale > 5f) {
5f
} else {
scale
}
})
val configuration = LocalConfiguration.current
AsyncImage(
//placeholder = rememberAsyncImagePainter(imgUrl),
model = ImageRequest.Builder(LocalContext.current)
.data(imgUrl)
.crossfade(true)
.build(),
contentDescription = null,
modifier = Modifier
.transformable(state = state)
.graphicsLayer(
scaleX = scale,
scaleY = scale,
)
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = {
scale = if (scale <= 1f) {
2f
} else {
1f
}
}
)
}
.fillMaxSize(),
contentScale = ContentScale.Fit,
onLoading = {
onLoading()
},
onSuccess = {
onSuccess()
drawable(it.result.drawable)
}
)
}
壁纸设置我是经过跳转到系统壁纸设置页面来完成,这样设置作用最好。
图片下载,我偷个懒,直接经过bitmap来保存。
图片共享
我们需求把compose UI转换为Android view再去截图共享
@Composable
fun ComposeUIToView(
content: @Composable () -> Unit,
onLoading: () -> Unit,
onSuccess: (ShareCardView) -> Unit,
) {
onLoading()
AndroidView(modifier = Modifier.wrapContentSize(),
factory = {
ShareCardView(
it,
content = {
content()
}
)
},
update = {
onSuccess(it)
}
)
}
@SuppressLint("ViewConstructor")
class ShareCardView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
val content: @Composable () -> Unit
) : AbstractComposeView(context, attrs, defStyleAttr) {
@Composable
override fun Content() {
content()
}
}
设置
主要用来装备一下APP需求的功能
暗黑形式几行代码就搞定啦
val systemUiController = rememberSystemUiController()
val isSystemInDarkTheme: Boolean = isSystemInDarkTheme()
appViewModel.initUserData(appContainer.appRepository, isSystemInDarkTheme)
val darkTheme = appUiState.userData?.darkTheme ?: false
NetworkStatus(context, appViewModel, appUiState.networkConnected, appUiState.dailyData)
// Update the dark content of the system bars to match the theme
DisposableEffect(systemUiController, darkTheme) {
systemUiController.systemBarsDarkContentEnabled = !darkTheme
onDispose {}
}
APP依靠库
dependencies {
implementation 'androidx.core:core-ktx:1.10.1'
implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
implementation 'androidx.activity:activity-compose:1.7.2'
implementation platform('androidx.compose:compose-bom:2022.10.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
implementation "androidx.compose.material:material:1.5.0"
implementation "androidx.compose.material:material-icons-extended:1.5.0"
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
//AsyncImage
implementation "io.coil-kt:coil-compose:2.2.2"
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.1"
implementation "androidx.core:core-splashscreen:1.0.1"
implementation "com.google.accompanist:accompanist-systemuicontroller:0.30.1"
implementation "androidx.compose.material3:material3-window-size-class:1.1.1"
implementation 'androidx.compose.ui:ui-util'
implementation "androidx.navigation:navigation-compose:2.7.0"
implementation files('src/main/libs/jsoup-1.9.2.jar')
//local-preference
implementation "androidx.datastore:datastore-preferences:1.0.0"
implementation "com.google.accompanist:accompanist-permissions:0.30.1"
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
}
整个APP跳转经过Navigation
/**
* Models the navigation actions in the app.
*/
class NavigationActions(navController: NavHostController) {
val navigateToPexels: () -> Unit = {
navController.navigate(PhotoDestinations.PEXELS_ROUTE) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
val navigateToDetail: (String) -> Unit = {
navController.navigate("detail/$it") {
launchSingleTop = true
restoreState = true
}
}
val navigateToShare: () -> Unit = {
navController.navigate(PhotoDestinations.SHARE_ROUTE) {
launchSingleTop = true
restoreState = true
}
}
val navigateToDaily: () -> Unit = {
navController.navigate(PhotoDestinations.DAILY_ROUTE) {
launchSingleTop = true
restoreState = true
}
}
}
总结:个人能力有限,经过有限的学习、项目练习,觉得做展示类型的APP非常适宜。
APP体验
下载
GitHub地址
传送门