原文: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>
<!-- Add your project components here -->
<component>
<implementation-class>
components.JiraComponent
</implementation-class>
</component>
</project-components>
第二步:UI
在针对我们的设置页面进行下一步之前,我们需要了解如何通过使用Java Swing
在IntelliJ
上创建UI
。IntelliJ
有许多可以使用的Swing
组件,以保证插件UI
与IDE
中其它插件保持一致。 但不要被名字中带有Java
给欺骗了,因为您仍可将代码转换为Kotlin
。
创建新GUI
(图形用户界面)的一种方法是,只需右键单击并转到New
,然后单击GUI Form
。该操作将创建一个名为YourName.form
的新文件,该文件将链接到另一个名为YourName.java
的文件。相比于按照IntelliJ
给的编辑器模式进行开发,我更喜欢用我自己的方式,给一个提示:
我将会使用 Eclipse !(欢呼声)
我知道你在想什么,但老实说,它真的很棒。由于一些原因,IntelliJ
的编辑器确实很难用,我无法获得预期的效果,但是,如果你对IntelliJ
感到满意,请继续使用它!
译者注:我也并不喜欢
IDEA
官方的编辑器,但也没有很大必要去使用Eclipse
,因为使用Eclipse
只是对UI预览而已。
回到Eclipse
,您可以从这里下载它。 我目前有Oxygen.3a
版本,该版本有些旧,但是对于我们要做的并不重要。只需创建一个新项目,然后右键单击New
,Other
,然后选择JPanel
:
下图是我们的设置页预览:
接下来我们只需将创建好的源代码从Eclipse
复制过来就行了,然后就可以关闭Eclipse
了。
回到我们的插件,我们现在将创建一个名为settings
的新包和一个名为JiraSettings
的新类。 在该类中,我们将创建一个名为createComponent()
的新方法,最后我们可以在该方法中粘贴从Eclipse
复制的源代码。然后是时候将代码转换为Kotlin
,您应该也可以自动将其成功转换为Kotlin
。
完成所有这些操作后,您可能会遇到一些错误,因此请修复它们。
我们需要解决的第一件事是我们的createComponent()
方法必须返回一个JComponent
,具体原因接下来我们会说到。
因为Eclipse
假定我们已经在JPanel
中,所以您可以看到很多add
方法或似乎并不存在的方法,原因是因为我们不在JPanel
中。为解决该问题,我们必须创建一个新的JPanel
并给它一些边界(您可以从在Eclipse
中创建的JPanel
中获取值),并且由于JPanel
是JComponent
的子类,因此我们将在我们的方法中将其返回。
最后,我们只需要进行一些调整就可以编译整个程序,最终效果应该如下:
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
在继续开发设置页面前,我们必须讨论extensions
和extension points
。它们将允许您的插件与其他插件或与IDE
本身进行交互。
- 如果要扩展其他插件或
IDE
的功能,则必须声明一个或多个extensions
。 - 如果要让插件允许其他插件扩展其功能,则必须声明一个或多个
extension points
。
因为我们要将设置页面添加到Android Studio
的Preferences
中,所以我们真正要做的是扩展Android Studio
的功能,因此我们必须声明一个extensions
。
为此,我们必须实现Configurable
,同时还必须重写一些方法。
- 幸运的是,我们已经有了
createComponent()
方法,因此我们只需添加override
关键字就可以了。 - 我们将创建一个
boolean
的modified
,其默认值为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种方法: changeUpdate ,insertUpdate 和 removeUpdate。
在这些方法中,我们唯一要做的就是简单地将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
都会记住自身的设置。
这就是第三部分的全部内容。在下一篇文章中,我们将看到如何使用这些设置来创建新的Action
,将Jira
相关功能迁移过来。同时,如果您有任何疑问,请访问Twitter或发表评论。
《编写AndroidStudio插件》译文系列
- 译: 编写AndroidStudio插件(一):创建一个基本插件
- 译: 编写AndroidStudio插件(二):持久化数据
- 译: 编写AndroidStudio插件(三):设置页
- 译: 编写AndroidStudio插件(四):整合Jira
- 译: 编写AndroidStudio插件(五):本地化和通知
关于译者
Hello,我是 却把清梅嗅 ,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的 博客 或者 GitHub。
如果您觉得文章还差了那么点东西,也请通过 关注 督促我写出更好的文章——万一哪天我进步了呢?
- 我的Android学习体系
- 关于文章纠错
- 关于知识付费
- 关于《反思》系列