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")

导航栏

构建底部导航栏,其中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.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 使用

Jetpack Compose 和 Compose Navigation 如何处理 Android 活动?

Google开源,Android Jetpack Compose最新开发应用指南