一个简单的微信界面

  • 简述
  • 效果视频
  • 底部导航栏
    • 导航元素
    • 导航栏
    • 放入插槽
    • 绘制地图
  • 消息列表
    • 效果图
    • 实现
  • 聊天
    • 效果图
    • 实现
      • 气泡背景
  • 联系人界面
    • 效果图
    • 实现
  • 好友详情
    • 效果图
    • 实现
  • 发现
    • 效果图
    • 实现
      • 未读红点
      • 未读条数
  • 朋友圈
    • 效果图
    • 实现
      • 上拉加载
  • 个人设置
    • 效果图
    • 实现
      • 个人信息
      • 功能区
  • 钱包
    • 效果图
    • 实现
  • 切换主题
    • 效果图
    • 实现
  • 一键切换主题
    • 建立颜色实体类
    • 创建主题样式
    • 主题
    • 主题状态
    • 切换主题
    • 应用
  • 沉浸式状态栏
    • 依赖
  • Git链接

简述

此Demo用于熟悉Jetpack Compose,故仿造微信写了部分界面,其中Icon、Theme部分引用扔老师视频中元素

效果视频

Android Compose——一个简单的微信界面

底部导航栏

导航元素

使用封闭类建立底部导航栏四个元素

sealed class BottomNavItem(var title:String,var normalIcon:Int,var selectIcon:Int,var route:String){object Message: BottomNavItem("微信", R.drawable.ic_chat_outlined,R.drawable.ic_chat_filled,"Message")object MailList: BottomNavItem("通讯录", R.drawable.ic_contacts_outlined,R.drawable.ic_contacts_filled,"MailList")object Finding: BottomNavItem("发现", R.drawable.ic_discovery_outlined,R.drawable.ic_discovery_filled,"Finding")object Mine: BottomNavItem("我", R.drawable.ic_me_outlined,R.drawable.ic_me_filled,"Mine")
}

导航栏

构建底部导航栏,其中NavControllerCompose用来导航路由(页面切换),unselectedContentColorselectedContentColor分别对应当此Item未选中和被选中两种状态颜色,使用主题颜色填充,方便后面切换主题的时候,发生相应变化

/*** 底部导航条*/
@Composable
fun BottomNavBar(navController: NavController){/*** 底部导航元素*/val items = listOf(BottomNavItem.Message,BottomNavItem.MailList,BottomNavItem.Finding,BottomNavItem.Mine)BottomNavigation(backgroundColor = BaseElementComposeTheme.colors.bottomBar) {//存储了导航中回退栈的信息val navBackStackEntry by navController.currentBackStackEntryAsState()//获取当前的路由状态val currentRoute = navBackStackEntry?.destination?.route/*** 遍历列表生成四个底部Item*/items.forEach { item ->val curSelected = currentRoute == item.route;BottomNavigationItem(icon = {Icon(painterResource(id = if(curSelected) item.selectIcon else item.normalIcon),item.title, modifier = Modifier.size(24.dp)) },label = { Text(item.title, fontSize = 12.sp) },alwaysShowLabel = true,selected = curSelected,unselectedContentColor = BaseElementComposeTheme.colors.icon,selectedContentColor = BaseElementComposeTheme.colors.iconCurrent,onClick = {navController.navigate(item.route){//弹出到图形的开始目的地// 避免建立大量目的地// 在用户选择项目时显示在后堆栈上navController.graph.startDestinationRoute?.let {route ->popUpTo(route){saveState = true}}//在以下情况下避免同一目标的多个副本//重新选择同一项目launchSingleTop = true//重新选择以前选定的项目时恢复状态restoreState = true}})}}
}

放入插槽

Scaffold相当于一个插槽,因为它在屏幕中预留了很多空位,比如底部导航栏、顶栏、FAB等,只需要通过命名可选参数填充即可

@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun MainScreenView(chatController: NavHostController,chatModel: ChatModel){val navController = rememberNavController()Scaffold(bottomBar = {BottomNavBar(navController)},){NavigationGraph(navController,chatController,chatModel)}
}

绘制地图

每一个NavHostController都必须绑定一个NavHost,其中NavHost就相当于是绘制了一个联通图,每一个结点之间都是联通的,而NavHostController就是哪个驱动,负责切换两个结点,此处结点是声明的Compose函数,一般为一个页面的入口处;下面定义了四个结点,也就是底部导航栏四个结点,NavHostController只能切换绑定的NavHost中已经声明的结点,没有声明的结点,不能相互进行切换

/*** 每个 NavController 都必须与一个 NavHost 可组合项相关联* route:路线是一个 String,用于定义指向可组合项的路径。您可以将其视为指向特定目的地的隐式深层链接。每个目的地都应该有一条唯一的路线。*/
@Composable
fun NavigationGraph(navHostController: NavHostController,chatController: NavHostController,chatModel: ChatModel){/*** 底部导航栏四个界面路线图*/NavHost(navHostController, startDestination = BottomNavItem.Message.route){composable(BottomNavItem.Message.route){MessagePageView(chatController,chatModel)}composable(BottomNavItem.MailList.route){MailListPageView(chatController,chatModel)}composable(BottomNavItem.Finding.route){FindingPageView(chatController)}composable(BottomNavItem.Mine.route){MinePageView(chatController)}}
}

消息列表

效果图

实现

布局主体是Column+TopBar+LazyColumn,其中Spacer组件用占位,例如两个组件之间需要隔开一点间距,则可使用,具体是上下还是左右,设置其modifier的width和height属性即可

@Composable
fun MessagePageView(chatController: NavHostController, chatModel: ChatModel){Column(Modifier.background(BaseElementComposeTheme.colors.background).fillMaxSize()) {TopTitleBar("微信",R.drawable.ic_add)Spacer(modifier = Modifier.height(10.dp))MessageList(Modifier.weight(1f),chatModel){chatController.navigate(RoutePoint.Chat.route)}}
}

LazyColumn对应的是RecyclerView,但使用起来更方便,无需建立Adapter,而且还可在其中插入不同类型的子组件;其中Divider组件是分界线,例如两个组件之间需要一条直线进行分割,即可使用

@Composable
fun MessageList(modifier: Modifier,chatModel: ChatModel,chatCallback: ()->Unit){LazyColumn(modifier.background(BaseElementComposeTheme.colors.listItem).padding(10.dp),verticalArrangement = Arrangement.spacedBy(10.dp),){itemsIndexed(chatModel.chats){ index,item->MessageItem(item){chatModel.startChat(it)chatCallback()}if(index < chatModel.chats.size-1)Divider(startIndent = 68.dp,thickness = 0.8f.dp,color = BaseElementComposeTheme.colors.chatListDivider,)}}
}

其中ConstraintLayout对应命令式UI中的约束布局,使用效果一致,首先通过createRefs创建引用实体,然后在每个modifier.constrainAs()属性中进行引用;clip(shape = RoundedCornerShape(4.dp))用于给图片四个角进行圆角处理,具体数值可通过参数进行传入;实现modifier.clickable即可实现点击事件

/*** 使用ConstraintLayout布局构建Item*/
@Composable
fun MessageItem(chatBean: ChatBean,chatCallback: (ChatBean)->Unit){ConstraintLayout(modifier = Modifier.fillMaxWidth().clickable {chatCallback(chatBean)}) {//声明ConstraintLayout实例val (image,title,content,time) = createRefs()Image(painter = painterResource(chatBean.userBean.wechatIcon),contentDescription = chatBean.userBean.wechatName,contentScale = ContentScale.Crop,modifier = Modifier.padding(4.dp).size(48.dp).clip(shape = RoundedCornerShape(4.dp)).constrainAs(image) {//引用实例进行排版top.linkTo(parent.top)start.linkTo(parent.start)bottom.linkTo(parent.bottom)})useText(text = chatBean.userBean.wechatName, fontSize = 16, color = BaseElementComposeTheme.colors.textPrimary, modifier = Modifier.constrainAs(title){top.linkTo(image.top,2.dp)start.linkTo(image.end,10.dp)})useText(text = chatBean.messageBeans.last().text,fontSize = 14, color = BaseElementComposeTheme.colors.textSecondary,modifier = Modifier.fillMaxWidth().constrainAs(content) {top.linkTo(title.bottom)bottom.linkTo(image.bottom, 2.dp)start.linkTo(image.end, 10.dp)width = Dimension.fillToConstraints})useText(text = chatBean.messageBeans.last().time, fontSize = 12,color = BaseElementComposeTheme.colors.textSecondary,modifier = Modifier.constrainAs(time){top.linkTo(image.top)end.linkTo(parent.end)})}
}

聊天

聊天数据全为静态数据,通过ViewModelmutableStateOf创建一个有状态的数据进行存放,然后当当聊天框发送信息后,获取此ViewModel的实体,在此记录尾部添加一条信息,然后,监听此实体的组件就会进行重组,然后进行刷新改变

效果图

实现

布局主体是TopBar+LazyColumn+BottomBar

/*** 聊天界面*/
@Composable
fun ChatPagePreview(navHostController: NavHostController, chatModel: ChatModel){val chat = chatModel.chattingif (chat != null){Column(modifier = Modifier.fillMaxSize().background(BaseElementComposeTheme.colors.background)) {TitleBar(title = chat.userBean.wechatName,searchId = R.drawable.icon_more,Modifier.padding(end = 10.dp)){chatModel.contacting = chat.userBeannavHostController.navigate(RoutePoint.ContactDetail.route)}Spacer(modifier = Modifier.height(5.dp))ChatList(chat,Modifier.weight(1f))BottomInputBar{val time = calculateTime()chat.messageBeans.add(MessageBean(UserBean.ME,it,time))}}}else{Box(modifier = Modifier.fillMaxSize().background(BaseElementComposeTheme.colors.background),Alignment.Center){useText(text = "内容加载失败,请重试!",color = BaseElementComposeTheme.colors.textPrimaryMe,modifier = Modifier.fillMaxWidth(),textAlign = TextAlign.Center)}}
}

BasicTextField输入框的软键盘的回车键改为发送,然后对发送键进行点击事件监听

   keyboardActions = KeyboardActions (onSend = {onInputListener(inputText)inputText = ""}),keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text,imeAction = ImeAction.Send)

己方发送消息,并对有状态的列表进行改变

`chat.messageBeans.add(MessageBean(UserBean.ME,it,time))`

通过判断list数据中发送消息的人是否为"自己"而进行左右排放

/*** 聊天记录*/
@Composable
fun ChatList(bean: ChatBean,modifier: Modifier){LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp),modifier = modifier.background(BaseElementComposeTheme.colors.chatPage).padding(top = 10.dp, start = 10.dp, end = 10.dp)){items(bean.messageBeans.size){if (bean.messageBeans[it].userBean == UserBean.ME){MeMessage(bean.messageBeans[it])}else{OtherMessage(bean.messageBeans[it])}}}
}

气泡背景

此为己方发送消息是的消息气泡背景

fun Modifier.meBackground(color: Color):Modifier = this.drawBehind {val bubble = Path().apply {val rect = RoundRect(10.dp.toPx(),0f,size.width - 10.dp.toPx(),size.height,4.dp.toPx(),4.dp.toPx())addRoundRect(rect)moveTo(size.width - 10.dp.toPx(), 15.dp.toPx())lineTo(size.width - 5.dp.toPx(), 20.dp.toPx())lineTo(size.width - 10.dp.toPx(), 25.dp.toPx())close()}drawPath(bubble, color)}.padding(20.dp, 10.dp)

此为对方发送消息是的消息气泡背景

fun Modifier.otherBackground(color: Color):Modifier = this.drawBehind {val bubble = Path().apply {val rect = RoundRect(10.dp.toPx(),0f,size.width - 10.dp.toPx(),size.height,4.dp.toPx(),4.dp.toPx())addRoundRect(rect)moveTo(10.dp.toPx(), 15.dp.toPx())lineTo(5.dp.toPx(), 20.dp.toPx())lineTo(10.dp.toPx(), 25.dp.toPx())close()}drawPath(bubble, color)}.padding(20.dp, 10.dp)

联系人界面

效果图

实现

布局主体部分由Column+TopBar+LazyColumn

@Composable
fun MailListPageView(chatController: NavHostController,chatModel: ChatModel){Column(Modifier.background(BaseElementComposeTheme.colors.background).fillMaxSize(),) {TopTitleBar("通讯录", R.drawable.icon_add_friend)Spacer(modifier = Modifier.height(10.dp))ContactList(){UserBean.AllFriend.forEach { bean ->if(bean.wechatName == it){chatModel.contacting = beanchatController.navigate(RoutePoint.ContactDetail.route)}}}}
}

LazyColumn由下面可展现其优势,item、items可插入不同的子组件,整体呈垂直排列

@Composable
fun ContactList(onClick:(String)->Unit){LazyColumn(modifier = Modifier.background(BaseElementComposeTheme.colors.listItem),contentPadding = PaddingValues(bottom = 50.dp)){itemsIndexed(contactList) { index, item -> ContactItem(item, index, contactList.size){} }item { useText("我的企业", fontSize = 14, color = BaseElementComposeTheme.colors.textSecondary,modifier = Modifier.background(BaseElementComposeTheme.colors.background).fillMaxWidth().padding(10.dp)) }itemsIndexed(schoolList) { index, item -> ContactItem(item, index, schoolList.size){} }item { useText("我的好友", fontSize = 14, color = BaseElementComposeTheme.colors.textSecondary,modifier = Modifier.background(BaseElementComposeTheme.colors.background).fillMaxWidth().padding(10.dp)) }itemsIndexed(friendList) { index, item -> ContactItem(item, index, friendList.size){onClick(item.title)} }}
}

好友详情

效果图

实现

通过在ViewModel创建一个有状态的用户变量,点击哪个联系人,就把当前联系人信息赋值给VM中的值,然后联系人详情界面读取此VM值即可

/*** 联系人详情*/
@Composable
fun ContactDetailPreview(chatModel: ChatModel) {val contact = chatModel.contactingif (contact != null){Box(modifier = Modifier.fillMaxSize().background(BaseElementComposeTheme.colors.background)){Column(modifier = Modifier.background(BaseElementComposeTheme.colors.listItem)) {DetailTopBar()Spacer(modifier = Modifier.height(20.dp))contactInfo(bean = contact)Spacer(modifier = Modifier.height(30.dp))Divider(thickness = 0.2.dp,color = BaseElementComposeTheme.colors.chatListDivider,)ContactFuncList()VideoAndMessage()}}}
}

发现

效果图

实现

@Composable
fun FindingPageView(chatController: NavHostController){Column(Modifier.background(BaseElementComposeTheme.colors.background).fillMaxSize()) {TitleBar("发现",-1,Modifier.padding(10.dp)){}Spacer(modifier = Modifier.height(10.dp))FindingList(chatController)}
}@Composable
fun FindingList(chatController: NavHostController){LazyColumn(modifier = Modifier.background(BaseElementComposeTheme.colors.listItem)){itemsIndexed(findingList){index, item ->  FindingItem(item){if (it == "朋友圈"){chatController.navigate(RoutePoint.SpacePage.route)} }if (index == 2){Divider(thickness = 0.2.dp,color = BaseElementComposeTheme.colors.chatListDivider,)}else if (index < findingList.size){Spacer(modifier = Modifier.height(10.dp).fillMaxWidth().background(BaseElementComposeTheme.colors.background))}}}
}

未读红点

未读消息红点

fun Modifier.unread(show: Boolean, color: Color): Modifier = this.drawWithContent {drawContent()if (show) drawCircle(color, 5.dp.toPx(), Offset(size.width - 1.dp.toPx(), 1.dp.toPx()))
}

未读条数

一个红色圆圈内包含一个数字,可通过modifier的属性完成构建

@Composable
fun AgreeNumber(number:Int){useText(text = "$number", color = BaseElementComposeTheme.colors.onBadge, textAlign = TextAlign.Center,modifier = Modifier.background(BaseElementComposeTheme.colors.badge, shape = CircleShape).size(18.dp))
}

朋友圈

效果图

实现

数据为存放在ViewModel中的有状态的数据变量中;上拉加载中,然后改变VM中的值,进而进行刷新,添加到第一个数据实体

上拉加载

使用协程模拟网络加载,并在协程中添加数据,即每下拉一次自己发送一个动态,即添加一条数据

 var refreshing by remember { mutableStateOf(false) }//启用协程val scope = rememberCoroutineScope()val state = rememberPullRefreshState(refreshing = refreshing, onRefresh = {scope.launch {/*** 在协程中使用延迟模拟网络延迟,然后加载数据*/refreshing = truedelay(1000)chatModel.spaceList.add(0,SpaceBean(UserBean.ME,"让我们看看这是几啊:${chatModel.count.value++}","刚刚",null))refreshing = false}})

为最外层布局添加下拉刷新状态

 ConstraintLayout(modifier = Modifier.fillMaxSize().pullRefresh(state)){...}

下拉加载指示器,因为我的外层使用的是ConstraintLayout约束布局,所以使用以下方式放到顶端中部位置,backgroundColor为背景颜色,contentColor为指示器内部元素颜色

   PullRefreshIndicator(refreshing = refreshing,state = state,backgroundColor = green4,contentColor = white,modifier = Modifier.constrainAs(refreshRef){top.linkTo(parent.top)start.linkTo(parent.start)end.linkTo(parent.end)})

个人设置

效果图

实现

分为两部分,顶部个人信息区域、下面功能区域

个人信息

使用ConstraintLayout布局进行构建,约束布局对复杂页面构建较为方便,嵌套较少,当然在Compose中嵌套深浅对性能的影响不是很大,与命令式UI有显著差距

@Composable
fun MineInfo(){ConstraintLayout(modifier = Modifier.background(BaseElementComposeTheme.colors.listItem).fillMaxWidth().padding(20.dp).height(100.dp)) {val (iconRef,nameRef,idRef,qrcodeRef,addStatusRef,statesRef,moreRef) = createRefs()Image(painter = painterResource(id = UserBean.ME.wechatIcon),contentDescription = UserBean.ME.wechatName,contentScale = ContentScale.Crop,modifier = Modifier.size(64.dp).clip(RoundedCornerShape(4.dp)).constrainAs(iconRef) {top.linkTo(parent.top)bottom.linkTo(parent.bottom)start.linkTo(parent.start)})useText(text = UserBean.ME.wechatName,color = BaseElementComposeTheme.colors.textPrimary,fontSize = 18,fontWeight = FontWeight.Bold,modifier = Modifier.constrainAs(nameRef){top.linkTo(iconRef.top,2.dp)start.linkTo(iconRef.end,15.dp)})useText(text = "微信号: ${UserBean.ME.wechatId}",color = BaseElementComposeTheme.colors.textSecondary,fontSize = 14,modifier = Modifier.constrainAs(idRef){top.linkTo(nameRef.bottom)bottom.linkTo(iconRef.bottom,2.dp)start.linkTo(iconRef.end,15.dp)})Icon(painter = painterResource(id = R.drawable.ic_qrcode),contentDescription = "QRCode",tint = BaseElementComposeTheme.colors.onBackground,modifier = Modifier.size(16.dp).constrainAs(qrcodeRef) {top.linkTo(idRef.top)//start.linkTo(idRef.end)end.linkTo(moreRef.start, 20.dp)})Icon(painter = painterResource(id = R.drawable.ic_arrow_more),contentDescription = "更多",tint = BaseElementComposeTheme.colors.more,modifier = Modifier.size(16.dp).constrainAs(moreRef) {top.linkTo(idRef.top)end.linkTo(parent.end, (-10).dp)})addStates(icon = R.drawable.icon_addition,text = "状态",modifier = Modifier.constrainAs(addStatusRef){top.linkTo(idRef.bottom,10.dp)start.linkTo(idRef.start)})addStates(icon = R.drawable.image_friend_three,text = "1个朋友",modifier = Modifier.constrainAs(statesRef){top.linkTo(addStatusRef.top)start.linkTo(addStatusRef.end,10.dp)})}
}

功能区

NavHostControllernavigate用于导航路由,传入的参数为目的地页面定义时的昵称,类型为String类型

@Composable
fun MineList(chatController: NavHostController){LazyColumn(modifier = Modifier.background(BaseElementComposeTheme.colors.listItem).wrapContentHeight().fillMaxWidth()) {item {Spacer(modifier = Modifier.height(10.dp).fillMaxWidth().background(BaseElementComposeTheme.colors.background))}itemsIndexed(mineList){index, item ->  MineItem(bean = item){when(it){"服务" -> chatController.navigate(RoutePoint.ServicePage.route)"设置" -> chatController.navigate(RoutePoint.ThemePage.route)} }if (index == 0 || index == mineList.size-2){Spacer(modifier = Modifier.height(10.dp).fillMaxWidth().background(BaseElementComposeTheme.colors.background))}else if (index < mineList.size-1){Divider(thickness = 0.2.dp,color = BaseElementComposeTheme.colors.chatListDivider,)}}}
}

钱包

效果图

实现

布局主体是Column+LazyColumn+LazyVerticalGrid

@Composable
fun ServiceList(){LazyColumn(){item {WalletArea()Spacer(modifier = Modifier.height(10.dp))}items(serviceList.size){ServiceItem(serviceList[it])if (it < serviceList.size - 1){Spacer(modifier = Modifier.height(10.dp))}}}
}

LazyVerticalGrid对应GridView,使用GridCells.Fixed(4)定义列数

@Composable
fun ServiceItem(bean: ServiceBean){Column(modifier = Modifier.fillMaxWidth().background(BaseElementComposeTheme.colors.listItem, shape = RoundedCornerShape(10.dp)).padding(10.dp).height(if (bean.services.size > 4) 200.dp else 100.dp)){useText(text = bean.name,color = grey5,textAlign = TextAlign.Start)Spacer(modifier = Modifier.height(20.dp))LazyVerticalGrid(columns = GridCells.Fixed(4),verticalArrangement = Arrangement.spacedBy(40.dp),horizontalArrangement = Arrangement.SpaceEvenly,modifier = Modifier.fillMaxWidth()){items(bean.services.size){Service(bean.services[it])}}}
}

切换主题

效果图

实现

四个颜色块对应四个主题,然后使用LazyVerticalGrid进行布局构建

@Composable
fun ThemePagePreview(chatModel: ChatModel) {var text by remember { mutableStateOf("古典灰") }Box(modifier = Modifier.background(BaseElementComposeTheme.colors.background).fillMaxSize().padding(start = 20.dp, end = 20.dp)){Column(modifier = Modifier.background(white, shape = RoundedCornerShape(10.dp)).fillMaxWidth().wrapContentHeight().padding(20.dp).align(Alignment.Center).shadow(elevation = 5.dp, ambientColor = green4, spotColor = Color.Transparent),horizontalAlignment = Alignment.CenterHorizontally){useText(text = "请选择一个主题", color = BaseElementComposeTheme.colors.textPrimaryMe, fontSize = 14)Spacer(modifier = Modifier.height(5.dp))useText(text = text, color = BaseElementComposeTheme.colors.textSecondary, fontSize = 12)Spacer(modifier = Modifier.height(10.dp))ThemeList(){/*** 将选择的主题进行刷新,然后保存到缓存中*/chatModel.theme.value = when(it){0-> {text = "古典灰"SPUtil.getInstance().PutData("Theme",0)BaseElementComposeTheme.Theme.Light}1-> {text = "哑光黑"SPUtil.getInstance().PutData("Theme",1)BaseElementComposeTheme.Theme.Dark}2-> {text = "活力红"SPUtil.getInstance().PutData("Theme",2)BaseElementComposeTheme.Theme.NewYear}3-> {text = "青春绿"SPUtil.getInstance().PutData("Theme",3)BaseElementComposeTheme.Theme.Green}else -> {text = "古典灰"SPUtil.getInstance().PutData("Theme",0)BaseElementComposeTheme.Theme.Light}}}}}
}@Composable
fun ThemeList(onClick:(Int)->Unit){val colors = listOf(white2,black2,red5,green4)LazyVerticalGrid(columns = GridCells.Fixed(2),verticalArrangement = Arrangement.spacedBy(20.dp),horizontalArrangement = Arrangement.spacedBy(20.dp)){items(colors.size){Card(backgroundColor = colors[it],shape = RoundedCornerShape(10.dp),modifier = Modifier.size(100.dp).clickable {onClick(it)}) {}}}
}

一键切换主题

建立颜色实体类

首先建立一个颜色实体类,包括你所要使用的所有颜色,以下为例

@Stable
class BaseElementComposeColors(bottomBar: Color,background: Color
) {var bottomBar: Color by mutableStateOf(bottomBar)private setvar background: Color by mutableStateOf(background)private set
}

创建主题样式

亮色主题

private val LightColorPalette = BaseElementComposeColors(bottomBar = white1,//底部导航栏背景颜色background = white2,//主题背景颜色
)

暗黑主题

private val DarkColorPalette = BaseElementComposeColors(bottomBar = black1,background = black2,
)

红色主题

private val NewYearColorPalette = BaseElementComposeColors(bottomBar = red4,background = red5,
)

绿色主题

private val GreenColorPalette = BaseElementComposeColors(bottomBar = green4,background = green5,
)

设置默认主题

private val LocalWeComposeColors = compositionLocalOf {LightColorPalette
}

使用枚举类将创建的主题进行包裹,使用一个别名

object BaseElementComposeTheme {val colors: BaseElementComposeColors@Composableget() = LocalWeComposeColors.currentenum class Theme {Light, Dark, NewYear,Green}
}

主题

判断当前系统主题是否为暗黑主题

@Composable
fun isSystemDark():Boolean = isSystemInDarkTheme()

对主题内所有颜色进行切换,此处没有对形状、排版等主题进行切换

@Composable
fun BaseElementComposeTheme(theme: BaseElementComposeTheme.Theme = BaseElementComposeTheme.Theme.Light, content: @Composable() () -> Unit) {val targetColors = if (isSystemDark()){DarkColorPalette}else{when (theme) {BaseElementComposeTheme.Theme.Light -> LightColorPaletteBaseElementComposeTheme.Theme.Dark -> DarkColorPaletteBaseElementComposeTheme.Theme.NewYear -> NewYearColorPaletteBaseElementComposeTheme.Theme.Green -> GreenColorPalette}}/*** 动画渐变切换主题*/val bottomBar = animateColorAsState(targetColors.bottomBar, TweenSpec(600))val background = animateColorAsState(targetColors.background, TweenSpec(600))val colors = BaseElementComposeColors(bottomBar = bottomBar.value,background = background.value,)CompositionLocalProvider(LocalWeComposeColors provides colors) {MaterialTheme(shapes = shapes,content = content)}
}

主题状态

通过ViewModel中创建一个有状态的变量存储当前主题,然后从SharedPreferences中读取当前主题

    /*** 当前主题* 默认白灰色主题*/var theme = mutableStateOf(getTheme())/*** 从缓存里面读取主题*/private fun getTheme():BaseElementComposeTheme.Theme{return when(SPUtil.getInstance().GetData("Theme",-1)){0-> BaseElementComposeTheme.Theme.Light1-> BaseElementComposeTheme.Theme.Dark2-> BaseElementComposeTheme.Theme.NewYear3-> BaseElementComposeTheme.Theme.Greenelse -> BaseElementComposeTheme.Theme.Light}}

切换主题

切换当前主题,同时改变缓存中的值以及ViewModel的值,并应用到系统中

      chatModel.theme.value = when(it){0-> {SPUtil.getInstance().PutData("Theme",0)BaseElementComposeTheme.Theme.Light}1-> {SPUtil.getInstance().PutData("Theme",1)BaseElementComposeTheme.Theme.Dark}2-> {SPUtil.getInstance().PutData("Theme",2)BaseElementComposeTheme.Theme.NewYear}3-> {SPUtil.getInstance().PutData("Theme",3)BaseElementComposeTheme.Theme.Green}else -> {SPUtil.getInstance().PutData("Theme",0)BaseElementComposeTheme.Theme.Light}}

应用

然后在Activity应用主题即可

     setContent{BaseElementComposeTheme(viewModel.theme.value) {//...}}

沉浸式状态栏

依赖

    implementation "com.google.accompanist:accompanist-insets:0.15.0"implementation "com.google.accompanist:accompanist-insets-ui:0.15.0"implementation "com.google.accompanist:accompanist-systemuicontroller:0.15.0"

让屏幕内容延伸到状态栏

 WindowCompat.setDecorFitsSystemWindows(window,false)

使用ProvideWindowInsets包裹Activity根布局,然后通过remember创建当前系统栏状态的变量,下方只设置状态栏为隐藏

   setContent{BaseElementComposeTheme(viewModel.theme.value) {ProvideWindowInsets() {val systemUiController = rememberSystemUiController()SideEffect {systemUiController.setStatusBarColor(Color.Transparent, darkIcons = false)}}}}

如果布局中使用了底部导航栏,使用如上会导致底部导航栏消失;在根布局应用如下代码即可,给一个间隔就行

Modifier.navigationBarsPadding()

Git链接

Git链接

https://gitee.com/FranzLiszt1847/fake-we-chat

Android Jetpack Compose——一个简单的微信界面相关推荐

  1. Android(安卓)一个简单的聊天界面的实现(eclipse实现)

    这几天刚刚学习一下安卓的编程,尝试制作了一个简单的聊天界面(还没有实现网络等后续功能)软件界面如图.(使用eclipse实现) 当输入一些内容后,聊天界面可以下拉显示更多的聊天信息,如下图 首先对这个 ...

  2. 用Android Studio设计一个简单个性的登录界面

    一.用到的组件: LinearLaout.TableLayout.FrameLayout.RelativeLout 二.效果图展示: 三.步骤及过程: 1.首先新建一个Project,并在app -& ...

  3. Android Jetpack Compose

    Android Jetpack Compose 一.什么是Jetpack Compose 二.关于Jetpack Compase的介绍 Jetpack Compose的特点 Jetpack Compo ...

  4. Android JetPack Compose初步2~实现可滚动列表的功能

    Android JetPack Compose的配置参考Android JetPack Compose初步1 在本应用中定义可滚动的列表的界面,类似RecyclerView组件的显示效果. 一.定义实 ...

  5. Android 二维码扫描(仿微信界面),根据Google zxing

    Android 二维码扫描(仿微信界面),根据Google zxing Android项目开发中经常会用到二维码扫描,例如登陆.支付等谷歌方面已经有了一个开源库(地址: https://github. ...

  6. 使用Android Studio编写一个简单的音乐盒

    文章目录 一.知识要点 二.xml代码 activity_main.xml 三.java代码 MainActivity.java MusicService.java 四.运行界面展示 五. 源码Git ...

  7. 一对一直播源码,实现一个简单的登录界面

    一对一直播源码,实现一个简单的登录界面 1.html <!DOCTYPE html> <html lang="en"> <head><me ...

  8. Android: Jetpack Compose如何禁用涟漪(水波纹)效果

    系列文章目录 Android: Jetpack Compose如何禁用涟漪(水波纹)效果 Android:使用Jetpack Compose 实现Text控件跑马灯效果 Android:使用Jetpa ...

  9. java qq ui界面_java swing 创建一个简单的QQ界面教程

    记录自己用java swing做的第一个简易界面. LoginAction.java package com.QQUI0819; import javax.swing.*; import java.a ...

最新文章

  1. 求一个二叉树中距离最远的两个节点
  2. vst3插件_Blue Cat Audio Blue Cat PatchWork mac(蓝猫桥接插件)
  3. 【软件测试】结构化分支和循环语句的白盒测试
  4. java easyreport 导入excel、 txt 数据复合属性(二)
  5. 【Linux】Aria2 一键安装管理脚本 BT\PT一键安装包
  6. springboot-2-ioc
  7. 计算机最低分怎么英语,编写一个学生类 有年龄 英语、数学、计算机三门成绩 求平均分、最高分、最低分...
  8. ABAP术语-V1 Module
  9. 我的第一篇cnds文章
  10. MSDN下载出现链接格式有误,如何解决
  11. 【深度学习模型】了解一下Faster RCNN
  12. deprecate node-sass@4.9.0 › request@~2.79.0 request has been deprecated, see https://github.com
  13. Linux 中文件压缩方法与tar打包详解
  14. 拼多多2021校招2020.9.1笔试题 T2 and T4
  15. 2021年危险化学品经营单位主要负责人考试内容及危险化学品经营单位主要负责人考试资料
  16. 使用PN532向小米手环写入加密卡(复制门禁卡)
  17. 移动安全常用技术相关术语总结
  18. ssh白名单_Linux(Ubuntu)SSH登录白名单设置
  19. 英语语法汇总(10.被动语态)
  20. 易中天品汉代风云人物07:韩信功过之谜

热门文章

  1. 用C语言简单实现图像的旋转90度
  2. android pdf显示不全,android 显示pdf文件内容
  3. 44个 灵感来自于“大自然”的网站设计(上)
  4. Builtin/administrators 与 Domain Admins 用户组的来历与区别
  5. windows 定义计划任务脚本
  6. PHOTOSHOP MAC快捷键
  7. 服务器加30台显示器,30台手机画面同时显示在一个显示器上的解决方案
  8. 数字格式化为金额格式 (3位一个逗号隔开)
  9. Python练习题六
  10. 第2章 理解计算机系统的基本思维(第一部分)