Android Jetpack Compose——一个简单的微信界面
Posted FranzLiszt1847
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Jetpack Compose——一个简单的微信界面相关的知识,希望对你有一定的参考价值。
一个简单的微信界面
简述
此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")
导航栏
构建底部导航栏,其中NavController
是Compose
用来导航路由(页面切换),unselectedContentColor
和selectedContentColor
分别对应当此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)
)
聊天
聊天数据全为静态数据,通过ViewModel
中mutableStateOf
创建一个有状态的数据进行存放,然后当当聊天框发送信息后,获取此ViewModel
的实体,在此记录尾部添加一条信息,然后,监听此实体的组件就会进行重组,然后进行刷新改变
效果图
实现
布局主体是TopBar+LazyColumn+BottomBar
;
/**
* 聊天界面*/
@Composable
fun ChatPagePreview(navHostController: NavHostController, chatModel: ChatModel)
val chat = chatModel.chatting
if (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.userBean
navHostController.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<以上是关于Android Jetpack Compose——一个简单的微信界面的主要内容,如果未能解决你的问题,请参考以下文章
Android Jetpack Compose学习—— Jetpack compose基础布局
Android Jetpack Compose学习—— Jetpack compose入门
Android Jetpack Compose学习—— Jetpack compose入门
Android Kotlin Jetpack Compose 使用