Spring does not stay, do not blame the east wind evil

Compose for Desktop

Compose is a framework for quick editing interfaces from The Kotlin language, based on Google’s modern toolbox, brought to you by JetBrains. Compose for Desktop simplifies and speeds up UI development for Desktop applications, and allows for a lot of UI code sharing between Android and Desktop applications, according to some official accounts. Compose started out as a declarative UI, and of course, in the era of cross-platform chaos, it also has cross-platform dreams. Why not try Compose for Desktop when it’s not yet popular and ubiquitous?

A, environmental

Versions of IntelliJ IDEA after 2020.3 can automatically rely on the associated Gradle based on the new type compose Desktop, Compose App, and Compose Web. I downloaded the latest version and tried it for 30 days. Screenshots look at the text, I believe the big guys can understand? Select Kotlin on the left, and on the right you’ll see all the things Kotlin can do :Desktop, Web, Mobile…. Kotlin cow force!

A Desktop,

Name, Location, Project Template->Desktopo Build System, Project JDK->11

You can click the green run arrow….. in front of main in the image below Waiting for a miracle

The effect of running, of course, this is the guy that I have nothing to do with the Bezier curve, it’s a matter of time. If you see my custom I believe that drawing is not a problem!!

Now that we’ve finished developing our environment, let’s get a little excited and start coding.

Ii. Desktop UI analysis – wechat

Wechat’s desktop is not flashy, but elegant, simple and beautiful. For this article, we are trying to Compose for Desktop by imitating this UI

Material preparation

In order to achieve a more consistent effect, we use Photoshop to capture the material. 1. Opening wechat screenshot requires an icon. 2. Photoshop screenshots with magic wand selection to delete unnecessary parts.

3. Debug boundaries by zooming the selection.

Just save the picture. Step-by-step operations require pictures.

Layout analysis

We use layouts all the time, and we know that they can be divided into these three pieces and they’re moving from left to right. So let’s start with a level 1 UI layout.

1. Colum on the left is perfectly matched with Spacer from top to bottom. 2. The right side of the ListView

Three, Desktop UI preparation – wechat -Left

Compose for Desktop simplifies and speeds up UI development for Desktop applications, and allows a lot of UI code sharing between Android and Desktop applications. Of course I felt a wave but a lot of the components were basically the same. In terms of customization, there is a lack of API and shadow Settings. If you find any, please let me know. Thank you very much. Since it’s consistent with Android, then a lot of code catches the..

The above analysis: 1. The left Colum is perfectly matched with Spacer from top to bottom

Entity class encapsulates click image path

/ * * *@paramDefaultPath Default image path *@paramSelectedPath Select path *@paramPath Actual path *@paramSelected Indicates whether */ is selected
data class WxSelectedBean(val defaultPath:String,var selectedPath:String,var path:String,var selected:Boolean)
Copy the code

Negative image path

object WxViewModel : RememberObserver {
    val isAppReady = mutableStateOf(false)
    val position = ArrayList<WxSelectedBean>()
    fun initData(a) {
        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(a){}override fun onForgotten(a){}override fun onRemembered(a){}}Copy the code

interface

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(a) = Window {
    WxViewModel.initData()
    var wxData by remember { mutableStateOf(WxViewModel.position) }
    // The selected index
    var selectedIndex by remember { mutableStateOf(1)}// Select animation to execute or not
    var imageAnimal by remember { mutableStateOf(true)}// Image rotation animation
    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)
                    )
                }
            }
        }

    }

}


/ * * *@paramWxData data collection *@paramSelectedIndex selectedIndex *@paramCurrenIndex Index of the current Image * return Returns the path of each selected and unselected Image */
private fun getPath(
    wxData: ArrayList<WxSelectedBean>,
    selectedIndex: Int,
    currenIndex: Int
): String {
    return if (selectedIndex == currenIndex) {
        wxData[currenIndex].selectedPath
    } else {
        wxData[currenIndex].defaultPath
    }

}
Copy the code

See the effect?

Iv. Desktop UI compilation – wechat -Center

The middle part of the Box is as follows: Before writing UI code, it is important to have a general idea of the code framework. Having a rough idea of the code structure is very helpful for the following ideas.

Box(){
  LazyColunm()
  Row{
     TextFile()
     Box{
        Image() 
     }
  }
}
Copy the code

/** * min wechat middle interface */
@Composable
fun centerView(a) {
    var inputValue by remember { mutableStateOf("Search") }
    Box() {
        Column(
            modifier = Modifier
                .width(320.Dp).background(color.red).verticalScroll(rememberScrollState())) {list contents} 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)
                )
            }
        }

    }
}
Copy the code

The following is the style of the list Item, that we carry out the basic Item code structure style clear.

Row{
  Image()
  Column{
     Row{
       Text("Wife in charge.")
       Text("06:42")
     }
     Text("[file] 20202323002030320302 PNG")}}Copy the code

 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("Wife in charge.",fontSize = 14.sp)
                            Text("06:42",fontSize = 11.sp,color = Color(111.111.111))
                        }
                        Spacer(Modifier.height(6.dp))
                        Text("[file] ll 202023230. PNG",fontSize = 12.sp,color = Color(111.111.111)}}}}Copy the code

If clickable is used for click events, there will be water ripples. However, wechat does not have this water ripple, so we cannot use Clickable to capture click events. Instead, we use gesture detector.

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

                }
Copy the code

Perfect the data to distinguish the fake from the real

Head part clipping +PS- Magic wand + reverse selection +delete+ save.

  // The middle part of the data is falsified
        wxDatas.add(WxListBean("Wife in charge."."images/item_a.png"."[file] 20211999 name 'LLL. PDF".45 "".0))
        wxDatas.add(WxListBean("CSDN Paid Columnist Exchange Group"."images/item_b.png"."Yang Xiuzhang: If the blog set permission, is not everyone can not see..."."At".1))
        wxDatas.add(WxListBean("CSDN Community Expert"."images/item_c.png"."Not everyone can sit down and enjoy the world's best food..."."At".2))
        wxDatas.add(WxListBean("Corbillan"."images/item_d.png"."Wank off, wank off."."At".3))
        wxDatas.add(WxListBean("Public Account"."images/item_e.png"."Guo Lin :Compose UI brings wonderful..."."10:45".4))
        wxDatas.add(WxListBean("Flutter alternating group"."images/items_h.png"."java Dart Kotlin js ..."."10:35".5))
        wxDatas.add(WxListBean("Jiang"."images/items_u.png"."Does the clickable water ripple out?"."At".5))
        wxDatas.add(WxListBean("lemone"."images/items_g.png"."I'm here to bring him in. If he does, he can interview."."When".5))
        wxDatas.add(WxListBean("Suffocation"."images/item_c.png"."Wank off, wank off."."At".3))
        wxDatas.add(WxListBean("Public health"."images/item_b.png"."Guo Lin :Compose UI brings wonderful..."."Then".4))

Copy the code

Five, Desktop UI compilation – wechat -Right

Finally, let’s complete the Right UI, which looks like this:

1. Top Row

2. Chat list section

3. Input box

1. Top Row

Our code structure is as follows:

Row{
   Text("Wife in charge.")
   Image(bitmap)
}
Copy the code

Code part:

@Composable
fun RightView(a) {
    Column {
        Row(
            modifier = Modifier.height(55.dp).fillMaxWidth().background(Color(243.243.243)).padding(15.dp),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text("Wife in charge.")
            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,
            )
        )
    }

}
Copy the code

2. Chat list section

The chat section is actually quite simple, as long as we analyze the UI structure and data: the following list is divided into left and right messages, pictures, videos, and text. So when we define the data, we need the message data type, the userId of the different person, and the avatar.

/ * * *@paramUserID userID *@paramHeadPath face *@paramMessage message *@paramMessageImg Message image *@paramMessageType messageType * */
data class WxMessageBean(
    val userID: String,
    var headPath: String,
    var message: String,
    var messageImg: String,
    var messageType: MessageType
)

  // Chat details
        wxMessages.add(WxMessageBean("002"."images/item_a.png"."Do you have any pictures of beautiful women?"."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"."Isn't it pretty? And..."."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"."Is there a girl who works out? There are so many pictures of beautiful women... Be flexible and go forward. Do you understand?"."images/mn_1.png",MessageType.MESSAGE))
        wxMessages.add(WxMessageBean("001"."images/item_d.png"."Peace of mind to learn technology how good, see what beauty right?"."images/mn_2.png",MessageType.MESSAGE))
        wxMessages.add(WxMessageBean("001"."images/item_d.png"."Compose recently took a look at that, and could cross platform as well?"."images/mn_2.png",MessageType.MESSAGE))
        wxMessages.add(WxMessageBean("002"."images/item_a.png"."Yes! But I think Flutter is currently better on the Web side."."images/mn_1.png",MessageType.MESSAGE))


Copy the code

Layout: I’m sure it’s easy for everyone. Check out my previous four posts if you don’t have any ideas. The overall chat can be divided into left and right messages. That is, you need two sets of information to display the location based on whether or not you are the person. The second one is the head and the position of the message, and the third one is the tip of the message.

Jetpack-compose basic Layout Jetpack-compose – Custom drawing Jetpack-compose dynamic UI? The Jetpack-Compose UI concludes with the Jetpack-Compose ink painting effect

@Composable
fun RightView(a) {
    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 blue")
            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,
            )
        )
    }

}
Copy the code

3. Input box

Is this the easiest part? In the code

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 = {

                    }
                )

            )
        }
Copy the code

Six, summarized

Building a Better desktop application composeforDesktop provides a declarative UI for creating user interfaces with Kotlin. Combine composable functionality to build user interfaces, And enjoy the IDE and build the system to provide a complete tool support – do not need to be XML or template language, to write a few hours of blog really realized Compose the ability in terms of the UI and convenience, a few hours of basic fix UI and some of the interactive logic, I carefully measure of which all ICONS are treated with PS, blogs typesetting, WeChat water skiing, etc Besides, writing code takes much less time than it actually does, so Compose is something to look forward to, and I firmly believe declarative UI is the future.