组成桌面
#java #kotlin #jetpackcompose #desktop

声明性的UI框架正在稳步取代其当务之急。十年前,网络和反应一直在开创这一运动。 Google Cross Platform Framework Flutter(第一个稳定版本出现在2018年底)是通过设计声明的。苹果在2019年WWDC期间引入了Swiftui。Android有点晚了。直到2020年夏天才向公众发布第一个alpha版本。

在MacOS,Windows和Linux上,情况有些不同。只有Apple严格在用于Mac应用程序的工具,框架和编程语言上。其他两个平台让开发人员决定。这引导并仍然导致风格混合。喜欢或讨厌这是一种口味问题。重要的是:大多数已建立的桌面框架可以追溯到很多年前,甚至可以追溯到他们运行的操作系统的早期。换句话说:他们的建筑已经有几十年了。相反,这不一定是不好的。它证明了基本的思想和概念是合理的,灵活的和可持续的。

然而,如果这是真的,为什么要有新的范式?更重要的是,桌面仍然相关吗?自从网络和浏览器变得无处不在以来,它已被宣布死亡多次。我们可以专注于一个平台,即浏览器,而不是为Windows,MacOS和Linux编写应用程序。当然,使用Java,我们可以编写在不同平台上运行的图形用户界面。但是AWT过于简单,至少在一开始就摆动,还不够快。当然,在某个时间点,我们学会了如何使用Swing编写快速,美丽,优雅的跨平台应用程序,但是到那时,网络已经 - 老实说 - 已经胜利了。因此,Adobe Air,Javafx等人没有机会。那么,让我重申我的问题,为什么还是要开发桌面?好吧,我们确实想保留一些本地的东西,这可能是由于安全性或性能注意事项或简单缺乏网络访问所致。更重要的是:不受浏览器的链条的约束,通常会使开发变得更加容易。本地文件访问,有人吗?最后,重新发现一个知名的平台可能会令人鼓舞。

命令与声明性

大多数流行的桌面UI框架都是面向对象的,基于组件的。在运行时,应用程序的用户界面由一个或多个对象树组成。每个对象代表UI元素或一个容器,它也取决于用户界面的一部分(例如,窗口,菜单栏或对话框)。实际上,所有框架都知道安排和大小的孩子的特殊容器。为了添加,删除或更改用户界面的部分,通过添加或删除分支或修改叶子(对象),在运行时,组件(对象)树会更改。这就是为什么这样的框架被称为命令的原因。任何更改,无论多么小或微不足道,都必须明确编码。

问题:用户界面获得的越复杂,进行更改就越复杂。那是因为最命令的框架的UI元素过着自己的生活。现在是提醒您1990年代后期组件的合适时机。但这本身就是一篇文章。因此,让我们举一个例子。文本输入字段不仅可以监视键盘,而且还知道剪切,复制和粘贴操作。它也可以验证和过滤输入。文本字段在属性中存储其状态(至少是当前文本和光标位置),实际上必须在应用程序源代码中访问。

这就是原因:仅仅因为变量的内容更改,文本字段没有神奇地更新。同样,输入或删除字符不一定会更改相应的变量。组件属性和变量都必须同步,并且要这样做是开发人员的工作。每个组件框架都有其喜欢的工具,技术或机制来实现这一目标,例如回调或绑定。但是程序员必须使用这些工具。事实证明,将两个组件属性保持OND程序变量同步可能会迅速变得乏味且容易出错。在时间的过程中,出现了许多设计模式,可以显着增强可读性和可维护性。尽管如此,数据和用户界面(即代表UI的组件)生活在不同世界(需要同步)的事实仍然如此。

这是脱发 UI框架的作用不同。我们 descript (嗯,声明)在“源代码”中,我们并没有在艰苦地更新组件树时,用户界面在当前数据中的外观(不:更改)。实际上,所有声明性的UI框架都知道状态的概念。状态是可能随着时间而变化的数据。重要的是:状态变化会自动触发UI更改。如果需要重建或重新绘制某些东西,则由框架决定。不是开发人员。不在应用程序源代码中。数据是开发的核心。

在JetPack组成,Google的Android声明性UI框架中,看起来像这样:

@Composable
@Preview
fun CounterDemo() {
  var counter by remember { mutableStateOf(0) }
  Column(
    horizontalAlignment = CenterHorizontally,
    modifier = Modifier.padding(16.dp)
  ) {
    Box(
      contentAlignment = Center,
      modifier = Modifier.height(200.dp)
    ) {
      if (counter == 0) {
        Text(
          text = "Not yet clicked",
          softWrap = true,
          textAlign = TextAlign.Center,
          style = MaterialTheme.typography.h3
        )
      } else {
        Text(
          text = "$counter",
          textAlign = TextAlign.Center,
          style = MaterialTheme.typography.h1
        )
      }
    }
    Button(
      onClick = { counter += 1 }
    ) {
      Text(text = "Klick")
    }
  }
}

UI元素出现在源代码中,作为kotlin函数,用@Composable注释。 @Preview在IDE内部呈现一个可组合函数(可组合功能)。

Android Studio with source code editor and preview

应用程序的用户界面是通过嵌套自定义和预构建(即:由框架提供的)组合功能创建的。 CounterDemo()Box()和一个Button放入Column()。样本中的Box()始终具有子元素。这是由if (counter == 0)确定的。如果条件为真,则尚未单击。如果是false,则显示可变counter的内容。 counter是一个州。在JetPack Compose state 中通常是用mutableStateOf创建的,并使用remember记住。如果值更改(counter += 1),则该框架确保更新了使用此状态的所有组合物。这称为重新分配。

您是否注意到UI元素没有引用或指示?与命令框架不同,不需要操纵或更改任何物体或树结构。因此,不再需要对分支或叶子进行参考。这消除了在错误指针引起的运行时难以发现的所有这些崩溃。如果状态更改需要更改内部数据结构,则在框架内处理所有内容。应用开发人员不必打扰。

合并功能的名称以大写字母开头,这使我们想起了类或数据结构。这种惯例的冲突是有意的。毕竟,组合代表UI元素(组件)。因此, deklarative 并不意味着不再存在组件。但是,组件现在减少了重量级。有很多小零碎的碎片,应该结合在一起。基本原理称为继承的组成。请注意,声明性不一定表示函数。例如,颤动是建立在课堂和继承的基础上。但是,从Java Swing或Javafx中您知道的组件的切割方式不同。根据声明的框架,例如AlignSizePadding组件。在当务之急的世界中,这些可能是属性。

我们如何显示CounterDemo()?在Android上看起来像这样:

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      MaterialTheme {
        Scaffold(
          topBar = {
            TopAppBar(
              title = {
                Text(text = stringResource(id = R.string.app_name))
              }
            )
          }
        ) {
          Box(
            modifier = Modifier
              .fillMaxSize()
              .padding(it),
            contentAlignment = Center
          ) {
            CounterDemo()
          }
        }
      }
    }
  }
}

CounterDemo on an Android device

在这里,CounterDemo()包裹在其他一些可复合功能中,MaterialTheme()Scaffold()TopAppBar(),最终使用setContent { }。 p>

桌面获得声明性

您想尝试编写声明性用户界面,但不在乎移动平台吗?这已经有一段时间了。例如,您可以构建一个React应用程序,并使用电子在桌面上运行它。但是,您需要了解JavaScript/Typescript并进行反应。也有颤音。该跨平台框架首先发布用于移动开发,但如今也适用于台式机和网络。颤抖非常受欢迎。但是,您需要学习飞镖编程语言。仍然很新的是组成多平台。它的创建者Jetbrains将其宣传为用于桌面和Web用户界面的快速,反应性Kotlin框架。它旨在简化和加快网络和桌面应用程序的UI开发。

从概念上讲,构图由组成,用于桌面 web kotlin Multiplatform 。您可能会熟悉移动开发中的后者。基本想法是在Kotlin中编写业务逻辑,并将其与本机用户界面结合使用。 Google和Jetbrains将JetPack移植到桌面上。这是可能的,因为JetPack组合仅与Android平台松散结合。 UI元素是使用开源2D图形库SKIA渲染的,顺便说一句,该元素也用于Chrome,Chromeos和Flutter中。在桌面上,组合用户界面托管在Java秋千窗口内。当前在JVM内运行的撰写应用程序。这是一件好事,因为您不仅可以使用所有Java和Kotlin库,还可以使用构建Java本机图像的工具。要在桌面上显示CounterDemo(),只需要几行代码:

fun main() = application {
  Window(
    title = TITLE,
    onCloseRequest = ::exitApplication,
  ) {
    MaterialTheme {
      Scaffold(
        topBar = {
          TopAppBar(
            title = {
              Text(text = TITLE)
            }
          )
        }
      ) {
        Box(
          modifier = Modifier.fillMaxSize(),
          contentAlignment = Center
        ) {
          CounterDemo()
        }
      }
    }
  }
}

CounterDemo running on macOS

您是否注意到此代码片段与Android版本非常相似?我们可以将许多部分重构为一个函数。只需要以另一种方式进行访问的字符串(使用Android stringResource())。

结论

您肯定记得我说桌面上有一种风格混合物。好吧,撰写多平台增加了另一种风味 - 材料设计。如果您想尝试撰写综合式介绍,还应查看附带的Googles Design系统附带的文档。这将使您穿越喷气背包的旅程变得更加容易。要开发该应用程序,您将使用Intellij作为IDE和Gradle作为构建系统。 JetPack仅适用于Kotlin,但是应用程序的所有其他部分都可以使用Java(或任何其他JVM语言)实现。

组成乘法的某些方面,例如构建和运行用户界面,已经感觉稳定且成熟。其他部分看起来仍在进行中。特别是,与主机系统的集成需要增强。例如,拖放和文件关联只能使用解决方案进行。有趣的是,在进一步的开发过程中,Jetbrains仍将提供哪些缺少的功能。编写桌面的撰写应用程序肯定很有趣。

链接


本文的德语版本首先出现在Informatik Aktuell