春光不自留,莫怪东风恶

image.png

Compose for Desktop

      Compose是由Kotlin言语快速编辑界面的结构,依据谷歌的现代东西箱,由JetBrains为您带来。
Compose for Desktop简化并加快了桌面应用程序的UI开发,并答应Android和桌面应用程序之间很多的UI代码同享,这是来自官方的一些论述解释。Compose初忠是声明式UI,当然了跨渠道的纷争乱战年代,它也有着跨渠道的梦想。在桌面端还未盛行和遍及之时为何不必划水的时刻来测验一下Compose for
Desktop!

一、环境

      IntelliJ IDEA 2020.3以后的版别依据新建的类型compose desktop、compose app、compose web都能够自动依赖相关的gradle。我下载的最新版别,pojie有点辣手,之前的注册码和.jar方式都能够,假如由大佬知道破解方式能够楼下指点,先试用30天。截图看文字,信任大佬们都能看懂?左面挑选Kotlin,右边会呈现各种kotlin能干的事:Desktop、Web、Mobile….kotlin牛逼!

image.png

image.png

image.png

一、Desktop

      Name、Location、Project Template->Desktopo即可、Build System 随意、Project JDK->11以上即可,持续finish完结。

你能够点击一下下图的main函数前面的绿色运转箭头…..等待奇观呈现
image.png

运转的作用,当然了这是我没事干用贝塞尔曲线制作的那个男人,时刻问题没制作完。大家假如看过我的自定义信任制作不是难题!!

image.png

上面咱们开发环境现已结束,接下来是不是有点小激动,咱们开端代码编写。

二、Desktop UI剖析 – 微信

      微信的桌面端说不上花里胡哨,但是很优雅简约不缺美观。咱们这篇文字首要模仿这个UI进行测验Compose for Desktop

image.png

资料准备

为了达到比较一致的作用,咱们经过PS进行资料获取。
1.打开微信截图需求图标。
2.PS截图用魔术棒进行选区删除不需求部分。

image.png

3.经过选区缩放来进行调试鸿沟。

image.png
保存图片即可。逐步操作需求图片。

布局剖析

      布局咱们经常用,也知道可分为这三块从左到右都有联动。所以咱们先进行一级布局UI。
image.png

1.左边Colum又上到下合作Spacer完美
2.中心的Box内部ListView加搜索框
3.右侧ListView

三、Desktop UI编写 – 微信-Left

      Compose for Desktop简化并加快了桌面应用程序的UI开发,并答应Android和桌面应用程序之间很多的UI代码同享已然官方如此说了和Android端的UI很多同享,咱们接下来体会一下。当然了我感受了一波的却很多的组件都根本一致。就自定义方面短少一些API,暗影的设置,假如你发现了能够告知一下我,感激不尽。已然和Android一致那么接下来很多的代码,接住了..

image.png

上面剖析:1.左边Colum又上到下合作Spacer完美

实体类封装点击图片途径

/**
* @param defaultPath 默认图片途径
* @param selectedPath 挑选途径
* @param path 实践途径
* @param selected 是否选中
*/
data class WxSelectedBean(val defaultPath:String,var selectedPath:String,var path:String,var selected:Boolean)

负值图片途径

object WxViewModel : RememberObserver {
val isAppReady = mutableStateOf(false)
val position = ArrayList<WxSelectedBean>()
fun initData() {
var selectedDatas = arrayListOf<WxSelectedBean>()
selectedDatas.add(
WxSelectedBean(
"images/head_lhc.png",
"images/head_lhc.png",
"images/head_lhc.png",
false
)
)
selectedDatas.add(
WxSelectedBean(
"images/message_unselected.png",
"images/message_selected.png",
"images/message_selected.png",
true
)
)
selectedDatas.add(
WxSelectedBean(
"images/person_unselected.png",
"images/person_selected.png",
"images/person_unselected.png",
false
)
)
selectedDatas.add(
WxSelectedBean(
"images/connected_unselecte.png",
"images/connected_selected.png",
"images/connected_unselecte.png",
false
)
)
selectedDatas.add(
WxSelectedBean(
"images/file_default.png",
"images/file_default.png",
"images/file_default.png",
false
)
)
selectedDatas.add(
WxSelectedBean(
"images/frends.png",
"images/frends.png",
"images/frends.png",
false
)
)
selectedDatas.add(
WxSelectedBean(
"images/phone.png",
"images/phone.png",
"images/phone.png",
false
)
)
selectedDatas.add(
WxSelectedBean(
"images/mulu.png",
"images/mulu.png",
"images/mulu.png",
false
)
)
position.addAll(selectedDatas)
}
override fun onAbandoned() {
}
override fun onForgotten() {
}
override fun onRemembered() {
}
}

界面

import androidx.compose.animation.core.TweenSpec
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.desktop.Window
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import module_view.WxSelectedBean
import module_view.WxViewModel
fun main() = Window {
WxViewModel.initData()
var wxData by remember { mutableStateOf(WxViewModel.position) }
//选中的索引
var selectedIndex by remember { mutableStateOf(1) }
//图片选中动画执行与否
var imageAnimal by remember { mutableStateOf(true) }
//图片旋转动画
val imageAngle: Float by animateFloatAsState(
if (imageAnimal) {
0f
} else {
360f
}, animationSpec = TweenSpec(durationMillis = 1001)
)
MaterialTheme {
Scaffold {
Row {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxHeight().width(66.dp)
.background(Color(247, 242, 243))
) {
ImageRes(
getPath(wxData, selectedIndex, 0),
modifier = Modifier.padding(top = 30.dp).size(48.dp)
.clickable(role = Role.Image) {
imageAnimal = !imageAnimal
}.rotate(imageAngle)
)
ImageRes(
getPath(wxData, selectedIndex, 1),
modifier = Modifier.padding(vertical = 20.dp).size(42.dp).clickable {
selectedIndex = 1
})
ImageRes(getPath(wxData, selectedIndex, 2),
modifier = Modifier.size(32.dp).clickable {
selectedIndex = 2
})
ImageRes(
getPath(wxData, selectedIndex, 3),
modifier = Modifier.padding(vertical = 20.dp).size(30.dp).clickable {
selectedIndex = 3
}
)
ImageRes(getPath(wxData, selectedIndex, 4), modifier = Modifier.size(30.dp))
ImageRes(
getPath(wxData, selectedIndex, 5),
modifier = Modifier.padding(vertical = 20.dp).size(30.dp)
)
Spacer(modifier = Modifier.weight(1f))
ImageRes(
getPath(wxData, selectedIndex, 6),
modifier = Modifier.padding(vertical = 20.dp).size(35.dp)
)
ImageRes(
getPath(wxData, selectedIndex, 7),
modifier = Modifier.padding(vertical = 20.dp).size(30.dp)
)
}
}
}
}
}
/**
* @param wxData 数据集合
* @param selectedIndex 选中的索引
* @param currenIndex 当前Image对应的索引
* return 返回各个按钮选中和未选中图片途径
*/
private fun getPath(
wxData: ArrayList<WxSelectedBean>,
selectedIndex: Int,
currenIndex: Int
): String {
return if (selectedIndex == currenIndex) {
wxData[currenIndex].selectedPath
} else {
wxData[currenIndex].defaultPath
}
}

看看作用?

compose_desk_1.gif

四、Desktop UI编写 – 微信-Center

      中心部分如下图剖析可见Box里面一个Row一个列表搞定?关于UI代码编写之前,大概的代码结构构思仍是比较重要的。
代码结构大概的有所构思关于后面的思路很有帮助。

Box(){
LazyColunm()
Row{
TextFile()
Box{
Image()
}
}
}

image.png

/**
* 分钟微信中心界面
*/
@Composable
fun centerView() {
var inputValue by remember { mutableStateOf("搜索") }
Box() {
Column(
modifier = Modifier
.width(320.dp)
.background(Color.Red)
.verticalScroll(rememberScrollState())
) {
列表内容
}
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.width(320.dp).background(Color.White).padding(8.dp)) {
TextField(
value = inputValue,
onValueChange = {
inputValue = it
},
colors = TextFieldDefaults.textFieldColors(
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
backgroundColor = Color.Transparent
),
modifier = Modifier.padding(8.dp).background(
color = Color(247, 242, 243), shape = RoundedCornerShape(20),
).height(26.dp).width(250.dp),
leadingIcon = {
Icon(
bitmap = getImageBitmap("images/sousuo.png"),
"",
modifier = Modifier.size(10.dp)
)
},
)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.size(26.dp).background(
color = Color(247, 242, 243),
shape = RoundedCornerShape(10)
).clip(shape = RoundedCornerShape(10))
) {
ImageRes(
"images/jia.png",
modifier = Modifier.size(18.dp)
)
}
}
}
}

compose_desk_center.gif

滑动列表Item的编写
      下面是列表Item的款式,那咱们来进行根本Item代码结构款式的清晰。

Row{
Image()
Column{
Row{
Text("主管老婆大人")
Text("06:42")
}
Text("[文件]20202323002030320302.png")
}
}

image.png

 LazyColumn(
state = scrollLazyState,
modifier = Modifier
.width(300.dp)
.padding(top = 70.dp)
) {
items(100) { index ->
Row (Modifier.background(selectedColor(selectedIndex,index)).padding(top=10.dp,start = 15.dp,bottom = 10.dp,end = 15.dp).clickable {
selectedIndex = index
}){
Image(bitmap = getImageBitmap("images/head_lhc.png"),"",modifier = Modifier.width(45.dp))
Column(verticalArrangement=Arrangement.SpaceBetween,horizontalAlignment = Alignment.Start,modifier = Modifier.width(300.dp).padding(start = 10.dp)){
Row(horizontalArrangement = Arrangement.SpaceBetween,modifier = Modifier.width(300.dp)) {
Text("主管老婆大人",fontSize = 14.sp)
Text("06:42",fontSize = 11.sp,color = Color(111,111,111))
}
Spacer(Modifier.height(6.dp))
Text("[文件]202023230ll.png",fontSize = 12.sp,color = Color(111,111,111))
}
}
}
}

compose_desk_center_1.gif

这儿点击事件假如用clickable就会呈现点击水波纹但是微信没有这个水波纹,所以咱们不能用clickable来进行点击事件的扑捉,咱们用手势检测器来替代。

.pointerInput(Unit) {
detectTapGestures(
onTap = {
selectedIndex = index
}
)
}

deskcompose_inputgesture.gif

完善数据,以假乱真

       头像部分裁剪+PS-魔术棒+反选+delete+保存即可。

  //中心部分数据造假
wxDatas.add(WxListBean("主管老婆大人","images/item_a.png","[文件]20211999lll.pdf","6:45",0))
wxDatas.add(WxListBean("CSDN付费专栏作者交流群","images/item_b.png","杨修张:假如博客设置权限,是不是每个人都看不到了...","7:45",1))
wxDatas.add(WxListBean("CSDN社区专家","images/item_c.png","不是每个人都能够坐吃享受全国美食...","7:45",2))
wxDatas.add(WxListBean("郭比蓝","images/item_d.png","撸啊撸","7:45",3))
wxDatas.add(WxListBean("大众号","images/item_e.png","郭霖:Compose UI 带来的精彩...","10:45",4))
wxDatas.add(WxListBean("Flutter交流群","images/items_h.png","java Dart Kotlin js ...","10:35",5))
wxDatas.add(WxListBean("小江","images/items_u.png","clickable点击水波纹能去掉不?","7:45",5))
wxDatas.add(WxListBean("lemone","images/items_g.png","我来了带他过来,假如他来了就能够面试了","13:45",5))
wxDatas.add(WxListBean("窒息","images/item_c.png","撸啊撸","7:45",3))
wxDatas.add(WxListBean("大众健康","images/item_b.png","郭霖:Compose UI 带来的精彩...","17:45",4))

desk_compose_deskecenter.gif

五、Desktop UI编写 – 微信-Right

       最终咱们来完结Right UI、这部分如下图:
1、顶部Row
2、谈天列表部分
3、输入框部分
image.png

1、顶部Row

image.png
咱们代码结构如下:

Row{
Text("主管老婆大人")
Image(bitmap)
}

代码部分:

@Composable
fun RightView() {
Column {
Row(
modifier = Modifier.height(55.dp).fillMaxWidth().background(Color(243, 243, 243)).padding(15.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("主管老婆大人")
Image(bitmap = getImageBitmap("images/gengduo.png"), "")
}
Spacer(Modifier.weight(1f))
TextField(
value = "hello", onValueChange = {
}, modifier = Modifier.height(226.dp).fillMaxWidth(),
colors = TextFieldDefaults.textFieldColors(
cursorColor = Color.Gray,
backgroundColor = Color(243, 243, 243),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
)
)
}
}

image.png

2、谈天列表部分

       谈天部分其实也很简略,只需咋们剖析UI结构和数据即可:如下图
列表分为左右音讯,有图片、有视频、有文字。所以咱们定义数据时候需求音讯数据类型和不同人的userId、以及头像即可。

/**
* @param userID 用户ID
* @param headPath 头像
* @param message 音讯
* @param messageImg 音讯图片
* @param messageType 音讯类型
*
*/
data class WxMessageBean(
val userID: String,
var headPath: String,
var message: String,
var messageImg: String,
var messageType: MessageType
)
//谈天详情内容
wxMessages.add(WxMessageBean("002","images/item_a.png","有美人照片没有?","images/mn_1.png",MessageType.MESSAGE))
wxMessages.add(WxMessageBean("001","images/item_d.png","","images/mn_1.png",MessageType.IMAGE))
wxMessages.add(WxMessageBean("001","images/item_d.png","漂亮不?还有...","images/mn_1.png",MessageType.MESSAGE))
wxMessages.add(WxMessageBean("001","images/item_d.png","","images/mn_2.png",MessageType.IMAGE))
wxMessages.add(WxMessageBean("002","images/item_a.png","有没有健身的妹纸呀?这些美人照片太多了没意思...要刚柔并进。你的明白吧?","images/mn_1.png",MessageType.MESSAGE))
wxMessages.add(WxMessageBean("001","images/item_d.png","安心学技能多好,看啥美人对不?","images/mn_2.png",MessageType.MESSAGE))
wxMessages.add(WxMessageBean("001","images/item_d.png","Compose最近看了一眼,也能跨渠道呢?","images/mn_2.png",MessageType.MESSAGE))
wxMessages.add(WxMessageBean("002","images/item_a.png","是的没错! 但是我觉得Flutter目前更胜一筹在Web端方面","images/mn_1.png",MessageType.MESSAGE))

布局:我信任关于大家都很简略吧。假如没想法能够看看我之前的四篇博客。整体的谈天能够分为左右信息。也便是需求两套信息依据是否本人来显示方位。第二无非头像和音讯的方位、第三关于音讯小尖头号简略的clip搞定这儿因为时刻问题设置圆角即可。

前四章
Jetpack-Compose根本布局
JetPack-Compose – 自定义制作
JetPack-Compose – Flutter 动态UI?
JetPack-Compose UI完结篇
JetPack-Compose 水墨画作用

@Composable
fun RightView() {
var inputText by remember { mutableStateOf("") }
Column {
Row(
modifier = Modifier.height(55.dp).fillMaxWidth().background(Color(247, 242, 243, 100))
.padding(15.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("郭b蓝")
Image(bitmap = getImageBitmap("images/gengduo.png"), "")
}
Spacer(Modifier.height(1.dp).fillMaxWidth().background(Color(222, 222, 222)))
LazyColumn(Modifier.weight(1f).fillMaxWidth().background(Color(247, 242, 243, 100))) {
items(WxViewModel.wxMessages.size) { index ->
val wxmessage = WxViewModel.wxMessages[index]
if (wxmessage.userID == "001") {
Box {
Row(Modifier.padding(10.dp)) {
Image(
bitmap = getImageBitmap(wxmessage.headPath),
"",
modifier = Modifier.size(45.dp),
contentScale = ContentScale.FillWidth
)
if (wxmessage.messageType == MessageType.MESSAGE) {
Text(
text = wxmessage.message,
fontSize = 13.sp,
modifier = Modifier.background(
color = Color.White,
shape = RoundedCornerShape(20)
).clip(shape = RoundedCornerShape(20)).padding(10.dp)
)
} else {
Image(
bitmap = getImageBitmap(wxmessage.messageImg),
"",
modifier = Modifier.size(80.dp)
)
}
}
}
} else {
Row(
modifier = Modifier.fillMaxWidth().padding(15.dp),
horizontalArrangement = Arrangement.End
) {
Row {
if (wxmessage.messageType == MessageType.MESSAGE) {
Text(
text = wxmessage.message,
fontSize = 13.sp,
modifier = Modifier.width(250.dp).background(
color = Color.White,
shape = RoundedCornerShape(20)
).clip(shape = RoundedCornerShape(20)).padding(10.dp)
)
} else {
Image(
bitmap = getImageBitmap(wxmessage.messageImg),
"",
modifier = Modifier.size(80.dp)
)
}
Image(
bitmap = getImageBitmap(wxmessage.headPath),
"",
modifier = Modifier.padding(start= 10.dp).size(40.dp),
contentScale = ContentScale.FillBounds
)
}
}
}
}
}
Spacer(Modifier.height(1.dp).fillMaxWidth().background(Color(222, 222, 222)))
TextField(
value = inputText, onValueChange = {
inputText = it
}, modifier = Modifier.height(226.dp).fillMaxWidth(),
colors = TextFieldDefaults.textFieldColors(
cursorColor = Color.Gray,
backgroundColor = Color(247, 242, 243, 100),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
)
)
}
}

desk_compose_message.gif

3、输入框部分

这部分最简略来有没有?上代码

Column {
Row(
modifier = Modifier.background(Color(247, 242, 243, 100)).padding(start = 15.dp,end = 15.dp,top=15.dp)
.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
bitmap = getImageBitmap("images/wx_face.png"),
"",
modifier = Modifier.padding(horizontal = 5.dp),
)
Image(
bitmap = getImageBitmap("images/wx_file.png"),
"",
modifier = Modifier.padding(horizontal = 5.dp),
)
Image(
bitmap = getImageBitmap("images/wx_jd.png"),
"",
modifier = Modifier.padding(horizontal = 5.dp),
)
Image(
bitmap = getImageBitmap("images/wx_msg.png"),
"",
modifier = Modifier.padding(horizontal = 5.dp).clickable {
WxViewModel.wxMessages.add(WxMessageBean("002","images/item_a.png",inputText,"images/mn_2.png",MessageType.MESSAGE))
send=!send
GlobalScope.launch{
state.animateScrollTo(yPosition)
}
}
)
}
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
bitmap = getImageBitmap("images/wx_phone.png"),
"",
modifier = Modifier.padding(horizontal = 5.dp)
)
Image(
bitmap = getImageBitmap("images/wx_sp.png"),
"",
modifier = Modifier.padding(horizontal = 5.dp)
)
}
}
TextField(
value = inputText, onValueChange = {
inputText = it
}, modifier = Modifier.height(226.dp).fillMaxWidth(),
colors = TextFieldDefaults.textFieldColors(
cursorColor = Color.Gray,
backgroundColor = Color(247, 242, 243, 100),
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
),
keyboardActions = KeyboardActions(
onDone = {
}
)
)
}

enen01.gif

enen02.gif

sdsdsdsdd.gif

六、总结

       构建更好的桌面应用程序
composefordesktop供给了一种用Kotlin创建用户界面的声明式UI。结合可组合的功能来构建用户界面,并享受IDE和构建系统供给的完整东西支撑—不需求XML或模板言语,写了几个小时的博客着实体会到了Compose在UI方面的才能和方便,几个小时根本搞定UI以及部分交互逻辑,我细心算来其间所有的图标都是我经过PS处理、博客排版、微信滑水等时刻除掉,写代码部分时刻比实践要少得多,所以Compose值得等待,我也坚信声明式UI才是未来。

endendend.gif