You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
537 lines
15 KiB
537 lines
15 KiB
package com.gyf.csams.uikit
|
|
|
|
import androidx.annotation.DrawableRes
|
|
import androidx.compose.animation.Crossfade
|
|
import androidx.compose.animation.animateColor
|
|
import androidx.compose.animation.core.*
|
|
import androidx.compose.foundation.Image
|
|
import androidx.compose.foundation.clickable
|
|
import androidx.compose.foundation.layout.*
|
|
import androidx.compose.foundation.text.KeyboardActions
|
|
import androidx.compose.foundation.text.KeyboardOptions
|
|
import androidx.compose.material.*
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
import androidx.compose.runtime.State
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.livedata.observeAsState
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.DefaultAlpha
|
|
import androidx.compose.ui.layout.ContentScale
|
|
import androidx.compose.ui.platform.LocalContext
|
|
import androidx.compose.ui.platform.LocalFocusManager
|
|
import androidx.compose.ui.res.painterResource
|
|
import androidx.compose.ui.text.input.ImeAction
|
|
import androidx.compose.ui.text.style.TextOverflow
|
|
import androidx.compose.ui.tooling.preview.Preview
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
import androidx.navigation.NavHostController
|
|
import androidx.navigation.compose.navigate
|
|
import androidx.navigation.compose.rememberNavController
|
|
import com.gyf.csams.APP
|
|
import com.gyf.csams.R
|
|
import com.gyf.csams.main.model.CarouselViewModel
|
|
import com.gyf.csams.main.model.MarqueeViewModel
|
|
import com.gyf.csams.uikit.theme.CSAMSTheme
|
|
import com.orhanobut.logger.Logger
|
|
import kotlinx.coroutines.launch
|
|
|
|
/**
|
|
* 淡入淡出并且颜色变化文本
|
|
*
|
|
* @param text
|
|
*/
|
|
@Composable
|
|
fun AnimationText(text: String) {
|
|
val infiniteTransition = rememberInfiniteTransition()
|
|
val color by infiniteTransition.animateColor(
|
|
initialValue = MaterialTheme.colors.primary,
|
|
targetValue = MaterialTheme.colors.onPrimary,
|
|
animationSpec = infiniteRepeatable(
|
|
animation = tween(1000, easing = LinearEasing),
|
|
repeatMode = RepeatMode.Reverse
|
|
)
|
|
)
|
|
|
|
Text(
|
|
text = text,
|
|
color = color,
|
|
style = MaterialTheme.typography.body1
|
|
)
|
|
}
|
|
|
|
/**
|
|
* 主菜单
|
|
*
|
|
*/
|
|
enum class MainMenu(
|
|
@DrawableRes val selectedIcon: Int,
|
|
@DrawableRes val unSelectedIcon: Int
|
|
) {
|
|
//主页
|
|
Main(R.drawable.ic_home_fill, R.drawable.ic_home),
|
|
|
|
//社团列表
|
|
List(R.drawable.ic_all_fill, R.drawable.ic_all),
|
|
|
|
//个人中心
|
|
Center(R.drawable.ic_account_fill, R.drawable.ic_account)
|
|
}
|
|
|
|
/**
|
|
* 底部菜单按钮
|
|
*
|
|
* @param _menu
|
|
* @param menu
|
|
* @param modifier
|
|
* @param onClick
|
|
*/
|
|
@Composable
|
|
fun MenuIconButton(_menu: MainMenu, menu: MainMenu, modifier: Modifier, onClick: () -> Unit) {
|
|
Row(
|
|
modifier = modifier, horizontalArrangement = Arrangement.Center
|
|
) {
|
|
IconButton(onClick = onClick) {
|
|
Icon(
|
|
painter = painterResource(id = if (_menu == menu) menu.selectedIcon else menu.unSelectedIcon),
|
|
contentDescription = null
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 主界面底部菜单
|
|
*
|
|
* @param menu
|
|
* @param nav
|
|
* @param modifier
|
|
*/
|
|
@Composable
|
|
fun MainBottomAppBar(menu: MainMenu, nav: NavHostController, modifier: Modifier = Modifier) {
|
|
BottomAppBar(backgroundColor = MaterialTheme.colors.background, modifier = modifier) {
|
|
//图标宽度平等分
|
|
|
|
val weight = 1 / (MainMenu.values().size * 1.0f)
|
|
|
|
Row(Modifier.fillMaxWidth()) {
|
|
MenuIconButton(_menu = menu, menu = MainMenu.Main, Modifier.weight(weight),
|
|
onClick = { nav.navigate(MainMenu.Main.name) })
|
|
MenuIconButton(_menu = menu, menu = MainMenu.List, Modifier.weight(weight),
|
|
onClick = { nav.navigate(MainMenu.List.name) })
|
|
MenuIconButton(_menu = menu, menu = MainMenu.Center, Modifier.weight(weight),
|
|
onClick = { nav.navigate(MainMenu.Center.name) })
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* 社团菜单
|
|
*
|
|
*/
|
|
enum class AssociationMenu(val menuName: String) {
|
|
member("社团成员"),
|
|
main("社团主页"),
|
|
list("活动列表")
|
|
}
|
|
|
|
/**
|
|
* 社团顶部菜单
|
|
*
|
|
*/
|
|
@Composable
|
|
fun AssociationAppBar(
|
|
menu: AssociationMenu,
|
|
nav: NavHostController,
|
|
back: () -> Unit,
|
|
dropMenu: () -> Unit
|
|
) {
|
|
TopAppBar(backgroundColor = MaterialTheme.colors.secondary) {
|
|
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
|
IconButton(onClick = back, modifier = Modifier.weight(0.1F)) {
|
|
Icon(
|
|
painter = painterResource(id = R.drawable.ic_arrow_left),
|
|
contentDescription = null
|
|
)
|
|
}
|
|
Row(
|
|
modifier = Modifier
|
|
.weight(0.8F)
|
|
.fillMaxHeight(),
|
|
horizontalArrangement = Arrangement.Center,
|
|
verticalAlignment = Alignment.CenterVertically
|
|
) {
|
|
val menus = AssociationMenu.values()
|
|
menus.forEach {
|
|
Row(
|
|
modifier = Modifier
|
|
.weight(1F / menus.size)
|
|
.clickable(onClick = { nav.navigate(it.name) }),
|
|
horizontalArrangement = Arrangement.Center
|
|
) {
|
|
Text(
|
|
text = it.menuName,
|
|
color = if (menu == it) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground
|
|
)
|
|
}
|
|
}
|
|
}
|
|
IconButton(onClick = dropMenu, modifier = Modifier.weight(0.1F)) {
|
|
Icon(
|
|
painter = painterResource(id = R.drawable.ic_configuration),
|
|
contentDescription = null
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 跑马灯
|
|
*
|
|
* @param model
|
|
* @param content
|
|
*/
|
|
@Composable
|
|
fun Marquee(
|
|
model: MarqueeViewModel = viewModel(), content: @Composable BoxScope.(
|
|
model: MarqueeViewModel,
|
|
value: State<Float>
|
|
) -> Unit
|
|
) {
|
|
|
|
val delayMillis = 2000
|
|
Column {
|
|
BoxWithConstraints {
|
|
val transition = rememberInfiniteTransition()
|
|
val maxWidth = maxWidth + 30.dp
|
|
val offset = transition.animateFloat(
|
|
initialValue = 0F,
|
|
targetValue = maxWidth.value,
|
|
animationSpec = infiniteRepeatable(
|
|
animation = tween(durationMillis = 3000, delayMillis = delayMillis),
|
|
repeatMode = RepeatMode.Restart
|
|
)
|
|
)
|
|
Box(modifier = Modifier.fillMaxWidth()) {
|
|
content(model = model, value = offset)
|
|
}
|
|
|
|
if (offset.value.toInt() == maxWidth.value.toInt()) {
|
|
model.addAsync(delayMillis = delayMillis)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 跑马灯布局
|
|
*
|
|
* @param model
|
|
* @param offset
|
|
*/
|
|
@Composable
|
|
fun MarqueeText(model: MarqueeViewModel = viewModel(), offset: State<Float>) {
|
|
val poetryIndex: Int by model.marqueeIndex.observeAsState(0)
|
|
|
|
// Text(text = "$poetryIndex")
|
|
// Text(text = "offset.value=${offset.value}")
|
|
Text(
|
|
text = model.marqueeTexts[poetryIndex % model.marqueeTexts.size],
|
|
modifier = Modifier.offset(x = offset.value.dp),
|
|
maxLines = 1,
|
|
overflow = TextOverflow.Ellipsis
|
|
)
|
|
|
|
}
|
|
|
|
/**
|
|
* 导航界面框架
|
|
*
|
|
* @param background 背景
|
|
* @param mainMenu 菜单
|
|
* @param nav 导航
|
|
* @param body 内容
|
|
*/
|
|
@Composable
|
|
fun MainFrame(
|
|
background: @Composable () -> Unit,
|
|
mainMenu: MainMenu,
|
|
nav: NavHostController,
|
|
body: @Composable ColumnScope.() -> Unit
|
|
) {
|
|
Box(modifier = Modifier.fillMaxSize()) {
|
|
background()
|
|
Column {
|
|
Column(modifier = Modifier.weight(0.9F), content = body)
|
|
MainBottomAppBar(
|
|
menu = mainMenu,
|
|
nav = nav
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 界面框架
|
|
*
|
|
* @param background
|
|
* @param body
|
|
*/
|
|
@Composable
|
|
fun MainFrame(background: @Composable () -> Unit, body: @Composable ColumnScope.() -> Unit) {
|
|
Box(modifier = Modifier.fillMaxSize()) {
|
|
background()
|
|
Column(content = body)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 图片轮播
|
|
*
|
|
*/
|
|
@Composable
|
|
fun Carousel(
|
|
model: CarouselViewModel = viewModel(),
|
|
durationMillis: Int = 2000,
|
|
content: @Composable (id: Int) -> Unit
|
|
) {
|
|
val index: Int by model.index.observeAsState(0)
|
|
Crossfade(targetState = index, animationSpec = tween(durationMillis = durationMillis)) {
|
|
content(id = model.imageList[it])
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 通用文本输入框
|
|
*
|
|
* @param T
|
|
* @param modifier
|
|
* @param form
|
|
* @param singeLine
|
|
*/
|
|
@Composable
|
|
fun <T : StringForm> BaseTextField(
|
|
modifier: Modifier = Modifier,
|
|
form: T,
|
|
singeLine: Boolean = false
|
|
) {
|
|
val name: String by form.formValue.observeAsState("")
|
|
val focusManager = LocalFocusManager.current
|
|
OutlinedTextField(
|
|
modifier = modifier,
|
|
value = name,
|
|
onValueChange = { form.onChange(it) },
|
|
label = { Text(text = form.formDesc) },
|
|
placeholder = { Text(text = form.formPlaceholder) },
|
|
singleLine = singeLine,
|
|
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
|
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
|
|
trailingIcon = { Text(text = "${name.length}/${form.textLength}") })
|
|
}
|
|
|
|
/**
|
|
* 底部提示
|
|
*
|
|
* @param model
|
|
* @param scaffoldState
|
|
*/
|
|
@Composable
|
|
fun ShowSnackbar(model: ScaffoldModel = viewModel(), scaffoldState: ScaffoldState) {
|
|
val snackBar: SnackBar? by model.data.observeAsState()
|
|
snackBar?.apply {
|
|
if (message != null) {
|
|
LaunchedEffect(scaffoldState) {
|
|
launch {
|
|
if (actionLabel != null) {
|
|
val result=scaffoldState.snackbarHostState.showSnackbar(
|
|
message = message, actionLabel = actionLabel,
|
|
duration = duration
|
|
|
|
)
|
|
when(result){
|
|
SnackbarResult.ActionPerformed->{
|
|
Logger.i("点击操作按钮")
|
|
callback()
|
|
}
|
|
SnackbarResult.Dismissed->{
|
|
Logger.d("窗口消失")
|
|
}
|
|
}
|
|
} else {
|
|
scaffoldState.snackbarHostState.showSnackbar(message = message)
|
|
}
|
|
model.update()
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 界面背景
|
|
*
|
|
* @property id 资源id
|
|
*/
|
|
enum class BackgroundImage(@DrawableRes val id: Int) {
|
|
//主页
|
|
main(R.drawable.mb_bg_fb_08),
|
|
|
|
//社团列表
|
|
list(R.drawable.mb_bg_fb_07),
|
|
|
|
//个人中心
|
|
center(R.drawable.mb_bg_fb_28),
|
|
|
|
//注册社团
|
|
reg_association(R.drawable.mb_bg_fb_06),
|
|
|
|
//社团主界面
|
|
association_main(R.drawable.mb_bg_fb_25_180),
|
|
|
|
//社团重命名
|
|
rename(R.drawable.mb_bg_fb_27),
|
|
|
|
//社团题库管理
|
|
exam(R.drawable.mb_bg_fb_02)
|
|
|
|
}
|
|
|
|
/**
|
|
* 界面背景图
|
|
*
|
|
* @param image
|
|
* @param alpha
|
|
*/
|
|
@Composable
|
|
fun Background(image: BackgroundImage, alpha: Float = DefaultAlpha) {
|
|
val app = LocalContext.current.applicationContext as APP
|
|
|
|
BoxWithConstraints {
|
|
Image(
|
|
bitmap = app.getImage(
|
|
image = image,
|
|
reqHeight = maxHeight.value.toInt() / 2,
|
|
reqWidth = maxWidth.value.toInt() / 2
|
|
),
|
|
contentDescription = null,
|
|
contentScale = ContentScale.FillHeight,
|
|
alpha = alpha,
|
|
modifier = Modifier.fillMaxSize()
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*
|
|
* @param content
|
|
*/
|
|
@Composable
|
|
fun Body(content: @Composable (scaffoldState: ScaffoldState) -> Unit) {
|
|
Surface(color = MaterialTheme.colors.background) {
|
|
val scaffoldState = rememberScaffoldState()
|
|
|
|
Scaffold(scaffoldState = scaffoldState) {
|
|
content(scaffoldState = scaffoldState)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 带导航的主体
|
|
*
|
|
* @param content
|
|
*/
|
|
@Composable
|
|
fun Body(content: @Composable (nav: NavHostController, scaffoldState: ScaffoldState) -> Unit) {
|
|
Surface(color = MaterialTheme.colors.background) {
|
|
val navController = rememberNavController()
|
|
val scaffoldState = rememberScaffoldState()
|
|
|
|
Scaffold(scaffoldState = scaffoldState) {
|
|
content(nav = navController, scaffoldState = scaffoldState)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 活动海报
|
|
*
|
|
*/
|
|
@Composable
|
|
fun Poster(modifier: Modifier = Modifier, @DrawableRes id: Int) {
|
|
Card(
|
|
modifier = modifier,
|
|
backgroundColor = Color.Transparent
|
|
) {
|
|
Box(contentAlignment = Alignment.Center) {
|
|
Image(
|
|
painter = painterResource(id = R.drawable.hot_activity_background),
|
|
contentDescription = null,
|
|
modifier = Modifier.fillMaxSize()
|
|
)
|
|
Image(
|
|
painter = painterResource(id = id),
|
|
contentDescription = null,
|
|
modifier = Modifier.fillMaxSize()
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 介绍卡片
|
|
*
|
|
*/
|
|
@Composable
|
|
fun DescCard(modifier: Modifier) {
|
|
Card(
|
|
modifier = modifier,
|
|
backgroundColor = Color.Transparent
|
|
) {
|
|
Image(
|
|
painter = painterResource(id = R.drawable.hot_activity_desc_background),
|
|
contentDescription = null
|
|
)
|
|
|
|
Row(modifier = Modifier.fillMaxWidth()) {
|
|
Spacer(modifier = Modifier.weight(0.2F))
|
|
Column(
|
|
modifier = Modifier
|
|
.weight(0.5F)
|
|
) {
|
|
Spacer(modifier = Modifier.weight(0.1F))
|
|
Text(
|
|
text = "文字对任何界面都属于核心内容,而利用 Jetpack Compose 可以更轻松地显示或写入文字。Compose 可以充分利用其构建块的组合,这意味着您无需覆盖各种属性和方法,也无需扩展大型类,即可拥有特定的可组合项设计以及按您期望的方式运行的逻辑。"
|
|
.repeat(10), overflow = TextOverflow.Ellipsis,
|
|
modifier = Modifier.weight(0.8F)
|
|
)
|
|
Spacer(modifier = Modifier.weight(0.1F))
|
|
}
|
|
Spacer(modifier = Modifier.weight(0.2F))
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@Preview
|
|
@Composable
|
|
fun AnimationTextPreview() {
|
|
AnimationText(text = "6666")
|
|
}
|
|
|
|
//@Preview
|
|
@Composable
|
|
fun MyBottomAppBarPreview() {
|
|
CSAMSTheme {
|
|
|
|
}
|
|
} |