[译] 编写AndroidStudio插件(三):设置页

原文:Write an Android Studio Plugin Part 3: Settings
作者:Marcos Holgado
译者:却把清梅嗅
《编写AndroidStudio插件》系列是 IntelliJ IDEA 官方推荐的学习IDE插件开发的博客专栏,希望对有需要的读者有所帮助。

在本系列的第二部分中,我们学习了如何使用Component对数据进行持久化,以及通过这些数据,在用户更新我们的插件后展示更新了哪些新功能。在今天的文章中,我们将看到如何使用持久化的数据来创建设置页面。

请记住,您可以在GitHub上找到本系列的所有代码,还可以在对应的分支上查看每篇文章的相关代码,本文的代码在Part3分支中。

https://github.com/marcosholgado/plugin-medium

我们要做什么?

本文的目的是为我们的插件创建一个 设置页面,这将是我们迈向将JIRA搬运过来的第一步。我们的设置页面上只会有一个用户名和密码字段,我们的插件将使用该用户名和密码字段与Jira API进行交互。我们还希望能够为 每个项目 分别配不同的设置,从而允许用户根据项目使用不同的Jira帐户(这可能很有用)。

第一步:新建一个Project级别的Component

在本系列的第二部分中,我们已经了解了什么是Component,并且还了解了存在三种不同类型的Component。 因为我们希望能够根据我们的Android Studio的各Project进行不同的设置,因此显而易见的选择是创建一个新的Project Component

我们基本上是在复制和粘贴我们先前创建的Component,但是删除了所有不必要的方法并添加了两个新字段。这些字段将是public的,因为我们将在插件的其它部分中使用它们。

另一处不同是这次我们实现ProjectComponent接口并实现AbstractProjectComponent方法,当然,它的构造方法中也有一个project参数。最后,我们有一个companion object,通过一个project参数,以获取我们的JiraComponent的实例。这将使我们能够从插件中其他位置访问存储的数据。新的JiraComponent看起来像这样:

@State(name = "JiraConfiguration",
        storages = [Storage(value = "jiraConfiguration.xml")])
class JiraComponent(project: Project? = null) :
        AbstractProjectComponent(project),
        Serializable,
        PersistentStateComponent<JiraComponent> {
     

    var username: String = ""
    var password: String = ""

    override fun getState(): JiraComponent? = this

    override fun loadState(state: JiraComponent) =
            XmlSerializerUtil.copyBean(state, this)

    companion object {
     
        fun getInstance(project: Project): JiraComponent =
                project.getComponent(JiraComponent::class.java)
    }
}

如我们在上文所做的一样,我们还必须在plugin.xml文件中注册Component

<project-components>
    
    <component>
        <implementation-class>
            components.JiraComponent
        implementation-class>
    component>
project-components>

第二步:UI

在针对我们的设置页面进行下一步之前,我们需要了解如何通过使用Java SwingIntelliJ上创建UIIntelliJ有许多可以使用的Swing组件,以保证插件UIIDE中其它插件保持一致。 但不要被名字中带有Java给欺骗了,因为您仍可将代码转换为Kotlin

创建新GUI(图形用户界面)的一种方法是,只需右键单击并转到New,然后单击GUI Form。该操作将创建一个名为YourName.form的新文件,该文件将链接到另一个名为YourName.java的文件。相比于按照IntelliJ给的编辑器模式进行开发,我更喜欢用我自己的方式,给一个提示:

[译] 编写AndroidStudio插件(三):设置页_第1张图片

我将会使用 Eclipse !(欢呼声)

我知道你在想什么,但老实说,它真的很棒。由于一些原因,IntelliJ的编辑器确实很难用,我无法获得预期的效果,但是,如果你对IntelliJ感到满意,请继续使用它!

译者注:我也并不喜欢IDEA官方的编辑器,但也没有很大必要去使用Eclipse,因为使用Eclipse只是对UI预览而已。

回到Eclipse,您可以从这里下载它。 我目前有Oxygen.3a版本,该版本有些旧,但是对于我们要做的并不重要。只需创建一个新项目,然后右键单击NewOther,然后选择JPanel

下图是我们的设置页预览:

[译] 编写AndroidStudio插件(三):设置页_第2张图片

接下来我们只需将创建好的源代码从Eclipse复制过来就行了,然后就可以关闭Eclipse了。

回到我们的插件,我们现在将创建一个名为settings的新包和一个名为JiraSettings的新类。 在该类中,我们将创建一个名为createComponent()的新方法,最后我们可以在该方法中粘贴从Eclipse复制的源​​代码。然后是时候将代码转换为Kotlin,您应该也可以自动将其成功转换为Kotlin

完成所有这些操作后,您可能会遇到一些错误,因此请修复它们。

我们需要解决的第一件事是我们的createComponent()方法必须返回一个JComponent,具体原因接下来我们会说到。

因为Eclipse假定我们已经在JPanel中,所以您可以看到很多add方法或似乎并不存在的方法,原因是因为我们不在JPanel中。为解决该问题,我们必须创建一个新的JPanel并给它一些边界(您可以从在Eclipse中创建的JPanel中获取值),并且由于JPanelJComponent的子类,因此我们将在我们的方法中将其返回。

最后,我们只需要进行一些调整就可以编译整个程序,最终效果应该如下:

class JiraSettings {
     

    private val passwordField = JPasswordField()
    private val txtUsername = JTextField()

    fun createComponent(): JComponent {
     

        val mainPanel = JPanel()
        mainPanel.setBounds(0, 0, 452, 120)
        mainPanel.layout = null

        val lblUsername = JLabel("Username")
        lblUsername.setBounds(30, 25, 83, 16)
        mainPanel.add(lblUsername)

        val lblPassword = JLabel("Password")
        lblPassword.setBounds(30, 74, 83, 16)
        mainPanel.add(lblPassword)

        passwordField.setBounds(125, 69, 291, 26)
        mainPanel.add(passwordField)

        txtUsername.setBounds(125, 20, 291, 26)
        mainPanel.add(txtUsername)
        txtUsername.columns = 10

        return mainPanel
    }
}

第三步:Extensions 和 Extension points

在继续开发设置页面前,我们必须讨论extensionsextension points。它们将允许您的插件与其他插件或与IDE本身进行交互。

  • 如果要扩展其他插件或IDE的功能,则必须声明一个或多个extensions
  • 如果要让插件允许其他插件扩展其功能,则必须声明一个或多个extension points

因为我们要将设置页面添加到Android StudioPreferences中,所以我们真正要做的是扩展Android Studio的功能,因此我们必须声明一个extensions

为此,我们必须实现Configurable,同时还必须重写一些方法。

  • 幸运的是,我们已经有了createComponent()方法,因此我们只需添加override关键字就可以了。
  • 我们将创建一个booleanmodified,其默认值为false,并作为isModified()的返回值。我们稍后会再讲到这一点,目前它代表了设置页面apply按钮是否被启用。
  • 我们将getDisplayName()的返回值用于展示设置页的名字。
  • apply方法中,我们需要编写将在用户单击Apply时执行的代码。很简单,我们为用户所在的Project获取JiraComponent的实例,然后将UI中的值保存到Component中。最后,我们将Modify设置为false,届时我们要禁用Apply按钮。

最终展示效果如下:

class JiraSettings(private val project: Project): Configurable {
     

    private val passwordField = JPasswordField()
    private val txtUsername = JTextField()

    private var modified = false

    override fun isModified(): Boolean = modified

    override fun getDisplayName(): String = "MyPlugin Jira"

    override fun apply() {
     
        val config = JiraComponent.getInstance(project)
        config.username = txtUsername.text
        config.password = String(passwordField.password)

        modified = false
    }

    override fun createComponent(): JComponent {
     

        val mainPanel = JPanel()
        mainPanel.setBounds(0, 0, 452, 120)
        mainPanel.layout = null

        val lblUsername = JLabel("Username")
        lblUsername.setBounds(30, 25, 83, 16)
        mainPanel.add(lblUsername)

        val lblPassword = JLabel("Password")
        lblPassword.setBounds(30, 74, 83, 16)
        mainPanel.add(lblPassword)

        passwordField.setBounds(125, 69, 291, 26)
        mainPanel.add(passwordField)

        txtUsername.setBounds(125, 20, 291, 26)
        mainPanel.add(txtUsername)
        txtUsername.columns = 10

        return mainPanel
    }
}

第四步:解决最后的问题

我们几乎完成了,只剩下最后几个问题。

首先,我们要保存用户的偏好设置,但目前我们还未对其加载。UI是在createComponent()方法中创建的,因此我们只需要在返回之前添加以下代码,即可使用先前存储的值设置UI:

val config = JiraComponent.getInstance(project)
txtUsername.text = config.username
passwordField.text = config.password

接下来,我们将使用isModified()解决问题。 当用户修改设置页中的任何值时,我们需要以某种方式将值从false更改为true。一种非常简单的方法是实现 DocumentListener,该接口为我们提供了3种方法: changeUpdateinsertUpdateremoveUpdate

在这些方法中,我们唯一要做的就是简单地将Modify的值更改为true,最后将DocumentListener添加到我们的密码和用户名字段中。

override fun changedUpdate(e: DocumentEvent?) {
     
    modified = true
}

override fun insertUpdate(e: DocumentEvent?) {
     
    modified = true
}

override fun removeUpdate(e: DocumentEvent?) {
     
    modified = true
}

最终实现如下:

class JiraSettings(private val project: Project): Configurable, DocumentListener {
     
    private val passwordField = JPasswordField()
    private val txtUsername = JTextField()
    private var modified = false

    override fun isModified(): Boolean = modified

    override fun getDisplayName(): String = "MyPlugin Jira"

    override fun apply() {
     
        val config = JiraComponent.getInstance(project)
        config.username = txtUsername.text
        config.password = String(passwordField.password)
        modified = false
    }

    override fun changedUpdate(e: DocumentEvent?) {
     
        modified = true
    }

    override fun insertUpdate(e: DocumentEvent?) {
     
        modified = true
    }

    override fun removeUpdate(e: DocumentEvent?) {
     
        modified = true
    }

    override fun createComponent(): JComponent {
     

        val mainPanel = JPanel()
        mainPanel.setBounds(0, 0, 452, 120)
        mainPanel.layout = null

        val lblUsername = JLabel("Username")
        lblUsername.setBounds(30, 25, 83, 16)
        mainPanel.add(lblUsername)

        val lblPassword = JLabel("Password")
        lblPassword.setBounds(30, 74, 83, 16)
        mainPanel.add(lblPassword)

        passwordField.setBounds(125, 69, 291, 26)
        mainPanel.add(passwordField)

        txtUsername.setBounds(125, 20, 291, 26)
        mainPanel.add(txtUsername)
        txtUsername.columns = 10

        val config = JiraComponent.getInstance(project)
        txtUsername.text = config.username
        passwordField.text = config.password

        passwordField.document?.addDocumentListener(this)
        txtUsername.document?.addDocumentListener(this)

        return mainPanel
    }
}

第五步:声明 extension

Component相同,我们还必须在plugin.xml文件中声明extension

<extensions defaultExtensionNs="com.intellij">
    <defaultProjectTypeProvider type="Android"/>
    <projectConfigurable
            instance="settings.JiraSettings">
    projectConfigurable>
extensions>

大功告成!调试或安装插件时,您可以转到Android Studio中的Preferences/Other Settings,找到新的设置页。您也可以使用不同的Project进行测试,并且每个Project都会记住自身的设置。

[译] 编写AndroidStudio插件(三):设置页_第3张图片

这就是第三部分的全部内容。在下一篇文章中,我们将看到如何使用这些设置来创建新的Action,将Jira相关功能迁移过来。同时,如果您有任何疑问,请访问Twitter或发表评论。


《编写AndroidStudio插件》译文系列

  • 译: 编写AndroidStudio插件(一):创建一个基本插件
  • 译: 编写AndroidStudio插件(二):持久化数据
  • 译: 编写AndroidStudio插件(三):设置页
  • 译: 编写AndroidStudio插件(四):整合Jira
  • 译: 编写AndroidStudio插件(五):本地化和通知

关于译者

Hello,我是 却把清梅嗅 ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 博客 或者 GitHub。

如果您觉得文章还差了那么点东西,也请通过 关注 督促我写出更好的文章——万一哪天我进步了呢?

  • 我的Android学习体系
  • 关于文章纠错
  • 关于知识付费
  • 关于《反思》系列

你可能感兴趣的:([译] 编写AndroidStudio插件(三):设置页)