Skip to content

Commit 03e0923

Browse files
committed
聊天消息列表
1 parent a1ba721 commit 03e0923

File tree

4 files changed

+300
-0
lines changed

4 files changed

+300
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
package com.icuxika.bittersweet.demo.controller
2+
3+
import com.icuxika.bittersweet.demo.annotation.AppFXML
4+
import com.icuxika.bittersweet.dsl.onAction
5+
import javafx.beans.property.SimpleDoubleProperty
6+
import javafx.beans.property.SimpleObjectProperty
7+
import javafx.beans.property.SimpleStringProperty
8+
import javafx.collections.FXCollections
9+
import javafx.collections.ObservableList
10+
import javafx.fxml.FXML
11+
import javafx.fxml.Initializable
12+
import javafx.geometry.Insets
13+
import javafx.scene.control.Button
14+
import javafx.scene.control.Label
15+
import javafx.scene.control.ListCell
16+
import javafx.scene.control.ListView
17+
import javafx.scene.image.Image
18+
import javafx.scene.image.ImageView
19+
import javafx.scene.layout.*
20+
import javafx.scene.paint.Color
21+
import javafx.scene.shape.SVGPath
22+
import javafx.scene.text.Font
23+
import javafx.scene.text.TextFlow
24+
import javafx.util.Callback
25+
import java.net.URL
26+
import java.util.*
27+
28+
@AppFXML(fxml = "fxml/chat-view.fxml")
29+
class ChatViewController : Initializable {
30+
31+
@FXML
32+
private lateinit var rootContainer: StackPane
33+
34+
@FXML
35+
private lateinit var container: BorderPane
36+
37+
private val messageObservableList: ObservableList<Message> = FXCollections.observableArrayList()
38+
39+
override fun initialize(location: URL?, resources: ResourceBundle?) {
40+
container.top = Button("测试").apply {
41+
onAction {
42+
messageObservableList.filter { it.type == 2 }[0].imageUrl =
43+
"https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
44+
(container.center as ListView<*>).refresh()
45+
}
46+
}
47+
container.center = ListView<Message>().apply {
48+
styleClass.add("message-list-view")
49+
items = messageObservableList
50+
cellFactory = Callback<ListView<Message>, ListCell<Message>> {
51+
object : ListCell<Message>() {
52+
val leftMsgDecorateTextFlow by lazy {
53+
createTextFlow(
54+
"M-0,0c0,565.161 458.839,1024 1024,1024l-0,-716.8c-408.482,0 -785.067,-137.652 -1024,-307.2Z"
55+
)
56+
}
57+
val rightMsgDecorateTextFlow by lazy {
58+
createTextFlow(
59+
"M0,307.2l0,716.8c565.161,0 1024,-458.839 1024,-1024c-238.933,169.548 -615.518,307.2 -1024,307.2Z"
60+
)
61+
}
62+
63+
val textProperty = SimpleStringProperty()
64+
val leftTextNode by lazy {
65+
val text = createText()
66+
AnchorPane().apply {
67+
AnchorPane.setLeftAnchor(text, 24.0)
68+
AnchorPane.setTopAnchor(text, 0.0)
69+
AnchorPane.setLeftAnchor(leftMsgDecorateTextFlow, 4.0)
70+
AnchorPane.setTopAnchor(leftMsgDecorateTextFlow, 2.0)
71+
children.addAll(text, leftMsgDecorateTextFlow)
72+
}
73+
}
74+
val rightTextNode by lazy {
75+
val text = createText()
76+
AnchorPane().apply {
77+
AnchorPane.setRightAnchor(text, 24.0)
78+
AnchorPane.setTopAnchor(text, 0.0)
79+
AnchorPane.setRightAnchor(rightMsgDecorateTextFlow, 4.0)
80+
AnchorPane.setTopAnchor(rightMsgDecorateTextFlow, 2.0)
81+
children.addAll(text, rightMsgDecorateTextFlow)
82+
}
83+
}
84+
85+
fun createText(): TextFlow = TextFlow().apply {
86+
padding = Insets(8.0)
87+
background = Background(BackgroundFill(Color.DODGERBLUE, CornerRadii(16.0), Insets.EMPTY))
88+
children.add(Label().apply {
89+
textProperty().bind(textProperty)
90+
textFill = Color.WHITE
91+
font = Font.font(16.0)
92+
maxWidth = 240.0
93+
isWrapText = true
94+
})
95+
}
96+
97+
val imageProperty = SimpleObjectProperty<Image>()
98+
val fitWidthProperty = SimpleDoubleProperty()
99+
val leftImageNode by lazy {
100+
val image = createImage()
101+
AnchorPane().apply {
102+
AnchorPane.setLeftAnchor(image, 24.0)
103+
AnchorPane.setTopAnchor(image, 0.0)
104+
AnchorPane.setLeftAnchor(leftMsgDecorateTextFlow, 4.0)
105+
AnchorPane.setTopAnchor(leftMsgDecorateTextFlow, 2.0)
106+
children.addAll(image, leftMsgDecorateTextFlow)
107+
}
108+
}
109+
val rightImageNode by lazy {
110+
val image = createImage()
111+
AnchorPane().apply {
112+
AnchorPane.setRightAnchor(image, 24.0)
113+
AnchorPane.setTopAnchor(image, 0.0)
114+
AnchorPane.setRightAnchor(rightMsgDecorateTextFlow, 4.0)
115+
AnchorPane.setTopAnchor(rightMsgDecorateTextFlow, 2.0)
116+
children.addAll(image, rightMsgDecorateTextFlow)
117+
}
118+
}
119+
120+
fun createImage(): TextFlow = TextFlow().apply {
121+
padding = Insets(8.0)
122+
background = Background(BackgroundFill(Color.DODGERBLUE, CornerRadii(16.0), Insets.EMPTY))
123+
val imageView = ImageView().apply {
124+
imageProperty().bind(imageProperty)
125+
fitWidthProperty().bind(fitWidthProperty)
126+
isPreserveRatio = true
127+
isCache = true
128+
}
129+
children.add(imageView)
130+
}
131+
132+
override fun updateItem(item: Message?, empty: Boolean) {
133+
super.updateItem(item, empty)
134+
135+
if (empty || item == null) {
136+
text = null
137+
graphic = null
138+
} else {
139+
text = null
140+
graphic = when (item.type) {
141+
1 -> {
142+
textProperty.bind(item.msgProperty())
143+
if (item.left) {
144+
leftTextNode
145+
} else {
146+
rightTextNode
147+
}
148+
}
149+
150+
2 -> {
151+
if (imageProperty.get() == null || imageProperty.get().url != item.imageUrl) {
152+
imageProperty.set(Image(item.imageUrl, true).apply {
153+
progressProperty().addListener { observable, oldValue, newValue ->
154+
if (newValue == 1.0) {
155+
println(imageProperty.get().width)
156+
if (imageProperty.get().width > 240.0) {
157+
fitWidthProperty.set(240.0)
158+
}
159+
}
160+
}
161+
})
162+
}
163+
if (item.left) {
164+
leftImageNode
165+
} else {
166+
rightImageNode
167+
}
168+
}
169+
170+
else -> throw IllegalArgumentException()
171+
}
172+
}
173+
}
174+
}
175+
}
176+
}
177+
178+
messageObservableList.addAll(
179+
Message().apply {
180+
left = true
181+
setMsg("你要好好长大,不要输给风,不要输给雨,不要输给冬雪,不要输给炎夏。少年人,你在孩童时应当快乐,使你的心欢畅,行你所愿行的,见你所愿见的,然而也应当记住黑暗的时日。但愿新的梦想永远不被无留陀侵蚀,但愿旧的故事与无留陀一同被忘却,但愿绿色的原野,山丘永远不会变得枯黄,但愿溪水永远清澈,但愿鲜花永远盛开。挚友将再次同行于茂密的森林中,一切美好的事物终将归来,一切痛苦的记忆也会远去,就像溪水净化自己,枯树绽出新芽。最终,森林会记住一切。")
182+
},
183+
Message().apply {
184+
left = false
185+
setMsg("如此绚丽的花朵,不该在绽放之前就枯萎。我会赠予你璀璨的祝福,而你的灵魂,也将绽放更耀眼的光辉。亲爱的山雀,请将我的箭,我的花,与我的爱,带给那孑然独行的旅人。愿你前行的道路有群星闪耀。愿你留下的足迹有百花绽放。你即是上帝的馈赠,世界因你而瑰丽。")
186+
},
187+
Message().apply {
188+
left = true
189+
type = 2
190+
},
191+
Message().apply {
192+
left = true
193+
type = 2
194+
imageUrl = "https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg"
195+
},
196+
Message().apply {
197+
left = true
198+
setMsg("你要好好长大,不要输给风,不要输给雨,不要输给冬雪,不要输给炎夏。少年人,你在孩童时应当快乐,使你的心欢畅,行你所愿行的,见你所愿见的,然而也应当记住黑暗的时日。但愿新的梦想永远不被无留陀侵蚀,但愿旧的故事与无留陀一同被忘却,但愿绿色的原野,山丘永远不会变得枯黄,但愿溪水永远清澈,但愿鲜花永远盛开。挚友将再次同行于茂密的森林中,一切美好的事物终将归来,一切痛苦的记忆也会远去,就像溪水净化自己,枯树绽出新芽。最终,森林会记住一切。")
199+
},
200+
Message().apply {
201+
left = false
202+
setMsg("如此绚丽的花朵,不该在绽放之前就枯萎。我会赠予你璀璨的祝福,而你的灵魂,也将绽放更耀眼的光辉。亲爱的山雀,请将我的箭,我的花,与我的爱,带给那孑然独行的旅人。愿你前行的道路有群星闪耀。愿你留下的足迹有百花绽放。你即是上帝的馈赠,世界因你而瑰丽。")
203+
},
204+
Message().apply {
205+
left = true
206+
type = 2
207+
imageUrl = "https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg"
208+
},
209+
Message().apply {
210+
left = true
211+
setMsg("你要好好长大,不要输给风,不要输给雨,不要输给冬雪,不要输给炎夏。少年人,你在孩童时应当快乐,使你的心欢畅,行你所愿行的,见你所愿见的,然而也应当记住黑暗的时日。但愿新的梦想永远不被无留陀侵蚀,但愿旧的故事与无留陀一同被忘却,但愿绿色的原野,山丘永远不会变得枯黄,但愿溪水永远清澈,但愿鲜花永远盛开。挚友将再次同行于茂密的森林中,一切美好的事物终将归来,一切痛苦的记忆也会远去,就像溪水净化自己,枯树绽出新芽。最终,森林会记住一切。")
212+
},
213+
Message().apply {
214+
left = true
215+
setMsg("你要好好长大,不要输给风,不要输给雨,不要输给冬雪,不要输给炎夏。少年人,你在孩童时应当快乐,使你的心欢畅,行你所愿行的,见你所愿见的,然而也应当记住黑暗的时日。但愿新的梦想永远不被无留陀侵蚀,但愿旧的故事与无留陀一同被忘却,但愿绿色的原野,山丘永远不会变得枯黄,但愿溪水永远清澈,但愿鲜花永远盛开。挚友将再次同行于茂密的森林中,一切美好的事物终将归来,一切痛苦的记忆也会远去,就像溪水净化自己,枯树绽出新芽。最终,森林会记住一切。")
216+
},
217+
Message().apply {
218+
left = true
219+
setMsg("你要好好长大,不要输给风,不要输给雨,不要输给冬雪,不要输给炎夏。少年人,你在孩童时应当快乐,使你的心欢畅,行你所愿行的,见你所愿见的,然而也应当记住黑暗的时日。但愿新的梦想永远不被无留陀侵蚀,但愿旧的故事与无留陀一同被忘却,但愿绿色的原野,山丘永远不会变得枯黄,但愿溪水永远清澈,但愿鲜花永远盛开。挚友将再次同行于茂密的森林中,一切美好的事物终将归来,一切痛苦的记忆也会远去,就像溪水净化自己,枯树绽出新芽。最终,森林会记住一切。")
220+
},
221+
Message().apply {
222+
left = false
223+
setMsg("如此绚丽的花朵,不该在绽放之前就枯萎。我会赠予你璀璨的祝福,而你的灵魂,也将绽放更耀眼的光辉。亲爱的山雀,请将我的箭,我的花,与我的爱,带给那孑然独行的旅人。愿你前行的道路有群星闪耀。愿你留下的足迹有百花绽放。你即是上帝的馈赠,世界因你而瑰丽。")
224+
},
225+
)
226+
}
227+
228+
private fun createTextFlow(svgContent: String, size: Double = 24.0): TextFlow = TextFlow().apply {
229+
shape = SVGPath().apply {
230+
content = svgContent
231+
}
232+
background = Background(BackgroundFill(Color.DODGERBLUE, CornerRadii.EMPTY, Insets.EMPTY))
233+
minWidth = size
234+
minHeight = size
235+
maxWidth = size
236+
maxHeight = size
237+
}
238+
239+
class Message {
240+
var left: Boolean = true
241+
242+
var type: Int = 1
243+
244+
private val msg = SimpleStringProperty()
245+
fun msgProperty() = msg
246+
fun setMsg(value: String) {
247+
msg.set(value)
248+
}
249+
250+
fun getMsg() = msg.get()
251+
252+
var imageUrl = "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/shuijiao.jpg"
253+
}
254+
}

demo/src/main/kotlin/com/icuxika/bittersweet/demo/controller/MainController.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,13 @@ class MainController : Initializable {
210210
(container.scene.window as Stage).close()
211211
}
212212
},
213+
Button("气泡消息").apply {
214+
styleClass.add("test-button")
215+
onAction {
216+
AppView(ChatViewController::class).show()
217+
(container.scene.window as Stage).close()
218+
}
219+
},
213220
ComboBox(FXCollections.observableArrayList(Theme.entries)).apply {
214221
valueProperty().bindBidirectional(AppResource.themeProperty())
215222
cellFactory = Callback<ListView<Theme>, ListCell<Theme>> {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@import "bittersweet.css";
2+
3+
.message-list-view {
4+
}
5+
6+
.message-list-view:focused {
7+
-fx-background-color: transparent;
8+
}
9+
10+
.message-list-view .list-cell {
11+
-fx-background-color: transparent;
12+
}
13+
14+
.message-list-view .scroll-bar:vertical {
15+
-fx-background-color: transparent;
16+
}
17+
18+
.message-list-view .scroll-bar:vertical > .thumb {
19+
-fx-background-color: rgba(0, 0, 0, 0.1);
20+
}
21+
22+
.message-list-view .scroll-bar:vertical > .increment-button,
23+
.message-list-view .scroll-bar:vertical > .increment-button > .increment-arrow,
24+
.message-list-view .scroll-bar:vertical > .decrement-button,
25+
.message-list-view .scroll-bar:vertical > .decrement-button > .decrement-arrow {
26+
-fx-background-color: transparent;
27+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<?import javafx.scene.layout.BorderPane?>
4+
<?import javafx.scene.layout.StackPane?>
5+
<StackPane fx:id="rootContainer" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
6+
prefHeight="800.0" prefWidth="800.0" stylesheets="@../css/chat-view.css" xmlns="http://javafx.com/javafx/21"
7+
xmlns:fx="http://javafx.com/fxml/1"
8+
fx:controller="com.icuxika.bittersweet.demo.controller.ChatViewController">
9+
<children>
10+
<BorderPane fx:id="container"/>
11+
</children>
12+
</StackPane>

0 commit comments

Comments
 (0)