├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── dictionaries │ └── pc.xml ├── markdown-exported-files.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── uiDesigner.xml └── vcs.xml ├── GenerateButterKnife.gif ├── GenerateButterKnife1.gif ├── GenerateFindViewById.gif ├── GenerateFindViewById.iml ├── GenerateFindViewById.jar ├── README.md ├── resources └── META-INF │ └── plugin.xml └── src ├── EditorExt.kt ├── Ext.kt ├── ListExt.kt ├── ProjectExt.kt ├── PsiClass.kt ├── PsiFileExt.kt ├── StringExt.kt ├── actions ├── ButterKnifeAction.kt └── FindViewByIdAction.kt ├── constant └── Constant.kt ├── entitys ├── Element.kt └── IdBean.kt ├── utils ├── CreateMethodCreator.kt └── GenerateCreator.kt └── views ├── ButterKnifeDialog.kt ├── FindViewByIdDialog.kt └── GenerateDialog.kt /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /out 3 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/dictionaries/pc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/markdown-exported-files.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 32 | 33 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /GenerateButterKnife.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzailfm/GenerateFindViewById/4f180c4b30f8c0cfe855fbcb08507d89fa3f78ed/GenerateButterKnife.gif -------------------------------------------------------------------------------- /GenerateButterKnife1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzailfm/GenerateFindViewById/4f180c4b30f8c0cfe855fbcb08507d89fa3f78ed/GenerateButterKnife1.gif -------------------------------------------------------------------------------- /GenerateFindViewById.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzailfm/GenerateFindViewById/4f180c4b30f8c0cfe855fbcb08507d89fa3f78ed/GenerateFindViewById.gif -------------------------------------------------------------------------------- /GenerateFindViewById.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /GenerateFindViewById.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzailfm/GenerateFindViewById/4f180c4b30f8c0cfe855fbcb08507d89fa3f78ed/GenerateFindViewById.jar -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GenerateFindViewById 2 | 3 | ## 演示 4 | FindViewById 5 | ![演示](GenerateFindViewById.gif) 6 | 7 | ButterKnife 8 | ![演示](GenerateButterKnife.gif) 9 | ![演示](GenerateButterKnife1.gif) 10 | 11 | 12 | 13 | ## 更新 14 | - 1.4.2 Fix bugs with the same xml name for more than one Module. 15 | - 1.4.1 Code replace to Kotlin, add Forced Casts option, default true, and Replace code to Kotlin language.[Revision 26.0.0 Beta 1 Important changes](https://developer.android.google.cn/topic/libraries/support-library/revisions.html#26-0-0-beta1) 16 | - 1.4 Variable names only support mAaBbCc and aaBbCc naming formats, The switch block adds the default statement.[Alibaba Java Coding Guidelines](https://github.com/alibaba/p3c) 17 | - 1.3 Variable names support three naming formats: 1, mAaBbCc, 2, aa_bb_cc, 3, aaBbCc 18 | - 1.2.2 Onclick can select all or deselect all 19 | - 1.2.1 Fix ImageView and TextView can not click the OnClick option 20 | - 1.2 Support ViewHolder 21 | - 1.1.2 Button defaults to the OnClick option 22 | - 1.1 Support ButterKnife, version 8.4.0, shortcut keys Ctrl+Alt+Shift+E 23 | - 1.0 Support Activity and Fragment, shortcut keys Ctrl+Alt+E 24 | 25 | 26 | ## 安装 27 | - 下载项目里面的`GenerateFindViewById.ja`r包,然后打开AS的Plugins,点击`Install plugin from disk...`安装 28 | - 打开AS的Plugins,点击`Browse repositories...`然后搜索`GenerateFindViewById`,然后安装 29 | 30 | ## 快捷键 31 | - FindViewById:`Ctrl+Alt+E` 32 | - ButterKnife:`Ctrl+Alt+Shift+E` 33 | 34 | ## 说明 35 | - 可输入布局字段,可选中布局文件字段,自动生成有id控件相应的代码 36 | - `Activity`如果没有`onCreate`方法,会先生成`onCreate`方法,再重新操作一次才生成有id控件相应的代码。 37 | - `Fragment`如果没有`onCreateView`方法,会先生成`onCreateView`方法,再重新操作一次才生成有id控件相应的代码。 38 | - 可选生成的字段,可编辑变量名,可选择是否`LayoutInflater`类型。 39 | - `LayoutInflater`类型生成的变量规则,如`LayoutInflater`的变量为`mView`,生成控件变量后面会加上`View`。 40 | 41 | ## 用法 42 | 1. 新建`Activity`或者`Fragment`后,选中布局按下快捷键`Alt+Insert`,然后选择`FindViewById`或者在菜单栏中的`Code`中选择`FindViewById`,或者直接使用快捷键`Ctrl+Alt+E` 43 | 2. 如果没有选中布局,会弹出输入框,输入布局,插件会自动遍历布局列出所有带id的控件 44 | 3. 会自动检测是否已有代码,可选择是否生成、是否生成`OnClick`代码,可编辑变量名 45 | 4. 可选择是否生成`View view = LayoutInflater.from(context).inflater()`代码,可编辑生成的`View`的变量名 46 | 5. 点击确认生成 47 | 48 | ## ButterKnife用法 49 | 1. 新建`Activity`或者`Fragment`后,选中布局按下快捷键`Alt+Insert`,然后选择`ButterKnife`或者在菜单栏中的`Code`中选择`ButterKnife`,或者直接使用快捷键`Ctrl+Alt+Shift+E` 50 | 2. 如果没有选中布局,会弹出输入框,输入布局,插件会自动遍历布局列出所有带id的控件 51 | 3. 会自动检测是否已有代码,可选择是否生成、是否生成`OnClick`代码,可编辑变量名 52 | 4. 可选择是否生成`View view = LayoutInflater.from(context).inflater()`代码,可编辑生成的`View`的变量名 53 | 5. 点击确认生成 54 | 55 | ## 规则 56 | 1. `Activity`如果没有`onCreate`方法,会先生成`onCreate`方法,`Fragment`如果没有`onCreateView`方法,会先生成`onCreateView`方法 57 | 2. 没有id的控件是不会识别到的 58 | 3. 识别到的控件变量名为`mAaBbCc`命名 59 | 4. 识别到的控件中有`clickable = true`属性,自动生成`setOnClickListener`代码和`onClick`方法 60 | 5. 识别到的控件`ButterKnife`都可以生成到`onClick`方法 61 | 6. 自动识别布局中的`include`标签, 读取对应布局中的控件 62 | 7. 识别到的控件中有`text`或者`hint`属性,会自动生成里面的值到字段注释 63 | 8. `LayoutInflater`生成的变量名规则为`mAaBbCc+View`的变量名(如`mView`会去掉`m`) 64 | 9. `ButterKnife`生成的`LayoutInflater`是`ButterKnife.findById()`来替换`view.findViewById()` 65 | 66 | ## License 67 | ``` 68 | Copyright 2016 Jowan 69 | 70 | Licensed under the Apache License, Version 2.0 (the "License"); 71 | you may not use this file except in compliance with the License. 72 | You may obtain a copy of the License at 73 | 74 | http://www.apache.org/licenses/LICENSE-2.0 75 | 76 | Unless required by applicable law or agreed to in writing, software 77 | distributed under the License is distributed on an "AS IS" BASIS, 78 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 79 | See the License for the specific language governing permissions and 80 | limitations under the License. 81 | ``` -------------------------------------------------------------------------------- /resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.wangzai.plugin.findViewById 3 | GenerateFindViewById 4 | 1.4.3 5 | GitHub 6 | 7 | 9 | If there is a problem, please go to my GitHub above issues.
10 | ]]>
11 | 12 | 14 | 1.4.2 Fix bugs with the same xml name for more than one Module.
15 | 1.4.1 Code replace to Kotlin, add Forced Casts option, default true, and Replace code to Kotlin language.
16 | Revision 26.0.0 Beta 1 Important changes
17 | 1.4 Variable names only support mAaBbCc and aaBbCc naming formats, The switch block adds the default statement.
18 | Alibaba Java Coding Guidelines
19 | 1.3 Variable names support three naming formats: 1, mAaBbCc, 2, aa_bb_cc, 3, aaBbCc
20 | 1.2.2 Onclick can select all or deselect all
21 | 1.2.1 Fix ImageView and TextView can not click the OnClick option
22 | 1.2 Support ViewHolder
23 | 1.1.2 Button defaults to the OnClick option
24 | 1.1 Support ButterKnife, version 8.4.0, shortcut keys Ctrl+Alt+Shift+E
25 | 1.0 Support Activity and Fragment, shortcut keys Ctrl+Alt+E
26 | ]]> 27 |
28 | 29 | 30 | 31 | 32 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
-------------------------------------------------------------------------------- /src/EditorExt.kt: -------------------------------------------------------------------------------- 1 | import com.intellij.openapi.application.ApplicationManager 2 | import com.intellij.openapi.editor.Editor 3 | import com.intellij.openapi.ui.popup.Balloon 4 | import com.intellij.openapi.ui.popup.JBPopupFactory 5 | import com.intellij.psi.PsiClass 6 | import com.intellij.psi.PsiFile 7 | import com.intellij.psi.SyntheticElement 8 | import com.intellij.psi.util.PsiTreeUtil 9 | import com.intellij.ui.JBColor 10 | import constant.Constant 11 | import java.awt.Color 12 | 13 | 14 | /** 15 | * 显示dialog 16 | * @param editor editor 17 | * 18 | * @param result 内容 19 | * 20 | * @param time 显示时间,单位秒 21 | */ 22 | fun Editor.showPopupBalloon(result: String?, time: Int) { 23 | ApplicationManager.getApplication().invokeLater { 24 | val factory = JBPopupFactory.getInstance() 25 | factory.createHtmlTextBalloonBuilder(result ?: Constant.Ext.UNKNOWN_ERROR, null, JBColor(Color(116, 214, 238), Color(76, 112, 117)), null).setFadeoutTime((time * 1000).toLong()).createBalloon().show(factory.guessBestPopupLocation(this), Balloon.Position.below) 26 | } 27 | result?.outInfo() 28 | } 29 | 30 | /** 31 | * 根据当前文件获取对应的class文件 32 | * @param editor editor 33 | * 34 | * @param file file 35 | * 36 | * @return PsiClass 37 | */ 38 | infix fun Editor.getTargetClass(file: PsiFile?): PsiClass? { 39 | val offset = this.caretModel.offset 40 | val element = file?.findElementAt(offset) ?: return null 41 | val target = PsiTreeUtil.getParentOfType(element, PsiClass::class.java) 42 | return if (target is SyntheticElement) null else target 43 | } -------------------------------------------------------------------------------- /src/Ext.kt: -------------------------------------------------------------------------------- 1 | import com.intellij.psi.* 2 | import entitys.Element 3 | import org.apache.commons.lang.StringUtils 4 | import java.util.* 5 | 6 | 7 | /** 8 | * 创建onCreateView方法 9 | * 10 | * @param mIsButterKnife mIsButterKnife 11 | * 12 | * @return String 13 | */ 14 | fun createOnCreateViewMethod(mIsButterKnife: Boolean): String { 15 | val method = StringBuilder() 16 | method.append("@Nullable @Override public View onCreateView(android.view.LayoutInflater inflater, @Nullable android.view.ViewGroup container, @Nullable android.os.Bundle savedInstanceState) {\n") 17 | method.append("\t// TODO:OnCreateView Method has been created, run ") 18 | method.append(if (!mIsButterKnife) "FindViewById" else "ButterKnife") 19 | method.append(" again to generate code\n") 20 | method.append(if (!mIsButterKnife) "\t\tinitView(view);\n" else "\t\tunbinder = ButterKnife.bind(this, view);\n") 21 | method.append("return view;") 22 | method.append("}\n") 23 | return method.toString() 24 | } 25 | 26 | /** 27 | * 创建initView方法,Fragment 28 | 29 | * @return String 30 | */ 31 | fun createFragmentInitViewMethod(): String = "public void initView(View view) {\n}" 32 | 33 | /** 34 | * 创建initView方法 35 | * 36 | * @return String 37 | */ 38 | fun createInitViewMethod(): String = "public void initView() {\n}" 39 | 40 | /** 41 | * 创建OnDestroyView方法,里面包含unbinder.unbind() 42 | * 43 | * @return String 44 | */ 45 | fun createOnDestroyViewMethod(): String = "@Override public void onDestroyView() {\n" + 46 | "\tsuper.onDestroyView();" + 47 | "\tunbinder.unbind();" + 48 | "}" 49 | 50 | /** 51 | * 判断是否实现了OnClickListener接口 52 | * @param referenceElements referenceElements 53 | * 54 | * @return boolean 55 | */ 56 | fun isImplementsOnClickListener(referenceElements: Array): Boolean { 57 | referenceElements.forEach { 58 | if (it.text.contains("OnClickListener")) { 59 | return true 60 | } 61 | } 62 | return false 63 | } 64 | 65 | 66 | /** 67 | * 获取OnClickList里面的id集合 68 | * 69 | * @param mOnClickList clickable的Element集合 70 | * 71 | * @return List 72 | */ 73 | fun getOnClickListById(mOnClickList: ArrayList): ArrayList { 74 | val list: ArrayList = ArrayList() 75 | mOnClickList.forEach { list.add(it.fullID) } 76 | return list 77 | } 78 | 79 | /** 80 | * 获取注解里面跟OnClickList的id集合 81 | * 82 | * @param annotationList OnClick注解里面的id集合 83 | * 84 | * @param onClickIdList clickable的Element集合 85 | * 86 | * @return List 87 | */ 88 | fun createOnClickValue(annotationList: ArrayList, onClickIdList: ArrayList): ArrayList { 89 | onClickIdList 90 | .filterNot { annotationList.contains(it) } 91 | .forEach { annotationList.add(it) } 92 | return annotationList 93 | } 94 | 95 | 96 | /** 97 | * FindViewById,创建findViewById代码到initView方法里面 98 | * 99 | * @param findPre Fragment的话要view.findViewById 100 | * 101 | * @param mIsLayoutInflater 是否选中LayoutInflater 102 | * 103 | * @param mLayoutInflaterText 选中的布局的变量名 104 | * 105 | * @param context context 106 | * 107 | * @param mSelectedText 选中的布局 108 | * 109 | * @param mElements Element的List 110 | * 111 | * @param mLayoutInflaterType type 112 | * 113 | * @param mNeedCast 是否需要强转 114 | * 115 | * @return String 116 | */ 117 | fun createFieldsByInitViewMethod(findPre: String?, mIsLayoutInflater: Boolean, 118 | mLayoutInflaterText: String, context: String, 119 | mSelectedText: String, mElements: ArrayList, 120 | mLayoutInflaterType: Int, mNeedCast: Boolean): String { 121 | val initView = StringBuilder() 122 | initView.append(if (StringUtils.isEmpty(findPre)) "private void initView() {\n" else "private void initView(View $findPre) {\n") 123 | if (mIsLayoutInflater) { 124 | // 添加LayoutInflater.from(this).inflate(R.layout.activity_main, null); 125 | val layoutInflater = "$mLayoutInflaterText = LayoutInflater.from($context).inflate(R.layout.$mSelectedText, null);\n" 126 | initView.append(layoutInflater) 127 | } 128 | mElements.filter(Element::isEnable).forEach { 129 | with(it) { 130 | var pre = findPre?.let { findPre + "." } ?: "" 131 | val inflater = if (mIsLayoutInflater) layoutInflaterType2Str(mLayoutInflaterText, mLayoutInflaterType) else "" 132 | pre = if (!mIsLayoutInflater) pre else mLayoutInflaterText + "." 133 | initView.append("$fieldName$inflater = ") 134 | if (mNeedCast) { 135 | initView.append("($name)") 136 | } 137 | initView.append("${pre}findViewById($fullID);\n") 138 | if (isClickable && isClickEnable) { 139 | initView.append("$fieldName$inflater.setOnClickListener(this);\n") 140 | } 141 | } 142 | } 143 | initView.append("}\n") 144 | return initView.toString() 145 | } 146 | 147 | 148 | /** 149 | * ButterKnife,创建findById代码到init方法里面 150 | * 151 | * @param mIsLayoutInflater mIsLayoutInflater 152 | * 153 | * @param mLayoutInflaterText mLayoutInflaterText 154 | * 155 | * @param context context 156 | * 157 | * @param mSelectedText mSelectedText 158 | * 159 | * @param mElements mElements 160 | * 161 | * @param viewMethodName viewMethodName 162 | * 163 | * @return String 164 | */ 165 | fun createButterKnifeViewMethod(mIsLayoutInflater: Boolean, 166 | mLayoutInflaterText: String, context: String, mSelectedText: 167 | String, mElements: ArrayList, viewMethodName: String, 168 | mLayoutInflaterType: Int): String { 169 | val initView = StringBuilder() 170 | initView.append("// TODO:Copy method name to use\n") 171 | initView.append("private void $viewMethodName() {\n") 172 | if (mIsLayoutInflater) { 173 | // 添加LayoutInflater.from(this).inflate(R.layout.activity_main, null); 174 | val layoutInflater = "$mLayoutInflaterText = LayoutInflater.from($context).inflate(R.layout.$mSelectedText, null);\n" 175 | initView.append(layoutInflater) 176 | } 177 | mElements.filter(Element::isEnable).forEach { 178 | val inflater = if (mIsLayoutInflater) layoutInflaterType2Str(mLayoutInflaterText, mLayoutInflaterType) else "" 179 | initView.append("${it.fieldName}$inflater = ButterKnife.findById($mLayoutInflaterText, ${it.fullID});\n") 180 | } 181 | initView.append("}\n") 182 | return initView.toString() 183 | } -------------------------------------------------------------------------------- /src/ListExt.kt: -------------------------------------------------------------------------------- 1 | import entitys.Element 2 | import java.util.* 3 | 4 | /** 5 | * FindViewById,创建OnClick方法和switch 6 | * 7 | * @param mOnClickList 可onclick的Element的集合 8 | * 9 | * @return String 10 | */ 11 | fun ArrayList.createFindViewByIdOnClickMethodAndSwitch(): String { 12 | val onClick = StringBuilder() 13 | onClick.append("@Override public void onClick(View v) {\n") 14 | onClick.append("switch (v.getId()) {\n") 15 | // add default statement 16 | onClick.append("\tdefault:\n") 17 | onClick.append("\t\tbreak;\n") 18 | this.filter(Element::isClickable).forEach { 19 | onClick.append("\tcase ${it.fullID}:\n") 20 | onClick.append("\t\tbreak;\n") 21 | } 22 | onClick.append("}\n") 23 | onClick.append("}\n") 24 | return onClick.toString() 25 | } 26 | 27 | /** 28 | * ButterKnife,创建OnClick方法和switch 29 | * 30 | * @param mOnClickList 可onclick的Element的集合 31 | * 32 | * @return String 33 | */ 34 | fun ArrayList.createButterKnifeOnClickMethodAndSwitch(): String { 35 | val onClick = StringBuilder() 36 | onClick.append("@butterknife.OnClick(") 37 | if (this.size == 1) { 38 | onClick.append(this[0].fullID) 39 | } else { 40 | onClick.append("{") 41 | this.forEachIndexed { i, element -> 42 | if (i != 0) { 43 | onClick.append(", ") 44 | } 45 | onClick.append(element.fullID) 46 | } 47 | onClick.append("}") 48 | } 49 | onClick.append(")\n") 50 | onClick.append("public void onClick(View v) {\n") 51 | onClick.append("switch (v.getId()) {\n") 52 | // add default statement 53 | onClick.append("\tdefault:\n") 54 | onClick.append("\t\tbreak;\n") 55 | this.forEach { 56 | onClick.append("\tcase ${it.fullID}:\n") 57 | onClick.append("\t\tbreak;\n") 58 | } 59 | onClick.append("}\n") 60 | onClick.append("}\n") 61 | return onClick.toString() 62 | } 63 | -------------------------------------------------------------------------------- /src/ProjectExt.kt: -------------------------------------------------------------------------------- 1 | 2 | import com.intellij.openapi.project.Project 3 | import com.intellij.psi.JavaPsiFacade 4 | import com.intellij.psi.PsiClass 5 | import com.intellij.psi.PsiFile 6 | import com.intellij.psi.search.EverythingGlobalScope 7 | import com.intellij.psi.search.FilenameIndex 8 | import com.intellij.psi.search.GlobalSearchScope 9 | import entitys.Element 10 | 11 | infix fun Project.findClassByProject(className: String) = 12 | JavaPsiFacade.getInstance(this).findClass(className, EverythingGlobalScope(this)) 13 | 14 | /** 15 | * 判断mClass是不是继承activityClass或者activityCompatClass 16 | * @param mProject mProject 17 | * 18 | * @param mClass mClass 19 | * 20 | * @return boolean 21 | */ 22 | infix fun Project.isExtendsActivityOrActivityCompat(mClass: PsiClass): Boolean { 23 | // 根据类名查找类 24 | val activityClass = this findClassByProject "android.app.Activity" 25 | val activityCompatClass = this findClassByProject "android.support.v7.app.AppCompatActivity" 26 | return activityClass != null && mClass checkInheritor activityClass 27 | || activityCompatClass != null && mClass checkInheritor activityCompatClass 28 | } 29 | 30 | /** 31 | * 判断mClass是不是继承fragmentClass或者fragmentV4Class 32 | * @param mProject mProject 33 | * 34 | * @param mClass mClass 35 | * 36 | * @return boolean 37 | */ 38 | infix fun Project.isExtendsFragmentOrFragmentV4(mClass: PsiClass): Boolean { 39 | // 根据类名查找类 40 | val fragmentClass = this findClassByProject "android.app.Fragment" 41 | val fragmentV4Class = this findClassByProject "android.support.v4.app.Fragment" 42 | return fragmentClass != null && mClass checkInheritor fragmentClass 43 | || fragmentV4Class != null && mClass checkInheritor fragmentV4Class 44 | } 45 | /** 46 | * FindViewById,获取xml里面的text 47 | * 48 | * @param element Element 49 | * 50 | * @param mProject Project 51 | * 52 | * @param psiFile PsiFile 53 | * 54 | * @return String 55 | */ 56 | fun Project.createFieldText(element: Element, psiFile: PsiFile): String? { 57 | // 如果是text为空,则获取hint里面的内容 58 | var text: String? = element.xml.getAttributeValue("android:text") ?: element.xml.getAttributeValue("android:hint") 59 | text?.let { 60 | if (it.contains("@string/")) { 61 | text = it.replace("@string/".toRegex(), "") 62 | // 获取strings.xml 63 | val psiFiles = FilenameIndex.getFilesByName(this, "strings.xml", GlobalSearchScope.allScope(this)) 64 | if (psiFiles.isNotEmpty()) { 65 | psiFiles 66 | .filter { 67 | // 获取src\main\res\values下面的strings.xml文件 68 | it.parent != null && it.parent!!.toString().contains("src\\main\\res\\values", false) 69 | } 70 | .filter { 71 | it.parent!!.toString().outInfo() 72 | val psiFilePath = psiFile.parent?.toString()!! 73 | val modulePath = it.parent?.toString()!! 74 | psiFilePath.substring(0, psiFilePath.indexOf("\\main\\")) == modulePath.substring(0, modulePath.indexOf("\\main\\")) 75 | } 76 | .forEach { text = it.getTextFromStringsXml(text!!) } 77 | } 78 | } 79 | } 80 | return text 81 | } -------------------------------------------------------------------------------- /src/PsiClass.kt: -------------------------------------------------------------------------------- 1 | import com.intellij.psi.* 2 | import constant.Constant 3 | import org.apache.commons.lang.StringUtils 4 | import java.util.* 5 | 6 | /** 7 | * 获取initView方法里面的每条数据 8 | 9 | * @param mClass mClass 10 | * 11 | * @return PsiStatement[] 12 | */ 13 | fun PsiClass.getInitViewBodyStatements(): Array? { 14 | // 获取initView方法 15 | val method = this.findMethodsByName(Constant.Ext.CREATOR_INITVIEW_NAME, false) 16 | return if (method.isNotEmpty() && method[0].body != null) 17 | method[0].body?.statements 18 | else null 19 | } 20 | 21 | /** 22 | * 获取onClick方法里面的每条数据 23 | 24 | * @param mClass mClass 25 | * 26 | * @return PsiElement[] 27 | */ 28 | fun PsiClass.getOnClickStatement(): Array? { 29 | // 获取onClick方法 30 | val onClickMethods = this.findMethodsByName(Constant.FIELD_ONCLICK, false) 31 | return if (onClickMethods.isNotEmpty() && onClickMethods[0].body != null) 32 | onClickMethods[0].body?.children 33 | else null 34 | } 35 | /** 36 | * 获取包含@OnClick注解的方法 37 | 38 | * @param mClass mClass 39 | * 40 | * @return PsiMethod 41 | */ 42 | fun PsiClass.getPsiMethodByButterKnifeOnClick(): PsiMethod? { 43 | for (psiMethod in this.methods) { 44 | // 获取方法的注解 45 | val modifierList = psiMethod.modifierList 46 | val annotations = modifierList.annotations 47 | annotations 48 | .map { it.qualifiedName } 49 | .filter { it != null && it == "butterknife.OnClick" } 50 | .forEach { 51 | // 包含@OnClick注解 52 | return psiMethod 53 | } 54 | } 55 | return null 56 | } 57 | 58 | /** 59 | * 获取View类型的变量名 60 | 61 | * @param mClass mClass 62 | * 63 | * @return String 64 | */ 65 | fun PsiClass.getPsiMethodParamsViewField(): String? { 66 | val butterKnifeOnClickMethod = this.getPsiMethodByButterKnifeOnClick() 67 | if (butterKnifeOnClickMethod != null) { 68 | // 获取方法的指定参数类型的变量名 69 | val parameterList = butterKnifeOnClickMethod.parameterList 70 | val parameters = parameterList.parameters 71 | parameters 72 | .filter { it.typeElement != null && it.typeElement?.text == "View" } 73 | .forEach { return it.name } 74 | } 75 | return null 76 | } 77 | 78 | /** 79 | * 获取包含@OnClick注解里面的值 80 | * 81 | * @return List 82 | */ 83 | fun PsiClass.getPsiMethodByButterKnifeOnClickValue(): ArrayList { 84 | val onClickValue = ArrayList() 85 | this.getPsiMethodByButterKnifeOnClick()?.let { 86 | // 获取方法的注解 87 | val modifierList = it.modifierList 88 | modifierList.annotations.filter { 89 | it.qualifiedName != null && it.qualifiedName == "butterknife.OnClick" 90 | }.forEach { 91 | val text = it.text.replace("(", "") 92 | .replace(")", "") 93 | .replace("{", "") 94 | .replace("}", "") 95 | .replace(" ", "") 96 | .replace("@OnClick", "") 97 | if (!StringUtils.isEmpty(text)) { 98 | val split = text.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray().dropLastWhile(String::isEmpty).toTypedArray() 99 | split.filterNotTo(onClickValue) { StringUtils.isEmpty(it) } 100 | } 101 | return@let 102 | } 103 | } 104 | return onClickValue 105 | } 106 | 107 | /** 108 | * 添加注解到方法 109 | 110 | * @param mClass mClass 111 | * 112 | * @param mFactory mFactory 113 | * 114 | * @param onClickValue onClickValue 115 | */ 116 | fun PsiClass.createOnClickAnnotation(mFactory: PsiElementFactory, onClickValue: ArrayList) { 117 | this.getPsiMethodByButterKnifeOnClick()?.let { 118 | // 获取方法的注解 119 | val modifierList = it.modifierList 120 | modifierList.annotations.filter { 121 | it.qualifiedName != null && it.qualifiedName == "butterknife.OnClick" 122 | }.forEach { 123 | val annotationText = StringBuilder() 124 | annotationText.append("@OnClick(") 125 | if (onClickValue.size == 1) { 126 | annotationText.append(onClickValue[0]) 127 | } else { 128 | annotationText.append("{") 129 | onClickValue.forEachIndexed { index, value -> 130 | if (index != 0) { 131 | annotationText.append(", ") 132 | } 133 | annotationText.append(value) 134 | } 135 | annotationText.append("}") 136 | } 137 | annotationText.append(")") 138 | modifierList.addBefore(mFactory.createAnnotationFromText(annotationText.toString(), modifierList), it) 139 | it.delete() 140 | return@let 141 | } 142 | } 143 | } 144 | /** 145 | * 判断是否存在ButterKnife.bind(this)/ButterKnife.bind(this, view) 146 | * @param mClass mClass 147 | * 148 | * @return boolean 149 | */ 150 | fun PsiClass.isButterKnifeBindExist(): Boolean { 151 | val onCreateMethod = this.getPsiMethodByName(Constant.PSI_METHOD_BY_ONCREATE) 152 | val onCreateViewMethod = this.getPsiMethodByName(Constant.PSI_METHOD_BY_ONCREATEVIEW) 153 | return !(onCreateMethod != null && onCreateMethod.body != null 154 | && onCreateMethod.body!!.text.contains(Constant.Ext.FIELD_BUTTERKNIFE_BIND) 155 | || onCreateViewMethod != null && onCreateViewMethod.body != null 156 | && onCreateViewMethod.body!!.text.contains(Constant.Ext.FIELD_BUTTERKNIFE_BIND)) 157 | } 158 | 159 | 160 | /** 161 | * 根据方法名获取方法 162 | * 163 | * @param mClass mClass 164 | * 165 | * @param methodName methodName 166 | * 167 | * @return PsiMethod 168 | */ 169 | fun PsiClass.getPsiMethodByName(methodName: String): PsiMethod? = 170 | this.methods.firstOrNull { it.name == methodName } 171 | -------------------------------------------------------------------------------- /src/PsiFileExt.kt: -------------------------------------------------------------------------------- 1 | import com.intellij.psi.PsiClass 2 | import com.intellij.psi.PsiElement 3 | import com.intellij.psi.PsiFile 4 | import com.intellij.psi.XmlRecursiveElementVisitor 5 | import com.intellij.psi.search.FilenameIndex 6 | import com.intellij.psi.search.GlobalSearchScope 7 | import com.intellij.psi.xml.XmlFile 8 | import com.intellij.psi.xml.XmlTag 9 | import constant.Constant 10 | import entitys.Element 11 | import org.apache.commons.lang.StringUtils 12 | import java.util.* 13 | import java.util.regex.Pattern 14 | 15 | fun PsiFile.fileAccept(accept: (element: PsiElement) -> Unit) = 16 | this.accept(object : XmlRecursiveElementVisitor() { 17 | override fun visitElement(element: PsiElement) { 18 | super.visitElement(element) 19 | accept.invoke(element) 20 | } 21 | }) 22 | 23 | /** 24 | * 获取所有id 25 | * @param file file 26 | * 27 | * @param elements elements 28 | */ 29 | fun getIDsFromLayoutToList(psiFile: PsiFile, elements: ArrayList) { 30 | psiFile.fileAccept { element -> 31 | // 解析Xml标签 32 | if (element is XmlTag) { 33 | with(element) { 34 | // 获取Tag的名字(TextView)或者自定义 35 | val name: String = name 36 | // 如果有include 37 | if (name.equals("include", ignoreCase = true)) { 38 | // 获取布局 39 | val layout = getAttribute("layout", null) ?: return@fileAccept 40 | val layoutName = layout.value.getLayoutName() ?: return@fileAccept 41 | // 获取project 42 | val project = this.project 43 | // 布局文件 44 | var include: XmlFile? = null 45 | val psiFiles = FilenameIndex.getFilesByName(project, layoutName + Constant.SELECTED_TEXT_SUFFIX, GlobalSearchScope.allScope(project)) 46 | if (psiFiles.isNotEmpty()) { 47 | include = if (psiFiles.size > 1) { 48 | val psiFilePath = psiFile.parent?.toString()!! 49 | val psiFiles1 = psiFiles.filter { 50 | val modulePath = it.parent?.toString()!! 51 | modulePath.contains("\\src\\main\\res\\layout") && psiFilePath.substring(0, psiFilePath.indexOf("\\main\\")) == modulePath.substring(0, modulePath.indexOf("\\main\\")) 52 | } 53 | if (psiFiles1.isEmpty()) return@fileAccept else psiFiles1[0] as XmlFile 54 | } else { 55 | psiFiles[0] as XmlFile 56 | } 57 | } 58 | if (include != null) { 59 | // 递归 60 | getIDsFromLayoutToList(include, elements) 61 | return@fileAccept 62 | } 63 | } 64 | // 获取id字段属性 65 | val id = getAttribute("android:id", null) ?: return@fileAccept 66 | // 获取id的值 67 | val idValue = id.value ?: return@fileAccept 68 | // 获取clickable 69 | val clickableAttr = getAttribute("android:clickable", null) 70 | var clickable = false 71 | if (clickableAttr != null && !StringUtils.isEmpty(clickableAttr.value)) { 72 | clickable = clickableAttr.value == "true" 73 | } 74 | if (name == "Button") { 75 | clickable = true 76 | } 77 | // 添加到list 78 | try { 79 | val e = Element(name, idValue, clickable, this) 80 | elements.add(e) 81 | } catch (ignored: IllegalArgumentException) { 82 | ignored.printStackTrace() 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | /** 90 | * 解析xml获取string的值 91 | 92 | * @param psiFile psiFile 93 | * 94 | * @param text text 95 | * 96 | * @return String 97 | */ 98 | fun PsiFile.getTextFromStringsXml(text: String): String? { 99 | var stringValue: String? = null 100 | this.fileAccept { element -> 101 | if (element is XmlTag) { 102 | with(element) { 103 | if (name == "string" && getAttributeValue("name") == text) { 104 | val value = StringBuilder() 105 | children.forEach { 106 | value.append(it.text) 107 | } 108 | // value = My Application 109 | // 用正则获取值 110 | val p = Pattern.compile("(.*)") 111 | val m = p.matcher(value.toString()) 112 | while (m.find()) { 113 | stringValue = m.group(1) 114 | } 115 | } 116 | } 117 | } 118 | } 119 | return stringValue 120 | } 121 | 122 | infix fun PsiClass.checkInheritor(toClass: PsiClass) = this.isInheritor(toClass, true) 123 | -------------------------------------------------------------------------------- /src/StringExt.kt: -------------------------------------------------------------------------------- 1 | 2 | import com.intellij.notification.* 3 | import constant.Constant 4 | import entitys.Element 5 | import org.apache.commons.lang.StringUtils 6 | import java.util.* 7 | 8 | 9 | /** 10 | * 第一个字母大写 11 | * 12 | * @param key key 13 | * 14 | * @return String 15 | */ 16 | fun String.firstToUpperCase(): String = this.substring(0, 1).toUpperCase(Locale.CHINA) + this.substring(1) 17 | 18 | /** 19 | * 输出到Log窗口 20 | */ 21 | fun String.outInfo() { 22 | NotificationsConfiguration.getNotificationsConfiguration().register(Constant.GENERATEFINDVIEWBYID, NotificationDisplayType.NONE) 23 | Notifications.Bus.notify( 24 | Notification(Constant.GENERATEFINDVIEWBYID, "${Constant.GENERATEFINDVIEWBYID} [INFO]", this, NotificationType.INFORMATION)) 25 | } 26 | 27 | /** 28 | * 根据layoutInflaterType生成不同内容 29 | * 30 | * @param mLayoutInflaterText mLayoutInflaterText 31 | * 32 | * @param mLayoutInflaterType mLayoutInflaterType 33 | * 34 | * @return String 35 | */ 36 | fun layoutInflaterType2Str(mLayoutInflaterText: String, mLayoutInflaterType: Int): String = when (mLayoutInflaterType) { 37 | 1 -> "_$mLayoutInflaterText" 38 | 2 -> mLayoutInflaterText.firstToUpperCase() 39 | else -> mLayoutInflaterText.substring(1) 40 | } 41 | 42 | /** 43 | * layout.getValue()返回的值为@layout/layout_view 44 | * @param layout layout 45 | * 46 | * @return String 47 | */ 48 | fun String?.getLayoutName(): String? { 49 | if (this == null || !this.startsWith("@") || !this.contains("/")) { 50 | return null 51 | } 52 | 53 | // @layout layout_view 54 | val parts = this.split("/".toRegex()).dropLastWhile(String::isEmpty).toTypedArray() 55 | if (parts.size != 2) { 56 | return null 57 | } 58 | // layout_view 59 | return parts[1] 60 | } 61 | 62 | /** 63 | * 驼峰 64 | * 65 | * @param fieldNames fieldName 66 | * 67 | * @param type type 68 | * 69 | * @return String 70 | */ 71 | infix fun String.getFieldName(type: Int): String { 72 | var fieldName = this 73 | if (!StringUtils.isEmpty(fieldName)) { 74 | val names = fieldName.split("_".toRegex()).dropLastWhile(String::isEmpty).toTypedArray() 75 | when (type) { 76 | 2 -> { 77 | // aaBbCc 78 | val sb = StringBuilder() 79 | for (i in names.indices) { 80 | if (i == 0) { 81 | sb.append(names[i]) 82 | } else { 83 | sb.append(names[i].firstToUpperCase()) 84 | } 85 | } 86 | sb.append("View") 87 | fieldName = sb.toString() 88 | } 89 | 3 -> { 90 | // mAaBbCc 91 | val sb = StringBuilder() 92 | for (i in names.indices) { 93 | if (i == 0) { 94 | sb.append("m") 95 | } 96 | sb.append(names[i].firstToUpperCase()) 97 | } 98 | sb.append("View") 99 | fieldName = sb.toString() 100 | } 101 | else -> fieldName += "_view" 102 | } 103 | } 104 | return fieldName 105 | } 106 | 107 | /** 108 | * 创建onCreate方法(Fragment) 109 | * 110 | * @param mSelectedText mSelectedText 111 | * 112 | * @return String 113 | */ 114 | fun String.createFragmentOnCreateMethod(): String = "@Override public void onCreate(@Nullable android.os.Bundle savedInstanceState) {\n" + 115 | "super.onCreate(savedInstanceState);\n" + 116 | "\tview = View.inflate(getActivity(), R.layout.$this, null);\n" + 117 | "}" 118 | 119 | /** 120 | * FindViewById,创建字段 121 | * 122 | * @param text 注释内容 123 | * 124 | * @param element Element 125 | * 126 | * @param mIsLayoutInflater 是否选中LayoutInflater 127 | * 128 | * @param mLayoutInflaterText 选中的布局的变量名 129 | * 130 | * @param mLayoutInflaterType mLayoutInflaterType 131 | * 132 | * @return String 133 | */ 134 | fun String?.createFieldByElement(element: Element, mIsLayoutInflater: Boolean, mLayoutInflaterText: String, mLayoutInflaterType: Int): String { 135 | val fromText = StringBuilder() 136 | this?.let { 137 | fromText.append("/** $it */\n") 138 | } 139 | with(element) { 140 | fromText.append("private $name $fieldName") 141 | } 142 | if (mIsLayoutInflater) { 143 | fromText.append(layoutInflaterType2Str(mLayoutInflaterText, mLayoutInflaterType)) 144 | } 145 | fromText.append(";") 146 | return fromText.toString() 147 | } 148 | 149 | /** 150 | * ButterKnife,在OnClick方法里面创建switch 151 | * 152 | * @param psiMethodParamsViewField View类型的变量名 153 | * 154 | * @param onClickValues 注解里面跟OnClickList的id集合 155 | * 156 | * @return String 157 | */ 158 | fun String.createSwitchByOnClickMethod(onClickValues: ArrayList): String { 159 | val psiSwitch = StringBuilder() 160 | psiSwitch.append("switch ($this.getId()) {\n") 161 | // add default statement 162 | psiSwitch.append("\tdefault:\n") 163 | psiSwitch.append("\t\tbreak;\n") 164 | onClickValues.forEach { 165 | psiSwitch.append("\tcase $it:\n") 166 | psiSwitch.append("\t\tbreak;\n") 167 | } 168 | psiSwitch.append("}") 169 | return psiSwitch.toString() 170 | } 171 | 172 | /** 173 | * FindViewById,创建ViewHolder 174 | * 175 | * @param viewHolderName viewHolderName 176 | * 177 | * @param viewHolderRootView viewHolderRootView 178 | * 179 | * @param mElements mElements 180 | * 181 | * @param mNeedCasts 是否需要强转 182 | * 183 | * @return String 184 | */ 185 | fun String.createFindViewByIdViewHolder(viewHolderRootView: String, 186 | mElements: ArrayList, 187 | mNeedCasts: Boolean): String { 188 | // ViewHolder 189 | val viewHolderText = StringBuilder() 190 | // ViewHolder的Constructor 191 | val viewHolderConstructorText = StringBuilder() 192 | // rootView 193 | viewHolderText.append("android.view.View $viewHolderRootView;\n") 194 | // Constructor 195 | viewHolderConstructorText.append("$this(android.view.View ) {\nthis.$viewHolderRootView = $viewHolderRootView;\n") 196 | // 添加field和findViewById 197 | mElements.forEach { 198 | with(it) { 199 | // 添加Field 200 | viewHolderText.append("$name $fieldName;\n") 201 | // 添加findViewById 202 | viewHolderConstructorText.append("this.$fieldName = ") 203 | if (mNeedCasts) { 204 | viewHolderConstructorText.append("($name) ") 205 | } 206 | viewHolderConstructorText.append("$viewHolderRootView.findViewById($fullID);\n") 207 | } 208 | } 209 | viewHolderConstructorText.append("}") 210 | // 添加Constructor到ViewHolder 211 | viewHolderText.append(viewHolderConstructorText.toString()) 212 | return viewHolderText.toString() 213 | } 214 | 215 | /** 216 | * ButterKnife,创建ViewHolder 217 | * 218 | * @param viewHolderName viewHolderName 219 | * 220 | * @param viewHolderRootView viewHolderRootView 221 | * 222 | * @param mElements mElements 223 | * 224 | * @return String 225 | */ 226 | fun String.createButterKnifeViewHolder(viewHolderRootView: String, mElements: ArrayList): String { 227 | // ViewHolder 228 | val viewHolderText = StringBuilder() 229 | // ViewHolder的Constructor 230 | val viewHolderConstructorText = StringBuilder() 231 | // 添加field和findViewById 232 | mElements.forEach { 233 | // 添加Field 234 | viewHolderText.append("@BindView(${it.fullID})\n${it.name} ${it.fieldName};\n") 235 | } 236 | // Constructor 237 | viewHolderConstructorText.append("$this(android.view.View $viewHolderRootView) {\nButterKnife.bind(this, $viewHolderRootView);\n}") 238 | // 添加Constructor到ViewHolder 239 | viewHolderText.append(viewHolderConstructorText.toString()) 240 | return viewHolderText.toString() 241 | } 242 | /** 243 | * 创建onCreate方法 244 | * 245 | * @param mSelectedText mSelectedText 246 | * 247 | * @param mIsButterKnife mIsButterKnife 248 | * 249 | * @return String 250 | */ 251 | fun String.createOnCreateMethod(mIsButterKnife: Boolean): String { 252 | val method = StringBuilder() 253 | method.append("@Override protected void onCreate(android.os.Bundle savedInstanceState) {\n") 254 | method.append("super.onCreate(savedInstanceState);\n") 255 | method.append("\t// TODO:OnCreate Method has been created, run ") 256 | method.append(if(!mIsButterKnife) "FindViewById" else "ButterKnife") 257 | method.append(" again to generate code\n\tsetContentView(R.layout.$this);\n") 258 | method.append(if(!mIsButterKnife) "\t\tinitView();\n" else "\t\tButterKnife.bind(this);\n") 259 | method.append("}") 260 | return method.toString() 261 | } -------------------------------------------------------------------------------- /src/actions/ButterKnifeAction.kt: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.actionSystem.PlatformDataKeys 6 | import com.intellij.openapi.ui.Messages 7 | import com.intellij.psi.search.FilenameIndex 8 | import com.intellij.psi.search.GlobalSearchScope 9 | import com.intellij.psi.util.PsiUtilBase 10 | import com.intellij.psi.xml.XmlFile 11 | import constant.Constant 12 | import entitys.Element 13 | import getIDsFromLayoutToList 14 | import getTargetClass 15 | import isExtendsActivityOrActivityCompat 16 | import isExtendsFragmentOrFragmentV4 17 | import showPopupBalloon 18 | import utils.CreateMethodCreator 19 | import views.ButterKnifeDialog 20 | import java.util.* 21 | 22 | /** 23 | * @author Jowan 24 | */ 25 | class ButterKnifeAction : AnAction() { 26 | private var mDialog: ButterKnifeDialog? = null 27 | 28 | override fun actionPerformed(e: AnActionEvent) { 29 | // 获取project 30 | val project = e.project ?: return 31 | // 获取选中内容 32 | val mEditor = e.getData(PlatformDataKeys.EDITOR) ?: return 33 | // 未选中布局内容,显示dialog 34 | val popupTime = 5 35 | val mSelectedText: String? = mEditor.selectionModel.selectedText ?: let { 36 | Messages.showInputDialog(project, 37 | Constant.Action.SELECTED_MESSAGE, 38 | Constant.Action.SELECTED_TITLE, 39 | Messages.getInformationIcon()) ?: let { 40 | mEditor.showPopupBalloon(Constant.Action.SELECTED_ERROR_NO_NAME, popupTime) 41 | return 42 | } 43 | } 44 | // 判断是否有onCreate/onCreateView方法 45 | val psiFile = PsiUtilBase.getPsiFileInEditor(mEditor, project) ?: return 46 | val psiClass = mEditor getTargetClass psiFile ?: let { 47 | mEditor.showPopupBalloon(Constant.Action.SELECTED_ERROR_NO_POINT, popupTime) 48 | return 49 | } 50 | // 判断是否有onCreate方法 51 | if (project isExtendsActivityOrActivityCompat psiClass && psiClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATE, false).isEmpty()) { 52 | // 写onCreate方法 53 | mSelectedText?.let { 54 | CreateMethodCreator(mEditor, psiFile, psiClass, Constant.CREATOR_COMMAND_NAME, 55 | it, Constant.CLASS_TYPE_BY_ACTIVITY, true).execute() 56 | } 57 | return 58 | } 59 | // 判断是否有onCreateView方法 60 | if (project isExtendsFragmentOrFragmentV4 psiClass && psiClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATEVIEW, false).isEmpty()) { 61 | mSelectedText?.let { 62 | CreateMethodCreator(mEditor, psiFile, psiClass, Constant.CREATOR_COMMAND_NAME, 63 | it, Constant.CLASS_TYPE_BY_FRAGMENT, true).execute() 64 | } 65 | return 66 | } 67 | // 获取布局文件,通过FilenameIndex.getFilesByName获取GlobalSearchScope.allScope(project)搜索整个项目 68 | val psiFiles = FilenameIndex.getFilesByName(project, 69 | mSelectedText + Constant.SELECTED_TEXT_SUFFIX, 70 | GlobalSearchScope.allScope(project)) 71 | if (psiFiles.isEmpty()) { 72 | mEditor.showPopupBalloon(Constant.Action.SELECTED_ERROR_NO_SELECTED, popupTime) 73 | return 74 | } 75 | val xmlFile = if (psiFiles.size > 1) { 76 | val psiFilePath = psiFile.parent?.toString()!! 77 | val psiFiles1 = psiFiles.filter { 78 | val modulePath = it.parent?.toString()!! 79 | modulePath.contains("\\src\\main\\res\\layout") && psiFilePath.substring(0, psiFilePath.indexOf("\\main\\")) == modulePath.substring(0, modulePath.indexOf("\\main\\")) 80 | } 81 | if (psiFiles1.isEmpty()) { 82 | mEditor.showPopupBalloon(Constant.Action.SELECTED_ERROR_NO_SELECTED, popupTime) 83 | return 84 | } else psiFiles1[0] as XmlFile 85 | } else { 86 | psiFiles[0] as XmlFile 87 | } 88 | // val xmlFile = psiFiles[0] as XmlFile 89 | val elements = ArrayList() 90 | getIDsFromLayoutToList(xmlFile, elements) 91 | // 将代码写入文件,不允许在主线程中进行实时的文件写入 92 | if (elements.size == 0) { 93 | mEditor.showPopupBalloon(Constant.Action.SELECTED_ERROR_NO_ID, popupTime) 94 | return 95 | } 96 | // 有的话就创建变量和ButterKnife代码 97 | if (mDialog != null && mDialog!!.isShowing) { 98 | mDialog?.cancelDialog() 99 | } 100 | mDialog = ButterKnifeDialog(mEditor = mEditor, 101 | mProject = project, 102 | mPsiFile = psiFile, 103 | mClass = psiClass, 104 | mElements = elements, 105 | mSelectedText = mSelectedText!!, 106 | mIsButterKnife = true, 107 | elementSize = elements.size) 108 | mDialog?.showDialog() 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/actions/FindViewByIdAction.kt: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.actionSystem.PlatformDataKeys 6 | import com.intellij.openapi.ui.Messages 7 | import com.intellij.psi.search.FilenameIndex 8 | import com.intellij.psi.search.GlobalSearchScope 9 | import com.intellij.psi.util.PsiUtilBase 10 | import com.intellij.psi.xml.XmlFile 11 | import constant.Constant 12 | import entitys.Element 13 | import getIDsFromLayoutToList 14 | import getTargetClass 15 | import isExtendsActivityOrActivityCompat 16 | import isExtendsFragmentOrFragmentV4 17 | import showPopupBalloon 18 | import utils.CreateMethodCreator 19 | import views.FindViewByIdDialog 20 | import java.util.* 21 | 22 | /** 23 | * @author Jowan 24 | */ 25 | class FindViewByIdAction : AnAction() { 26 | private var mDialog: FindViewByIdDialog? = null 27 | 28 | override fun actionPerformed(e: AnActionEvent) { 29 | // 获取project 30 | val project = e.project ?: return 31 | // 获取选中内容 32 | val mEditor = e.getData(PlatformDataKeys.EDITOR) ?: return 33 | // 未选中布局内容,显示dialog 34 | val popupTime = 5 35 | val mSelectedText: String? = mEditor.selectionModel.selectedText ?: let { 36 | Messages.showInputDialog(project, 37 | Constant.Action.SELECTED_MESSAGE, 38 | Constant.Action.SELECTED_TITLE, 39 | Messages.getInformationIcon()) ?: let { 40 | mEditor.showPopupBalloon(Constant.Action.SELECTED_ERROR_NO_NAME, popupTime) 41 | return 42 | } 43 | } 44 | // 判断是否有onCreate/onCreateView方法 45 | val psiFile = PsiUtilBase.getPsiFileInEditor(mEditor, project) ?: return 46 | val psiClass = mEditor getTargetClass psiFile ?: let { 47 | mEditor.showPopupBalloon(Constant.Action.SELECTED_ERROR_NO_POINT, popupTime) 48 | return 49 | } 50 | // 判断是否有onCreate方法 51 | if (project isExtendsActivityOrActivityCompat psiClass && psiClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATE, false).isEmpty()) { 52 | // 写onCreate方法 53 | mSelectedText?.let { 54 | CreateMethodCreator(mEditor, psiFile, psiClass, Constant.CREATOR_COMMAND_NAME, 55 | it, Constant.CLASS_TYPE_BY_ACTIVITY, false).execute() 56 | } 57 | return 58 | } 59 | // 判断是否有onCreateView方法 60 | if (project isExtendsFragmentOrFragmentV4 psiClass && psiClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATEVIEW, false).isEmpty()) { 61 | mSelectedText?.let { 62 | CreateMethodCreator(mEditor, psiFile, psiClass, Constant.CREATOR_COMMAND_NAME, 63 | it, Constant.CLASS_TYPE_BY_FRAGMENT, false).execute() 64 | } 65 | return 66 | } 67 | // 获取布局文件,通过FilenameIndex.getFilesByName获取GlobalSearchScope.allScope(project)搜索整个项目 68 | val psiFiles = FilenameIndex.getFilesByName(project, 69 | mSelectedText + Constant.SELECTED_TEXT_SUFFIX, 70 | GlobalSearchScope.allScope(project)) 71 | if (psiFiles.isEmpty()) { 72 | mEditor.showPopupBalloon(Constant.Action.SELECTED_ERROR_NO_SELECTED, popupTime) 73 | return 74 | } 75 | val xmlFile = if (psiFiles.size > 1) { 76 | val psiFilePath = psiFile.parent?.toString()!! 77 | val psiFiles1 = psiFiles.filter { 78 | val modulePath = it.parent?.toString()!! 79 | modulePath.contains("\\src\\main\\res\\layout") && psiFilePath.substring(0, psiFilePath.indexOf("\\main\\")) == modulePath.substring(0, modulePath.indexOf("\\main\\")) 80 | } 81 | if (psiFiles1.isEmpty()) { 82 | mEditor.showPopupBalloon(Constant.Action.SELECTED_ERROR_NO_SELECTED, popupTime) 83 | return 84 | } else psiFiles1[0] as XmlFile 85 | } else { 86 | psiFiles[0] as XmlFile 87 | } 88 | val elements = ArrayList() 89 | getIDsFromLayoutToList(xmlFile, elements) 90 | // 将代码写入文件,不允许在主线程中进行实时的文件写入 91 | if (elements.size == 0) { 92 | mEditor.showPopupBalloon(Constant.Action.SELECTED_ERROR_NO_ID, popupTime) 93 | return 94 | } 95 | // 有的话就创建变量和findViewById 96 | if (mDialog != null && mDialog!!.isShowing) { 97 | mDialog?.cancelDialog() 98 | } 99 | mDialog = FindViewByIdDialog(mEditor = mEditor, 100 | mProject = project, 101 | mPsiFile = psiFile, 102 | mClass = psiClass, 103 | mElements = elements, 104 | mSelectedText = mSelectedText!!, 105 | mIsButterKnife = false, 106 | elementSize = elements.size) 107 | mDialog?.showDialog() 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/constant/Constant.kt: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | /** 4 | * @author Jowan 5 | */ 6 | object Constant { 7 | const val GENERATEFINDVIEWBYID = "GenerateFindViewById" 8 | const val ACTION_FINDVIEWBYID = "FindViewById" 9 | const val ACTION_BUTTERKNIFE = "ButterKnife" 10 | const val SELECTED_TEXT_SUFFIX = ".xml" 11 | const val PSI_METHOD_BY_ONCREATE = "onCreate" 12 | const val PSI_METHOD_BY_ONCREATEVIEW = "onCreateView" 13 | const val CREATOR_COMMAND_NAME = "Generate Injections" 14 | const val CLASS_TYPE_BY_ACTIVITY = "activity" 15 | const val CLASS_TYPE_BY_FRAGMENT = "fragment" 16 | const val FIELD_ON_CLICK = "OnClick" 17 | const val FIELD_ONCLICK = "onClick" 18 | 19 | object Action { 20 | const val SELECTED_MESSAGE = "布局内容:(不需要输入R.layout.)" 21 | const val SELECTED_TITLE = "未选中布局内容,请输入layout文件名" 22 | const val SELECTED_ERROR_NO_NAME = "未输入layout文件名" 23 | const val SELECTED_ERROR_NO_SELECTED = "未找到选中的布局文件" 24 | const val SELECTED_ERROR_NO_ID = "未找到任何Id" 25 | const val SELECTED_ERROR_NO_POINT = "光标未在Class内" 26 | const val SELECTED_LAYOUT_FIELD_TEXT_NO_NULL = "LayoutInflater变量名不能为空" 27 | const val SELECTED_SUCCESS = "生成成功" 28 | } 29 | 30 | object Dialog { 31 | const val TITLE_BUTTERKNIFE = "ButterKnifeDialog" 32 | const val TITLE_FINDVIEWBYID = "FindViewByIdDialog" 33 | const val TABLE_FIELD_VIEW_WIDGET = "ViewWidget" 34 | const val TABLE_FIELD_VIEW_ID = "ViewId" 35 | const val TABLE_FIELD_VIEW_FILED = "ViewFiled" 36 | const val FIELD_LAYOUT_INFLATER = "LayoutInflater.from(context).inflater" 37 | const val FIELD_CHECK_ALL = "CheckAll" 38 | const val VIEWHOLDER_CHECK = "Create ViewHolder" 39 | const val NEED_CASTS = "Forced Casts(SupportLibrary uncheck the above version of 26)" 40 | const val FIELD_BUTTERKNIFE_BIND = "ButterKnife.bind()" 41 | const val BUTTON_CONFIRM = "确定" 42 | const val BUTTON_CANCEL = "取消" 43 | } 44 | 45 | object Ext { 46 | const val CREATOR_NO_ONCREATE_METHOD = "没有OnCreate方法,已创建OnCreate方法,请重新使用" 47 | const val CREATOR_NO_ONCREATEVIEW_METHOD = "没有OnCreateView方法,已创建OnCreateView方法,请重新使用" 48 | const val CREATOR_INITVIEW_NAME = "initView" 49 | const val CREATOR_VIEW_NAME = "view" 50 | const val CREATOR_UNBINDER_NAME = "unbinder" 51 | const val CREATOR_SETCONTENTVIEW_METHOD = "setContentView" 52 | const val CREATOR_ONDESTROYVIEW_METHOD = "onDestroyView" 53 | const val CREATOR_UNBINDER_FIELD = "unbinder.unbind();" 54 | const val FIELD_BUTTERKNIFE_BIND = "ButterKnife.bind" 55 | const val UNKNOWN_ERROR = "Unknown Error" 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/entitys/Element.kt: -------------------------------------------------------------------------------- 1 | package entitys 2 | 3 | import com.intellij.psi.xml.XmlTag 4 | import firstToUpperCase 5 | import org.apache.commons.lang.StringUtils 6 | import java.util.regex.Pattern 7 | 8 | /** 9 | * @author Jowan 10 | */ 11 | class Element 12 | /** 13 | * 构造函数 14 | * @param name View的名字 15 | * 16 | * @param id android:id属性 17 | * 18 | * @param clickable clickable 19 | * 20 | */ 21 | (name: String, id: String, clickable: Boolean, var xml: XmlTag) { 22 | // id 23 | var id: String? = null 24 | // 名字如TextView 25 | var name: String? = null 26 | // 命名1 aa_bb_cc; 2 aaBbCc 3 mAaBbCc 27 | var fieldNameType = 3 28 | /** 29 | * 获取变量名 30 | * @return 变量名 31 | */ 32 | // aaBbCc mAaBbCc 33 | var fieldName: String = "" 34 | get() { 35 | if (StringUtils.isEmpty(field)) { 36 | var fieldName: String = id!! 37 | val names = id!!.split("_".toRegex()).dropLastWhile(String::isEmpty).toTypedArray() 38 | when (fieldNameType) { 39 | 2 -> { 40 | val sb = StringBuilder() 41 | for (i in names.indices) { 42 | if (i == 0) { 43 | sb.append(names[i]) 44 | } else { 45 | sb.append(names[i].firstToUpperCase()) 46 | } 47 | } 48 | fieldName = sb.toString() 49 | } 50 | 3 -> { 51 | val sb = StringBuilder() 52 | for (i in names.indices) { 53 | if (i == 0) { 54 | sb.append("m") 55 | } 56 | sb.append(names[i].firstToUpperCase()) 57 | } 58 | fieldName = sb.toString() 59 | } 60 | 1 -> fieldName = id!! 61 | } 62 | this.fieldName = fieldName 63 | } 64 | return field 65 | } 66 | // 是否生成 67 | var isEnable = true 68 | // 是否有clickable 69 | var isClickEnable = false 70 | private set 71 | // 是否Clickable 72 | var isClickable = false 73 | 74 | init { 75 | // id 76 | val matcher = sIdPattern.matcher(id) 77 | if (matcher.find() && matcher.groupCount() > 1) { 78 | this.id = matcher.group(2) 79 | } 80 | 81 | if (this.id == null) { 82 | throw IllegalArgumentException("Invalid format of view id") 83 | } 84 | 85 | this.name = name 86 | 87 | val packages = name.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 88 | if (packages.size > 1) { 89 | // com.example.CustomView 90 | this.name = packages[packages.size - 1] 91 | } 92 | 93 | this.isClickEnable = true 94 | 95 | this.isClickable = clickable 96 | } 97 | 98 | fun setClickEnable() { 99 | this.isClickEnable = true 100 | } 101 | 102 | /** 103 | * 获取id,R.id.id 104 | * @return R.id.id 105 | */ 106 | val fullID: String 107 | get() { 108 | val rPrefix = "R.id." 109 | return rPrefix + id!! 110 | } 111 | 112 | companion object { 113 | 114 | // 判断id正则 115 | private val sIdPattern = Pattern.compile("@\\+?(android:)?id/([^$]+)$", Pattern.CASE_INSENSITIVE) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/entitys/IdBean.kt: -------------------------------------------------------------------------------- 1 | package entitys 2 | 3 | import java.awt.LayoutManager 4 | import java.awt.event.FocusEvent 5 | import java.awt.event.FocusListener 6 | import javax.swing.JCheckBox 7 | import javax.swing.JLabel 8 | import javax.swing.JPanel 9 | import javax.swing.JTextField 10 | import javax.swing.border.EmptyBorder 11 | 12 | /** 13 | * @author Jowan 14 | */ 15 | class IdBean 16 | /** 17 | * 构造方法 18 | * @param layout 布局 19 | * 20 | * @param emptyBorder border 21 | * 22 | * @param jCheckBox 是否生成+name 23 | * 24 | * @param jLabelId id 25 | * 26 | * @param jCheckBoxClick onClick 27 | * 28 | * @param jTextField 字段名 29 | * 30 | * @param enable 是否生成 31 | * 32 | * @param clickable clickable 33 | * 34 | * @param clickEnable 是否Enable 35 | */ 36 | (layout: LayoutManager, 37 | emptyBorder: EmptyBorder, 38 | private val mEnableCheckBox: JCheckBox, 39 | private val mIdJLabel: JLabel, 40 | private val mClickCheckBox: JCheckBox, 41 | private val mFieldJTextField: JTextField, 42 | enable: Boolean, 43 | clickable: Boolean, 44 | clickEnable: Boolean) : JPanel(layout) { 45 | 46 | /** 47 | * mEnableCheckBox接口 48 | */ 49 | private var mEnableListener: ((enableCheckBox: JCheckBox) -> Unit)? = null 50 | 51 | fun setEnableActionListener(enableListener: (enableCheckBox: JCheckBox) -> Unit) { 52 | mEnableListener = enableListener 53 | } 54 | 55 | /** 56 | * mFieldJTextField接口 57 | */ 58 | private var mFieldFocusListener: ((fieldJTextField: JTextField) -> Unit)? = null 59 | 60 | fun setFieldFocusListener(fieldFocusListener: (fieldJTextField: JTextField) -> Unit) { 61 | mFieldFocusListener = fieldFocusListener 62 | } 63 | 64 | /** 65 | * mClickCheckBox接口 66 | */ 67 | private var mClickListener: ((clickCheckBox: JCheckBox) -> Unit)? = null 68 | 69 | fun setClickActionListener(clickListener: (clickCheckBox: JCheckBox) -> Unit) { 70 | mClickListener = clickListener 71 | } 72 | 73 | init { 74 | initLayout(layout, emptyBorder) 75 | initComponent(enable, clickable, clickEnable) 76 | addComponent() 77 | } 78 | 79 | /** 80 | * addComponent 81 | */ 82 | private fun addComponent() { 83 | this.add(mEnableCheckBox) 84 | this.add(mIdJLabel) 85 | this.add(mClickCheckBox) 86 | this.add(mFieldJTextField) 87 | } 88 | 89 | /** 90 | * 设置Component 91 | 92 | * @param enable enable 93 | * 94 | * @param clickable clickable 95 | * 96 | * @param clickEnable clickEnable 97 | */ 98 | private fun initComponent(enable: Boolean, clickable: Boolean, clickEnable: Boolean) { 99 | mEnableCheckBox.isSelected = enable 100 | if (clickEnable) { 101 | mClickCheckBox.isSelected = clickable 102 | mClickCheckBox.isEnabled = enable 103 | } else { 104 | mClickCheckBox.isEnabled = false 105 | } 106 | 107 | mIdJLabel.isEnabled = enable 108 | mFieldJTextField.isEnabled = enable 109 | 110 | // 设置左对齐 111 | mEnableCheckBox.horizontalAlignment = JLabel.LEFT 112 | mIdJLabel.horizontalAlignment = JLabel.LEFT 113 | mFieldJTextField.horizontalAlignment = JTextField.LEFT 114 | // 监听 115 | mEnableCheckBox.addActionListener { 116 | mEnableListener?.let { 117 | mEnableListener?.invoke(mEnableCheckBox) 118 | mIdJLabel.isEnabled = mEnableCheckBox.isSelected 119 | if (clickEnable) mClickCheckBox.isEnabled = mEnableCheckBox.isSelected 120 | mFieldJTextField.isEnabled = mEnableCheckBox.isSelected 121 | } 122 | } 123 | // 监听 124 | mClickCheckBox.addActionListener { 125 | mClickListener?.invoke(mClickCheckBox) 126 | } 127 | // 监听 128 | mFieldJTextField.addFocusListener(object : FocusListener { 129 | override fun focusGained(e: FocusEvent) { 130 | mFieldFocusListener?.invoke(mFieldJTextField) 131 | } 132 | 133 | override fun focusLost(e: FocusEvent) { 134 | mFieldFocusListener?.invoke(mFieldJTextField) 135 | } 136 | }) 137 | } 138 | 139 | /** 140 | * 设置布局相关 141 | 142 | * @param layout layout 143 | * 144 | * @param emptyBorder emptyBorder 145 | */ 146 | private fun initLayout(layout: LayoutManager, emptyBorder: EmptyBorder) { 147 | // 设置布局内容 148 | this.layout = layout 149 | // 设置border 150 | this.border = emptyBorder 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/utils/CreateMethodCreator.kt: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import com.intellij.codeInsight.actions.ReformatCodeProcessor 4 | import com.intellij.openapi.command.WriteCommandAction.Simple 5 | import com.intellij.openapi.editor.Editor 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.psi.JavaPsiFacade 8 | import com.intellij.psi.PsiClass 9 | import com.intellij.psi.PsiElementFactory 10 | import com.intellij.psi.PsiFile 11 | import com.intellij.psi.codeStyle.JavaCodeStyleManager 12 | import constant.Constant 13 | import createFragmentInitViewMethod 14 | import createFragmentOnCreateMethod 15 | import createInitViewMethod 16 | import createOnCreateMethod 17 | import createOnCreateViewMethod 18 | import createOnDestroyViewMethod 19 | import isExtendsActivityOrActivityCompat 20 | import isExtendsFragmentOrFragmentV4 21 | import showPopupBalloon 22 | 23 | /** 24 | * 生成Activity的onCreate方法和Fragment的onCreateView方法 25 | * @author Jowan 26 | */ 27 | class CreateMethodCreator(private val mEditor: Editor, 28 | private val mFile: PsiFile, 29 | private val mClass: PsiClass, 30 | command: String, 31 | private val mSelectedText: String, // activity/fragment 32 | private val mType: String, 33 | private val mIsButterKnife: Boolean) : 34 | Simple(mClass.project, command) { 35 | private val mProject: Project = mClass.project 36 | private val mFactory: PsiElementFactory 37 | 38 | init { 39 | // 获取Factory 40 | mFactory = JavaPsiFacade.getElementFactory(mProject) 41 | } 42 | 43 | @Throws(Throwable::class) 44 | override fun run() { 45 | try { 46 | createMethod() 47 | } catch (e: Exception) { 48 | mEditor.showPopupBalloon(e.message, 10) 49 | return 50 | } 51 | 52 | // 重写class 53 | val styleManager = JavaCodeStyleManager.getInstance(mProject) 54 | styleManager.optimizeImports(mFile) 55 | styleManager.shortenClassReferences(mClass) 56 | ReformatCodeProcessor(mProject, mClass.containingFile, null, false).runWithoutProgress() 57 | var actionName = Constant.ACTION_FINDVIEWBYID 58 | if (mIsButterKnife) { 59 | actionName = Constant.ACTION_BUTTERKNIFE 60 | } 61 | if (mType == Constant.CLASS_TYPE_BY_ACTIVITY) { 62 | mEditor.showPopupBalloon(Constant.Ext.CREATOR_NO_ONCREATE_METHOD + actionName, 10) 63 | } else if (mType == Constant.CLASS_TYPE_BY_FRAGMENT) { 64 | mEditor.showPopupBalloon(Constant.Ext.CREATOR_NO_ONCREATEVIEW_METHOD + actionName, 10) 65 | } 66 | } 67 | 68 | /** 69 | * 设置Activity的onCreate方法和Fragment的onCreateView方法 70 | */ 71 | private fun createMethod() { 72 | if (mProject isExtendsActivityOrActivityCompat mClass) { 73 | // 判断是否有onCreate方法 74 | if (mClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATE, false).isEmpty()) { 75 | // 添加 76 | mClass.add(mFactory.createMethodFromText(mSelectedText.createOnCreateMethod(mIsButterKnife), mClass)) 77 | if (!mIsButterKnife && mClass.findMethodsByName(Constant.Ext.CREATOR_INITVIEW_NAME, false).isEmpty()) { 78 | mClass.add(mFactory.createMethodFromText(createInitViewMethod(), mClass)) 79 | } 80 | } 81 | 82 | } else if (mProject isExtendsFragmentOrFragmentV4 mClass) { 83 | var isViewExist = false 84 | var isUnbinderExist = false 85 | for (psiField in mClass.fields) { 86 | if (psiField.name != null && psiField.name == Constant.Ext.CREATOR_VIEW_NAME) { 87 | isViewExist = true 88 | } 89 | if (psiField.name != null && psiField.name == Constant.Ext.CREATOR_UNBINDER_NAME) { 90 | isUnbinderExist = true 91 | } 92 | } 93 | if (!isViewExist) { 94 | mClass.add(mFactory.createFieldFromText("private View view;", mClass)) 95 | } 96 | if (mIsButterKnife && !isUnbinderExist) { 97 | mClass.add(mFactory.createFieldFromText("private Unbinder unbinder;", mClass)) 98 | } 99 | // 判断是否有onCreate方法 100 | if (mClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATE, false).isEmpty()) { 101 | mClass.add(mFactory.createMethodFromText(mSelectedText.createFragmentOnCreateMethod(), mClass)) 102 | } else { 103 | // onCreate是否存在View.inflate方法 104 | var hasInflateStatement = false 105 | val onCreate = mClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATE, false)[0] 106 | if (onCreate.body != null) { 107 | for (psiStatement in onCreate.body!!.statements) { 108 | if (psiStatement.text.contains("View.inflate(getActivity(), R.layout.$mSelectedText, null);")) { 109 | hasInflateStatement = true 110 | break 111 | } else { 112 | hasInflateStatement = false 113 | } 114 | } 115 | if (!hasInflateStatement) { 116 | onCreate.body?.add(mFactory.createStatementFromText("view = View.inflate(getActivity(), R.layout.$mSelectedText, null);", mClass)) 117 | } 118 | } 119 | } 120 | // 判断是否有onCreateView方法 121 | if (mClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATEVIEW, false).isEmpty()) { 122 | // 添加 123 | mClass.add(mFactory.createMethodFromText(createOnCreateViewMethod(mIsButterKnife), mClass)) 124 | if (!mIsButterKnife && mClass.findMethodsByName(Constant.Ext.CREATOR_INITVIEW_NAME, false).isEmpty()) { 125 | mClass.add(mFactory.createMethodFromText(createFragmentInitViewMethod(), mClass)) 126 | } 127 | } 128 | // ButterKnife判断是否有onDestroyView方法 129 | if (mIsButterKnife) { 130 | if (mClass.findMethodsByName(Constant.Ext.CREATOR_ONDESTROYVIEW_METHOD, false).isEmpty()) { 131 | mClass.add(mFactory.createMethodFromText(createOnDestroyViewMethod(), mClass)) 132 | } else { 133 | // onDestroyView是否存在unbinder.unbind(); 134 | var hasUnbinderStatement = false 135 | val onCreate = mClass.findMethodsByName(Constant.Ext.CREATOR_ONDESTROYVIEW_METHOD, false)[0] 136 | if (onCreate.body != null) { 137 | for (psiStatement in onCreate.body!!.statements) { 138 | if (psiStatement.text.contains(Constant.Ext.CREATOR_UNBINDER_FIELD)) { 139 | hasUnbinderStatement = true 140 | break 141 | } else { 142 | hasUnbinderStatement = false 143 | } 144 | } 145 | if (!hasUnbinderStatement) { 146 | onCreate.body?.add(mFactory.createStatementFromText(Constant.Ext.CREATOR_UNBINDER_FIELD, mClass)) 147 | } 148 | } 149 | } 150 | } 151 | 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/utils/GenerateCreator.kt: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import com.intellij.codeInsight.actions.ReformatCodeProcessor 4 | import com.intellij.openapi.command.WriteCommandAction.Simple 5 | import com.intellij.openapi.editor.Editor 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.psi.* 8 | import com.intellij.psi.codeStyle.JavaCodeStyleManager 9 | import constant.Constant 10 | import createButterKnifeOnClickMethodAndSwitch 11 | import createButterKnifeViewHolder 12 | import createButterKnifeViewMethod 13 | import createFieldByElement 14 | import createFieldText 15 | import createFieldsByInitViewMethod 16 | import createFindViewByIdOnClickMethodAndSwitch 17 | import createFindViewByIdViewHolder 18 | import createOnClickAnnotation 19 | import createOnClickValue 20 | import createOnCreateMethod 21 | import createOnCreateViewMethod 22 | import createOnDestroyViewMethod 23 | import createSwitchByOnClickMethod 24 | import entitys.Element 25 | import getOnClickListById 26 | import getPsiMethodByButterKnifeOnClick 27 | import getPsiMethodByButterKnifeOnClickValue 28 | import getPsiMethodByName 29 | import getPsiMethodParamsViewField 30 | import isExtendsActivityOrActivityCompat 31 | import isExtendsFragmentOrFragmentV4 32 | import isImplementsOnClickListener 33 | import layoutInflaterType2Str 34 | import showPopupBalloon 35 | import views.GenerateDialog 36 | import java.util.* 37 | 38 | 39 | /** 40 | * 生成代码 41 | * @author Jowan 42 | */ 43 | class GenerateCreator( 44 | private val mDialog: GenerateDialog, 45 | private val mEditor: Editor, 46 | private val mFile: PsiFile, 47 | private val mClass: PsiClass, 48 | private val mProject: Project, 49 | private val mElements: ArrayList, 50 | private val mFactory: PsiElementFactory, 51 | private val mSelectedText: String, 52 | private val mIsLayoutInflater: Boolean, 53 | private val mLayoutInflaterText: String, 54 | private val mLayoutInflaterType: Int, 55 | private val mIsButterKnife: Boolean, 56 | private val mIsBind: Boolean, 57 | private val mViewHolder: Boolean, 58 | command: String, 59 | private val mNeedCasts: Boolean 60 | ) : Simple(mClass.project, command) { 61 | 62 | private val mOnClickList = ArrayList() 63 | 64 | init { 65 | if (mIsButterKnife) { 66 | mElements.filterTo(mOnClickList) { 67 | it.isEnable && it.isClickable 68 | } 69 | } 70 | } 71 | 72 | @Throws(Throwable::class) 73 | override fun run() { 74 | try { 75 | if (mViewHolder) { 76 | generateViewHolder(mIsButterKnife) 77 | } else { 78 | if (mIsButterKnife) { 79 | generateButterKnife() 80 | } else { 81 | generateFindViewById() 82 | } 83 | } 84 | } catch (e: Exception) { 85 | e.printStackTrace() 86 | // 异常打印 87 | mDialog.cancelDialog() 88 | mEditor.showPopupBalloon(e.message, 10) 89 | return 90 | } 91 | 92 | // 重写class 93 | val styleManager = JavaCodeStyleManager.getInstance(mProject) 94 | styleManager.optimizeImports(mFile) 95 | styleManager.shortenClassReferences(mClass) 96 | ReformatCodeProcessor(mProject, mClass.containingFile, null, false).runWithoutProgress() 97 | mEditor.showPopupBalloon(Constant.Action.SELECTED_SUCCESS, 5) 98 | } 99 | 100 | /** 101 | * 创建ViewHolder 102 | 103 | * @param isButterKnife true FindViewById 104 | * false ButterKnife 105 | */ 106 | private fun generateViewHolder(isButterKnife: Boolean) { 107 | val viewHolderName = "ViewHolder" 108 | val viewHolderRootView = "view" 109 | val str: String 110 | str = if (isButterKnife) { 111 | viewHolderName.createButterKnifeViewHolder(viewHolderRootView, mElements) 112 | } else { 113 | viewHolderName.createFindViewByIdViewHolder(viewHolderRootView, mElements, mNeedCasts) 114 | } 115 | // 创建ViewHolder类 116 | val viewHolder = mFactory.createClassFromText(str, mClass) 117 | // 设置名字 118 | viewHolder.setName(viewHolderName) 119 | // 添加ViewHolder类到类中 120 | mClass.add(viewHolder) 121 | // 添加static 122 | mClass.addBefore(mFactory.createKeyword("static"), mClass.findInnerClassByName(viewHolderName, true)) 123 | } 124 | 125 | /** 126 | * 设置变量的值FindViewById,Activity和Fragment 127 | */ 128 | private fun generateFindViewById() { 129 | if (mProject isExtendsActivityOrActivityCompat mClass) { 130 | // 判断是否有onCreate方法 131 | if (mClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATE, false).isEmpty()) { 132 | // 添加 133 | mClass.add(mFactory.createMethodFromText(mSelectedText.createOnCreateMethod(false), mClass)) 134 | return 135 | } 136 | generateFindViewByIdFields() 137 | // 获取setContentView 138 | var setContentViewStatement: PsiStatement? = null 139 | // onCreate是否存在initView方法 140 | var hasInitViewStatement = false 141 | // 获取onCreate方法对象 142 | val onCreate = mClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATE, false)[0] 143 | if (onCreate.body != null) { 144 | for (psiStatement in onCreate.body!!.statements) { 145 | // 查找setContentView 146 | if (psiStatement.firstChild is PsiMethodCallExpression) { 147 | val methodExpression = (psiStatement.firstChild as PsiMethodCallExpression).methodExpression 148 | if (methodExpression.text == Constant.Ext.CREATOR_SETCONTENTVIEW_METHOD) { 149 | setContentViewStatement = psiStatement 150 | } else if (methodExpression.text == Constant.Ext.CREATOR_INITVIEW_NAME) { 151 | hasInitViewStatement = true 152 | } 153 | } 154 | } 155 | if (setContentViewStatement == null) { 156 | onCreate.body?.add(mFactory.createStatementFromText("setContentView(R.layout.$mSelectedText);", mClass)) 157 | } 158 | 159 | if (!hasInitViewStatement) { 160 | // 将initView()写到setContentView()后面 161 | setContentViewStatement?.let { 162 | onCreate.body?.addAfter(mFactory.createStatementFromText("initView();", mClass), it) 163 | } ?: let { 164 | onCreate.body?.add(mFactory.createStatementFromText("initView();", mClass)) 165 | } 166 | } 167 | } 168 | 169 | generateFindViewByIdLayoutCode(null, "getApplicationContext()") 170 | 171 | return 172 | } 173 | if (mProject isExtendsFragmentOrFragmentV4 mClass) { 174 | var isViewExist = false 175 | for (psiField in mClass.fields) { 176 | if (psiField.text != null && psiField.text == "private View view;") { 177 | isViewExist = true 178 | break 179 | } else { 180 | isViewExist = false 181 | } 182 | } 183 | if (!isViewExist) { 184 | mClass.add(mFactory.createFieldFromText("private View view;", mClass)) 185 | } 186 | // 判断是否有onCreateView方法 187 | if (mClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATEVIEW, false).isEmpty()) { 188 | // 添加 189 | mClass.add(mFactory.createMethodFromText(createOnCreateViewMethod(false), mClass)) 190 | return 191 | } 192 | generateFindViewByIdFields() 193 | // 查找onCreateView 194 | var returnStatement: PsiReturnStatement? = null 195 | // view 196 | var returnValue: String? = null 197 | // onCreateView是否存在initView方法 198 | var hasInitViewStatement = false 199 | 200 | val onCreate = mClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATEVIEW, false)[0] 201 | if (onCreate.body != null) { 202 | for (psiStatement in onCreate.body!!.statements) { 203 | if (psiStatement is PsiReturnStatement) { 204 | // 获取view的值 205 | returnStatement = psiStatement 206 | if (returnStatement.returnValue != null) { 207 | returnValue = returnStatement.returnValue?.text 208 | } 209 | } else if (psiStatement.firstChild is PsiMethodCallExpression) { 210 | val methodExpression = (psiStatement.firstChild as PsiMethodCallExpression).methodExpression 211 | if (methodExpression.text == Constant.Ext.CREATOR_INITVIEW_NAME) { 212 | hasInitViewStatement = true 213 | } 214 | } 215 | } 216 | 217 | if (!hasInitViewStatement && returnStatement != null && returnValue != null) { 218 | onCreate.body?.addBefore(mFactory.createStatementFromText("initView($returnValue);", mClass), returnStatement) 219 | } 220 | } 221 | generateFindViewByIdLayoutCode(returnValue, "getActivity()") 222 | } 223 | } 224 | 225 | /** 226 | * 创建变量 227 | */ 228 | private fun generateFindViewByIdFields() { 229 | if (mIsLayoutInflater) { 230 | val inflater = "private View $mLayoutInflaterText;" 231 | // 已存在的变量就不创建 232 | var duplicateField = false 233 | for (field in mClass.fields) { 234 | val name = field.name 235 | if (name != null && name == mLayoutInflaterText) { 236 | duplicateField = true; 237 | break; 238 | } 239 | } 240 | if (!duplicateField) { 241 | mClass.add(mFactory.createFieldFromText(inflater, mClass)) 242 | } 243 | } 244 | for (element in mElements) { 245 | // 已存在的变量就不创建 246 | val fields = mClass.fields 247 | var duplicateField = false 248 | for (field in fields) { 249 | if (!mIsLayoutInflater) { 250 | if (field.name != null && field.name == element.fieldName) { 251 | duplicateField = true 252 | break 253 | } 254 | } else { 255 | val layoutField = element.fieldName + layoutInflaterType2Str(mLayoutInflaterText, mLayoutInflaterType) 256 | if (field.name != null && field.name == layoutField) { 257 | duplicateField = true 258 | break 259 | } 260 | } 261 | } 262 | // 已存在跳出 263 | if (duplicateField) { 264 | continue 265 | } 266 | // 设置变量名,获取text里面的内容 267 | if (element.isEnable) { 268 | // 添加到class 269 | mClass.add(mFactory.createFieldFromText(mProject.createFieldText(element, mFile).createFieldByElement( 270 | element, mIsLayoutInflater, mLayoutInflaterText, mLayoutInflaterType), mClass)) 271 | } 272 | } 273 | } 274 | 275 | /** 276 | * 写initView方法 277 | 278 | * @param findPre Fragment的话要view.findViewById 279 | * 280 | * @param context context 281 | */ 282 | private fun generateFindViewByIdLayoutCode(findPre: String?, context: String) { 283 | // 判断是否已有initView方法 284 | val initViewMethods = mClass.findMethodsByName(Constant.Ext.CREATOR_INITVIEW_NAME, false) 285 | // 有initView方法 286 | if (initViewMethods.isNotEmpty() && initViewMethods[0].body != null) { 287 | val initViewMethodBody = initViewMethods[0].body 288 | // 获取initView方法里面的每条内容 289 | val statements = initViewMethodBody!!.statements 290 | if (mIsLayoutInflater) { 291 | // 添加LayoutInflater.from(this).inflate(R.layout.activity_main, null); 292 | val layoutInflater = "$mLayoutInflaterText = LayoutInflater.from($context).inflate(R.layout.$mSelectedText, null);" 293 | // 判断是否存在 294 | var isExist = false 295 | for (statement in statements) { 296 | if (statement.text == layoutInflater) { 297 | isExist = true 298 | break 299 | } else { 300 | isExist = false 301 | } 302 | } 303 | // 不存在才添加 304 | if (!isExist) { 305 | initViewMethodBody.add(mFactory.createStatementFromText(layoutInflater, initViewMethods[0])) 306 | } 307 | } 308 | for (element in mElements) { 309 | if (element.isEnable) { 310 | // 判断是否已存在findViewById 311 | var isFdExist = false 312 | var pre = findPre?.let { it + "." } ?: "" 313 | var inflater = "" 314 | if (mIsLayoutInflater) { 315 | inflater = layoutInflaterType2Str(mLayoutInflaterText, mLayoutInflaterType) 316 | pre = mLayoutInflaterText + "." 317 | } 318 | val casts = if(mNeedCasts) " (${element.name}) " else "" 319 | val findViewById = "${element.fieldName}$inflater =$casts${pre}findViewById(${element.fullID});" 320 | for (statement in statements) { 321 | if (statement.text == findViewById) { 322 | isFdExist = true 323 | break 324 | } else { 325 | isFdExist = false 326 | } 327 | } 328 | // 不存在就添加 329 | if (!isFdExist) { 330 | initViewMethodBody.add(mFactory.createStatementFromText(findViewById, initViewMethods[0])) 331 | } 332 | if (element.isClickEnable) { 333 | // 判断是否已存在setOnClickListener 334 | var isClickExist = false 335 | val setOnClickListener = "${element.fieldName}$inflater.setOnClickListener(this);" 336 | for (statement in statements) { 337 | if (statement.text == setOnClickListener) { 338 | isClickExist = true 339 | break 340 | } else { 341 | isClickExist = false 342 | } 343 | } 344 | if (!isClickExist && element.isClickable) { 345 | initViewMethodBody.add(mFactory.createStatementFromText(setOnClickListener, initViewMethods[0])) 346 | } 347 | } 348 | } 349 | } 350 | } else { 351 | mClass.add(mFactory.createMethodFromText( 352 | createFieldsByInitViewMethod(findPre, mIsLayoutInflater, mLayoutInflaterText, context, mSelectedText, mElements, mLayoutInflaterType, mNeedCasts), mClass)) 353 | } 354 | getFindViewByIdOnClickList() 355 | if (mOnClickList.size != 0) { 356 | generateFindViewByIdOnClickListenerCode() 357 | } 358 | } 359 | 360 | /** 361 | * 添加实现OnClickListener接口 362 | */ 363 | private fun generateFindViewByIdOnClickListenerCode() { 364 | // 获取已实现的接口 365 | val implementsList = mClass.implementsList 366 | var isImplOnClick = false 367 | if (implementsList != null) { 368 | // 获取列表 369 | val referenceElements = implementsList.referenceElements 370 | // 是否实现了OnClickListener接口 371 | isImplOnClick = isImplementsOnClickListener(referenceElements) 372 | } 373 | // 未实现添加OnClickListener接口 374 | if (!isImplOnClick) { 375 | val referenceElementByFQClassName = mFactory.createReferenceElementByFQClassName("android.view.View.OnClickListener", mClass.resolveScope) 376 | // 添加的PsiReferenceList 377 | implementsList?.add(referenceElementByFQClassName) 378 | } 379 | generateFindViewByIdClickCode() 380 | } 381 | 382 | /** 383 | * 获取有OnClick属性的Element 384 | */ 385 | private fun getFindViewByIdOnClickList() { 386 | mElements.filterTo(mOnClickList) { it.isEnable && it.isClickEnable && it.isClickable } 387 | } 388 | 389 | /** 390 | * 写onClick方法 391 | */ 392 | private fun generateFindViewByIdClickCode() { 393 | // 判断是否已有onClick方法 394 | val onClickMethods = mClass.findMethodsByName(Constant.FIELD_ONCLICK, false) 395 | // 判断是否包含@OnClick注解 396 | val butterKnifeOnClickMethod = mClass.getPsiMethodByButterKnifeOnClick() 397 | // 已有onClick方法 398 | if (butterKnifeOnClickMethod == null && onClickMethods.isNotEmpty() && onClickMethods[0].body != null) { 399 | val onClickMethodBody = onClickMethods[0].body 400 | // 获取switch 401 | for (psiElement in onClickMethodBody!!.children) { 402 | if (psiElement is PsiSwitchStatement) { 403 | // 获取switch的内容 404 | val psiSwitchStatementBody = psiElement.body 405 | if (psiSwitchStatementBody != null) { 406 | for (element in mOnClickList) { 407 | val cass = "case " + element.fullID + ":" 408 | // 判断是否存在 409 | var isExist = false 410 | for (statement in psiSwitchStatementBody.statements) { 411 | if (statement.text.replace("\n".toRegex(), "").replace("break;".toRegex(), "") == cass) { 412 | isExist = true 413 | break 414 | } else { 415 | isExist = false 416 | } 417 | } 418 | // 不存在就添加 419 | if (!isExist) { 420 | psiSwitchStatementBody.add(mFactory.createStatementFromText(cass, psiSwitchStatementBody)) 421 | psiSwitchStatementBody.add(mFactory.createStatementFromText("break;", psiSwitchStatementBody)) 422 | } 423 | } 424 | } 425 | } 426 | } 427 | return 428 | } 429 | if (mOnClickList.size != 0) { 430 | mClass.add(mFactory.createMethodFromText(mOnClickList.createFindViewByIdOnClickMethodAndSwitch(), mClass)) 431 | } 432 | } 433 | 434 | /** 435 | * 设置变量,Activity和Fragment 436 | */ 437 | private fun generateButterKnife() { 438 | if (mProject isExtendsActivityOrActivityCompat mClass) { 439 | // 判断是否有onCreate方法 440 | if (mClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATE, false).isEmpty()) { 441 | // 添加 442 | mClass.add(mFactory.createMethodFromText(mSelectedText.createOnCreateMethod(true), mClass)) 443 | } else { 444 | generateButterKnifeFields(Constant.CLASS_TYPE_BY_ACTIVITY) 445 | if (mIsLayoutInflater) { 446 | generateButterKnifeViewMethod("getApplicationContext()") 447 | } 448 | // 获取setContentView 449 | var setContentViewStatement: PsiStatement? = null 450 | // onCreate是否ButterKnife.bind(this); 451 | var hasButterKnifeBindStatement = false 452 | 453 | val onCreate = mClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATE, false)[0] 454 | if (onCreate.body != null) { 455 | for (psiStatement in onCreate.body!!.statements) { 456 | // 查找setContentView 457 | if (psiStatement.firstChild is PsiMethodCallExpression) { 458 | val methodExpression = (psiStatement.firstChild as PsiMethodCallExpression).methodExpression 459 | if (methodExpression.text == Constant.Ext.CREATOR_SETCONTENTVIEW_METHOD) { 460 | setContentViewStatement = psiStatement 461 | } else if (methodExpression.text.contains(Constant.Ext.FIELD_BUTTERKNIFE_BIND)) { 462 | hasButterKnifeBindStatement = true 463 | } 464 | } 465 | } 466 | if (setContentViewStatement == null) { 467 | onCreate.body?.add(mFactory.createStatementFromText("setContentView(R.layout.$mSelectedText);", mClass)) 468 | } 469 | 470 | if (!hasButterKnifeBindStatement) { 471 | // 将ButterKnife.bind(this);写到setContentView()后面 472 | if (setContentViewStatement != null) { 473 | onCreate.body?.addAfter(mFactory.createStatementFromText("ButterKnife.bind(this);", mClass), setContentViewStatement) 474 | } else { 475 | onCreate.body?.add(mFactory.createStatementFromText("ButterKnife.bind(this);", mClass)) 476 | } 477 | } 478 | } 479 | } 480 | if (mOnClickList.size > 0 && !mIsLayoutInflater) { 481 | generateButterKnifeClickCode() 482 | } 483 | return 484 | } 485 | if (mProject isExtendsFragmentOrFragmentV4 mClass) { 486 | // 判断是否有onCreateView方法 487 | if (mClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATEVIEW, false).isEmpty()) { 488 | // 添加 489 | mClass.add(mFactory.createMethodFromText(createOnCreateViewMethod(true), mClass)) 490 | 491 | } else { 492 | generateButterKnifeFields(Constant.CLASS_TYPE_BY_FRAGMENT) 493 | if (mIsLayoutInflater) { 494 | generateButterKnifeViewMethod("getActivity()") 495 | } 496 | // 查找onCreateView 497 | var returnStatement: PsiReturnStatement? = null 498 | // view 499 | var returnValue: String? = null 500 | 501 | val onCreateView = mClass.findMethodsByName(Constant.PSI_METHOD_BY_ONCREATEVIEW, false)[0] 502 | if (onCreateView.body != null) { 503 | for (psiStatement in onCreateView.body!!.statements) { 504 | if (psiStatement is PsiReturnStatement) { 505 | // 获取view的值 506 | returnStatement = psiStatement 507 | if (returnStatement.returnValue != null) { 508 | returnValue = returnStatement.returnValue?.text 509 | } 510 | } 511 | } 512 | 513 | if (returnStatement != null && returnValue != null) { 514 | var isBindExist = false 515 | // 判断是否已存在unbinder = ButterKnife.bind 516 | val bind = "unbinder = ButterKnife.bind(this, $returnValue);" 517 | // 获取onCreateView方法里面的PsiStatement 518 | for (psiStatement in onCreateView.body!!.statements) { 519 | if (psiStatement.text.contains(bind)) { 520 | isBindExist = true 521 | break 522 | } else { 523 | isBindExist = false 524 | } 525 | } 526 | if (!isBindExist && mIsBind) { 527 | //将ButterKnife.bind(this);写到return view前面 528 | onCreateView.body?.addBefore(mFactory.createStatementFromText(bind, mClass), returnStatement) 529 | } 530 | } 531 | } 532 | } 533 | if (mOnClickList.size > 0 && !mIsLayoutInflater) { 534 | generateButterKnifeClickCode() 535 | } 536 | if (mIsBind) { 537 | generateButterKnifeLayoutCode() 538 | } 539 | } 540 | } 541 | 542 | /** 543 | * 创建变量 544 | * 545 | * @param type type 546 | */ 547 | private fun generateButterKnifeFields(type: String) { 548 | if (type == Constant.CLASS_TYPE_BY_FRAGMENT) { 549 | // 判断View是否存在 550 | var isViewExist = false 551 | // 判断Unbinder是否存在 552 | var isUnBinderExist = false 553 | for (field in mClass.fields) { 554 | if (field.name != null && field.name == Constant.Ext.CREATOR_VIEW_NAME) { 555 | isViewExist = true 556 | } 557 | if (field.name != null && field.name == Constant.Ext.CREATOR_UNBINDER_NAME) { 558 | isUnBinderExist = true 559 | } 560 | } 561 | if (!isViewExist) { 562 | mClass.add(mFactory.createFieldFromText("private View view;", mClass)) 563 | } 564 | if (!isUnBinderExist && mIsBind) { 565 | mClass.add(mFactory.createFieldFromText("private Unbinder unbinder;", mClass)) 566 | } 567 | } 568 | if (mIsLayoutInflater) { 569 | val inflater = "private View $mLayoutInflaterText;" 570 | // 已存在的变量就不创建 571 | var duplicateField = false 572 | for (field in mClass.fields) { 573 | if (field.name != null && field.name == mLayoutInflaterText) { 574 | duplicateField = true 575 | break 576 | } 577 | } 578 | if (!duplicateField) { 579 | mClass.add(mFactory.createFieldFromText(inflater, mClass)) 580 | } 581 | } 582 | mElements.filter { 583 | it.isEnable 584 | }.forEach { element -> 585 | if (mIsLayoutInflater) { 586 | val layoutField = element.fieldName + layoutInflaterType2Str(mLayoutInflaterText, mLayoutInflaterType) 587 | var duplicateField = false 588 | for (field in mClass.fields) { 589 | if (field.name != null && field.name == layoutField) { 590 | duplicateField = true 591 | } 592 | } 593 | // 已存在跳出 594 | if (!duplicateField) { 595 | // 添加到class 596 | mClass.add(mFactory.createFieldFromText(mProject.createFieldText(element, mFile).createFieldByElement( 597 | element, true, mLayoutInflaterText, mLayoutInflaterType), mClass)) 598 | } 599 | } else { 600 | // 已存在的变量就不创建 601 | var isFieldExist = false 602 | var isAnnotationExist = false 603 | for (field in mClass.fields) { 604 | if (field.name != null) { 605 | isAnnotationExist = field.text.contains("@BindView(${element.fullID})") 606 | isFieldExist = field.text.contains("${element.name} ${element.fieldName};") 607 | && !(field.text.contains("private") || field.text.contains("static")) 608 | if (isAnnotationExist || isFieldExist) { 609 | break 610 | } 611 | } 612 | } 613 | // @BindView(R.id.text) TextView mText; 614 | // 如果两个都存在则跳出 615 | if (isAnnotationExist && isFieldExist) { 616 | return@forEach 617 | } 618 | if (!isFieldExist) { 619 | // 如果只存在TextView mText 620 | val fromText = element.name + " " + element.fieldName + ";" 621 | // 添加到class 622 | mClass.add(mFactory.createFieldFromText(fromText, mClass)) 623 | } 624 | 625 | for (field in mClass.fields) { 626 | if (field.name != null && field.text.contains("${element.name} ${element.fieldName};") 627 | && !(field.text.contains("private") || field.text.contains("static"))) { 628 | val annotationText = "@BindView(" + element.fullID + ")" 629 | // 添加注解到field 630 | mClass.addBefore(mFactory.createAnnotationFromText(annotationText, mClass), field) 631 | break 632 | } 633 | } 634 | } 635 | } 636 | } 637 | 638 | /** 639 | * 写onClick方法 640 | */ 641 | private fun generateButterKnifeClickCode() { 642 | // 判断是否包含@OnClick注解 643 | val butterKnifeOnClickMethod = mClass.getPsiMethodByButterKnifeOnClick() 644 | // 有@OnClick注解 645 | if (butterKnifeOnClickMethod != null && butterKnifeOnClickMethod.body != null) { 646 | val onClickMethodBody = butterKnifeOnClickMethod.body 647 | // 获取switch 648 | var psiSwitchStatement: PsiSwitchStatement? = null 649 | for (psiElement in onClickMethodBody!!.children) { 650 | if (psiElement is PsiSwitchStatement) { 651 | psiSwitchStatement = psiElement 652 | break 653 | } 654 | } 655 | val psiMethodByButterKnifeOnClickValue = mClass.getPsiMethodByButterKnifeOnClickValue() 656 | val onClickIdList = getOnClickListById(mOnClickList) 657 | val onClickValues = createOnClickValue(psiMethodByButterKnifeOnClickValue, onClickIdList) 658 | // 有switch 659 | if (psiSwitchStatement != null) { 660 | // 获取switch的内容 661 | val psiSwitchStatementBody = psiSwitchStatement.body 662 | if (psiSwitchStatementBody != null) { 663 | for (onClickValue in onClickValues) { 664 | val cass = "case $onClickValue:" 665 | // 判断是否存在 666 | var isExist = false 667 | for (statement in psiSwitchStatementBody.statements) { 668 | if (statement.text.replace("\n".toRegex(), "").replace("break;".toRegex(), "") == cass) { 669 | isExist = true 670 | break 671 | } else { 672 | isExist = false 673 | } 674 | } 675 | // 不存在就添加 676 | if (!isExist) { 677 | psiSwitchStatementBody.add(mFactory.createStatementFromText(cass, psiSwitchStatementBody)) 678 | psiSwitchStatementBody.add(mFactory.createStatementFromText("break;", psiSwitchStatementBody)) 679 | } 680 | } 681 | } 682 | } else { 683 | // 没有switch 684 | val psiMethodParamsViewField = mClass.getPsiMethodParamsViewField() 685 | if (psiMethodParamsViewField != null) { 686 | butterKnifeOnClickMethod.body?.add(mFactory.createStatementFromText( 687 | psiMethodParamsViewField.createSwitchByOnClickMethod(onClickValues), butterKnifeOnClickMethod)) 688 | } 689 | } 690 | mClass.createOnClickAnnotation(mFactory, onClickValues) 691 | return 692 | } 693 | if (mOnClickList.size != 0) { 694 | mClass.add(mFactory.createMethodFromText(mOnClickList.createButterKnifeOnClickMethodAndSwitch(), mClass)) 695 | } 696 | } 697 | 698 | /** 699 | * 写onDestroyView方法 700 | */ 701 | private fun generateButterKnifeLayoutCode() { 702 | // 判断是否已有onDestroyView方法 703 | val onDestroyViewMethods = mClass.findMethodsByName(Constant.Ext.CREATOR_ONDESTROYVIEW_METHOD, false) 704 | // 有onDestroyView方法 705 | if (onDestroyViewMethods.isNotEmpty() && onDestroyViewMethods[0].body != null) { 706 | val onDestroyViewMethodBody = onDestroyViewMethods[0].body 707 | // 获取onDestroyView方法里面的每条内容 708 | val statements = onDestroyViewMethodBody!!.statements 709 | for (element in mElements) { 710 | if (element.isEnable) { 711 | // 判断是否已存在unbinder.unbind(); 712 | var isFdExist = false 713 | for (statement in statements) { 714 | if (statement.text == Constant.Ext.CREATOR_UNBINDER_FIELD) { 715 | isFdExist = true 716 | break 717 | } else { 718 | isFdExist = false 719 | } 720 | } 721 | // 不存在就添加 722 | if (!isFdExist) { 723 | onDestroyViewMethodBody.add(mFactory.createStatementFromText(Constant.Ext.CREATOR_UNBINDER_FIELD, onDestroyViewMethods[0])) 724 | break 725 | } 726 | } 727 | } 728 | return 729 | } 730 | mClass.add(mFactory.createMethodFromText(createOnDestroyViewMethod(), mClass)) 731 | } 732 | 733 | /** 734 | * 创建LayoutInflater的方法,包含ButterKnife.findById(view, R.id.id) 735 | * @param context context 736 | */ 737 | private fun generateButterKnifeViewMethod(context: String) { 738 | val viewMethodName = "init" + layoutInflaterType2Str(mLayoutInflaterText, mLayoutInflaterType) 739 | val viewMethod = mClass.getPsiMethodByName(viewMethodName) 740 | if (viewMethod != null && viewMethod.body != null) { 741 | val body = viewMethod.body 742 | // 获取initView方法里面的每条内容 743 | val statements = body!!.statements 744 | for (element in mElements) { 745 | if (element.isEnable) { 746 | // 判断是否已存在findViewById 747 | var isFdExist = false 748 | var inflater = "" 749 | if (mIsLayoutInflater) { 750 | inflater = layoutInflaterType2Str(mLayoutInflaterText, mLayoutInflaterType) 751 | } 752 | val findViewById = "${element.fieldName}$inflater = ButterKnife.findById($mLayoutInflaterText, ${element.fullID});" 753 | for (statement in statements) { 754 | if (statement.text == findViewById) { 755 | isFdExist = true 756 | break 757 | } else { 758 | isFdExist = false 759 | } 760 | } 761 | // 不存在就添加 762 | if (!isFdExist) { 763 | body.add(mFactory.createStatementFromText(findViewById, viewMethod)) 764 | } 765 | } 766 | } 767 | return 768 | } 769 | mClass.add(mFactory.createMethodFromText( 770 | createButterKnifeViewMethod(mIsLayoutInflater, mLayoutInflaterText, context, mSelectedText, mElements, viewMethodName, mLayoutInflaterType), mClass)) 771 | } 772 | 773 | } 774 | -------------------------------------------------------------------------------- /src/views/ButterKnifeDialog.kt: -------------------------------------------------------------------------------- 1 | package views 2 | 3 | import com.intellij.openapi.editor.Editor 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.psi.* 6 | import entitys.Element 7 | import getPsiMethodByButterKnifeOnClick 8 | import getPsiMethodByButterKnifeOnClickValue 9 | import java.util.* 10 | 11 | /** 12 | * ButterKnifeDialog 13 | * @author Jowan 14 | */ 15 | class ButterKnifeDialog 16 | /** 17 | * ButterKnifeDialog 18 | */ 19 | (mProject: Project, 20 | mEditor: Editor, 21 | mSelectedText: String, 22 | /*获取mElements*/ 23 | private val mElements: ArrayList, 24 | /*获取当前文件*/ 25 | mPsiFile: PsiFile, 26 | /*获取class*/ 27 | private val mClass: PsiClass, 28 | /*判断是否全选*/ 29 | private var elementSize: Int, 30 | /*判断是否是ButterKnife*/ 31 | mIsButterKnife: Boolean) 32 | : GenerateDialog(mProject = mProject, 33 | mEditor = mEditor, 34 | mSelectedText = mSelectedText, 35 | elements = mElements, 36 | mPsiFile = mPsiFile, 37 | psiClass = mClass, 38 | elementSize = elementSize, 39 | mIsButterKnife = mIsButterKnife) { 40 | 41 | init { 42 | initExist() 43 | initTopPanel() 44 | initContentPanel() 45 | setCheckAll() 46 | checkBind() 47 | initBottomPanel() 48 | setConstraints() 49 | setDialog() 50 | } 51 | 52 | /** 53 | * 判断已存在的变量,设置全选 54 | * 判断onclick是否写入 55 | */ 56 | private fun initExist() { 57 | // 判断是否已存在的变量 58 | var isFdExist: Boolean 59 | // 判断是否存在case R.id.id: 60 | var isCaseExist = false 61 | // 判断注解是否存在R.id.id 62 | var isAnnotationValueExist = false 63 | val fields = mClass.fields 64 | var onClickStatement: Array? = null 65 | val psiMethodByButterKnifeOnClickValue = mClass.getPsiMethodByButterKnifeOnClickValue() 66 | val psiMethodByButterKnifeOnClick = mClass.getPsiMethodByButterKnifeOnClick() 67 | if (psiMethodByButterKnifeOnClick != null && psiMethodByButterKnifeOnClick.body != null) { 68 | onClickStatement = psiMethodByButterKnifeOnClick.body?.statements 69 | } 70 | for (element in mElements) { 71 | element.isClickable = true 72 | element.setClickEnable() 73 | isFdExist = checkFieldExist(fields, element) 74 | if (onClickStatement != null) { 75 | isCaseExist = checkCaseExist(onClickStatement, element) 76 | } 77 | if (psiMethodByButterKnifeOnClickValue.size > 0) { 78 | isAnnotationValueExist = psiMethodByButterKnifeOnClickValue.contains(element.fullID) 79 | } 80 | setElementProperty(elementSize, isFdExist, isCaseExist, isAnnotationValueExist, fields, element) 81 | } 82 | } 83 | 84 | /** 85 | * 判断onClick方法里面是否包含field的case 86 | * @param onClickStatement onClick方法 87 | * 88 | * @param element element 89 | * 90 | * @return boolean 91 | */ 92 | private fun checkCaseExist(onClickStatement: Array, element: Element): Boolean { 93 | val cass = "case " + element.fullID + ":" 94 | onClickStatement 95 | .filterIsInstance() 96 | .mapNotNull { // 获取switch的内容 97 | it.body 98 | } 99 | .forEach { 100 | it.statements 101 | .filter { it.text.replace("\n".toRegex(), "").replace("break;".toRegex(), "") == cass } 102 | .forEach { return true } 103 | } 104 | return false 105 | } 106 | 107 | /** 108 | * 判断变量是否包含@BindView注解 109 | * @param fields fields 110 | * 111 | * @param element element 112 | * 113 | * @return boolean 114 | */ 115 | private fun checkFieldExist(fields: Array, element: Element): Boolean { 116 | fields.forEach { 117 | if (it.name != null && it.name == element.fieldName && it.text.contains("@BindView(${element.fullID})")) { 118 | return true 119 | } 120 | } 121 | return false 122 | } 123 | 124 | /** 125 | * 为已存在的变量设置checkbox 126 | * @param mElementSizeOld mElementSizeOld 127 | * 128 | * @param isFdExist 判断是否已存在的变量 129 | * 130 | * @param isCaseExist 判断是否存在case R.id.id: 131 | * 132 | * @param isAnnotationValueExist 判断注解是否存在R.id.id 133 | * 134 | * @param fields fields 135 | * 136 | * @param element element 137 | */ 138 | private fun setElementProperty(mElementSizeOld: Int, isFdExist: Boolean, isCaseExist: Boolean, 139 | isAnnotationValueExist: Boolean, fields: Array, element: Element) { 140 | var mElementSize = mElementSizeOld 141 | for (field in fields) { 142 | val name = field.name 143 | if (name != null && name == element.fieldName && isFdExist) { 144 | // 已存在的变量设置checkbox为false 145 | element.isEnable = false 146 | mElementSize -= 1 147 | elementSize = mElementSize 148 | if (element.isClickEnable && (!isCaseExist || !isAnnotationValueExist)) { 149 | element.isClickable = true 150 | element.isEnable = true 151 | mElementSize += 1 152 | elementSize = mElementSize 153 | } 154 | break 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/views/FindViewByIdDialog.kt: -------------------------------------------------------------------------------- 1 | package views 2 | 3 | import com.intellij.openapi.editor.Editor 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.psi.* 6 | import entitys.Element 7 | import getInitViewBodyStatements 8 | import getOnClickStatement 9 | import java.util.* 10 | 11 | /** 12 | * FindViewByIdDialog 13 | * @author Jowan 14 | */ 15 | class FindViewByIdDialog 16 | /** 17 | * FindViewByIdDialog 18 | */ 19 | (mProject: Project, 20 | mEditor: Editor, 21 | mSelectedText: String, 22 | /*获取mElements*/ 23 | private val mElements: ArrayList, 24 | /*获取当前文件*/ 25 | mPsiFile: PsiFile, 26 | /*获取class*/ 27 | private val mClass: PsiClass, 28 | /*判断是否全选*/ 29 | private var elementSize: Int, 30 | /*判断是否是ButterKnife*/ 31 | mIsButterKnife: Boolean) 32 | : GenerateDialog(mProject = mProject, 33 | mEditor = mEditor, 34 | mSelectedText = mSelectedText, 35 | elements = mElements, 36 | mPsiFile = mPsiFile, 37 | psiClass = mClass, 38 | elementSize = elementSize, 39 | mIsButterKnife = mIsButterKnife) { 40 | 41 | init { 42 | initExist() 43 | initTopPanel() 44 | initContentPanel() 45 | setCheckAll() 46 | initBottomPanel() 47 | setConstraints() 48 | setDialog() 49 | } 50 | 51 | /** 52 | * 判断已存在的变量,设置全选 53 | * 判断onclick是否写入 54 | */ 55 | private fun initExist() { 56 | // 判断是否已存在的变量 57 | var isFdExist = false 58 | // 判断是否已存在setOnClickListener 59 | var isClickExist = false 60 | // 判断是否存在case R.id.id: 61 | var isCaseExist = false 62 | val fields = mClass.fields 63 | // 获取initView方法的内容 64 | val statements = mClass.getInitViewBodyStatements() 65 | val onClickStatement = mClass.getOnClickStatement() 66 | elementSize = mElements.size 67 | for (element in mElements) { 68 | if (statements != null) { 69 | isFdExist = checkFieldExist(statements, element) 70 | val setOnClickListener = element.fieldName + ".setOnClickListener(this);" 71 | isClickExist = checkClickExist(statements, setOnClickListener) 72 | } 73 | if (onClickStatement != null) { 74 | isCaseExist = checkCaseExist(onClickStatement, element) 75 | } 76 | setElementProperty(elementSize, isFdExist, isClickExist, isCaseExist, fields, element) 77 | } 78 | } 79 | 80 | /** 81 | * 判断onClick方法里面是否包含field的case 82 | * @param onClickStatement onClick方法 83 | * 84 | * @param element element 85 | * 86 | * @return boolean 87 | */ 88 | private fun checkCaseExist(onClickStatement: Array, element: Element): Boolean { 89 | val cass = "case " + element.fullID + ":" 90 | onClickStatement 91 | .filterIsInstance() 92 | .mapNotNull { // 获取switch的内容 93 | it.body 94 | } 95 | .forEach { 96 | it.statements 97 | .filter { it.text.replace("\n".toRegex(), "").replace("break;".toRegex(), "") == cass } 98 | .forEach { return true } 99 | } 100 | return false 101 | } 102 | 103 | /** 104 | * 判断initView方法里面是否包含field的findViewById 105 | * @param statements initView方法 106 | * 107 | * @param element element 108 | * 109 | * @return boolean 110 | */ 111 | private fun checkFieldExist(statements: Array, element: Element): Boolean { 112 | statements.forEach { 113 | if (it.text.contains(element.fieldName) 114 | && it.text.contains("findViewById(${element.fullID});") ) { 115 | return true 116 | } 117 | } 118 | return false 119 | } 120 | 121 | /** 122 | * 判断是否setOnClickListener 123 | * @param statements onClick方法 124 | * 125 | * @param setOnClickListener setOnClickListener 126 | * 127 | * @return boolean 128 | */ 129 | private fun checkClickExist(statements: Array, setOnClickListener: String): Boolean { 130 | statements.forEach { 131 | if (it.text == setOnClickListener) { 132 | return true 133 | } 134 | } 135 | return false 136 | } 137 | 138 | /** 139 | * 为已存在的变量设置checkbox 140 | * @param mElementSizes mElementSizes 141 | * 142 | * @param isFdExist 判断是否已存在的变量 143 | * 144 | * @param isClickExist 判断是否已存在setOnClickListener 145 | * 146 | * @param isCaseExist 判断是否存在case R.id.id: 147 | * 148 | * @param fields fields 149 | * 150 | * @param element element 151 | */ 152 | private fun setElementProperty(mElementSizes: Int, isFdExist: Boolean, isClickExist: Boolean, 153 | isCaseExist: Boolean, fields: Array, element: Element) { 154 | var mElementSize = mElementSizes 155 | for (field in fields) { 156 | val name = field.name 157 | if (name != null && name == element.fieldName && isFdExist) { 158 | // 已存在的变量设置checkbox为false 159 | element.isEnable = false 160 | mElementSize -= 1 161 | elementSize = mElementSize 162 | if (element.isClickEnable && (!isClickExist || !isCaseExist)) { 163 | element.isClickable = true 164 | element.isEnable = true 165 | mElementSize = elementSize + 1 166 | elementSize = mElementSize 167 | } 168 | break 169 | } 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/views/GenerateDialog.kt: -------------------------------------------------------------------------------- 1 | package views 2 | 3 | import com.intellij.openapi.editor.Editor 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.psi.JavaPsiFacade 6 | import com.intellij.psi.PsiClass 7 | import com.intellij.psi.PsiFile 8 | import com.intellij.ui.components.JBScrollPane 9 | import constant.Constant 10 | import entitys.Element 11 | import entitys.IdBean 12 | import getFieldName 13 | import isButterKnifeBindExist 14 | import showPopupBalloon 15 | import utils.GenerateCreator 16 | import java.awt.FlowLayout 17 | import java.awt.GridBagConstraints 18 | import java.awt.GridBagLayout 19 | import java.awt.GridLayout 20 | import java.awt.event.ActionEvent 21 | import java.awt.event.ActionListener 22 | import java.awt.event.ItemEvent 23 | import java.awt.event.ItemListener 24 | import java.util.* 25 | import javax.swing.* 26 | import javax.swing.border.EmptyBorder 27 | 28 | 29 | /** 30 | * GenerateDialog 31 | * @author Jowan 32 | */ 33 | open class GenerateDialog( 34 | private val mProject: Project, 35 | private val mEditor: Editor, 36 | private val mSelectedText: String, 37 | /*获取mElements*/ 38 | private val elements: ArrayList, 39 | /*获取当前文件*/ 40 | private val mPsiFile: PsiFile, 41 | /*获取class*/ 42 | private val psiClass: PsiClass, 43 | /*判断是否全选*/ 44 | private var elementSize: Int, 45 | /*判断是否是ButterKnife*/ 46 | private val mIsButterKnife: Boolean 47 | ) : JFrame(), ActionListener, ItemListener { 48 | 49 | // 判断OnClick是否全选 50 | private var mOnClickSize: Int = 0 51 | 52 | // 标签JPanel 53 | private val mPanelTitle = JPanel() 54 | private val mTitleName = JCheckBox(Constant.Dialog.TABLE_FIELD_VIEW_WIDGET) 55 | private val mTitleId = JLabel(Constant.Dialog.TABLE_FIELD_VIEW_ID) 56 | private val mTitleClick = JCheckBox(Constant.FIELD_ON_CLICK, false) 57 | // 命名JPanel 58 | private val mPanelTitleField = JPanel() 59 | private val mTitleFieldGroup = ButtonGroup() 60 | // aa_bb 61 | private val mTitleFieldUnderline = JRadioButton("aa_bb") 62 | // aaBb 63 | private val mTitleFieldHump = JRadioButton("aaBb") 64 | // mAaBb 65 | private val mTitleFieldPrefix = JRadioButton("mAaBb", true) 66 | 67 | // 内容JPanel 68 | private val mContentJPanel = JPanel() 69 | private val mContentLayout = GridBagLayout() 70 | private val mContentConstraints = GridBagConstraints() 71 | // 内容JBScrollPane滚动 72 | private lateinit var jScrollPane: JBScrollPane 73 | 74 | // 底部JPanel 75 | // LayoutInflater JPanel 76 | private val mPanelInflater = JPanel(FlowLayout(FlowLayout.LEFT)) 77 | // 是否选择LayoutInflater 78 | private val mLayoutInflater = JCheckBox(Constant.Dialog.FIELD_LAYOUT_INFLATER, false) 79 | // 手动修改LayoutInflater字段名 80 | private lateinit var mLayoutInflaterField: JTextField 81 | private var type = 3 82 | 83 | // viewHolder 84 | private val mPanelViewHolder = JPanel(FlowLayout(FlowLayout.LEFT)) 85 | private val mViewHolderCheck = JCheckBox(Constant.Dialog.VIEWHOLDER_CHECK, false) 86 | 87 | // 是否需要强转 88 | private val mPanelNeedCasts = JPanel(FlowLayout(FlowLayout.LEFT)) 89 | private val mNeedCastsCheck = JCheckBox(Constant.Dialog.NEED_CASTS, true) 90 | 91 | // 是否bind,默认是true 92 | private val mBind = JCheckBox(Constant.Dialog.FIELD_BUTTERKNIFE_BIND, true) 93 | 94 | // 确定、取消JPanel 95 | private val mPanelButtonRight = JPanel() 96 | private val mButtonConfirm = JButton(Constant.Dialog.BUTTON_CONFIRM) 97 | private val mButtonCancel = JButton(Constant.Dialog.BUTTON_CANCEL) 98 | 99 | // GridBagLayout不要求组件的大小相同便可以将组件垂直、水平或沿它们的基线对齐 100 | private val mLayout = GridBagLayout() 101 | // GridBagConstraints用来控制添加进的组件的显示位置 102 | private val mConstraints = GridBagConstraints() 103 | 104 | 105 | /** 106 | * 全选设置 107 | */ 108 | internal fun setCheckAll() { 109 | elements 110 | .filter { it.isClickable || mIsButterKnife } 111 | .forEach { mOnClickSize++ } 112 | mTitleName.isSelected = elementSize == elements.size 113 | mTitleName.addActionListener(this) 114 | mTitleClick.isSelected = mOnClickSize == elements.size 115 | mTitleClick.addActionListener(this) 116 | } 117 | 118 | /** 119 | * 判断是否存在ButterKnife.bind(this)/ButterKnife.bind(this, view) 120 | */ 121 | internal fun checkBind() { 122 | mBind.isSelected = psiClass.isButterKnifeBindExist() 123 | } 124 | 125 | /** 126 | * 添加头部 127 | */ 128 | internal fun initTopPanel() { 129 | mPanelTitle.layout = GridLayout(1, 4, 8, 10) 130 | mPanelTitle.border = EmptyBorder(5, 10, 5, 10) 131 | mPanelTitleField.layout = GridLayout(1, 3, 0, 0) 132 | mTitleName.horizontalAlignment = JLabel.LEFT 133 | mTitleName.border = EmptyBorder(0, 5, 0, 0) 134 | mTitleId.horizontalAlignment = JLabel.LEFT 135 | mTitleClick.horizontalAlignment = JLabel.LEFT 136 | // 添加listener 137 | mTitleFieldUnderline.addItemListener(this); 138 | mTitleFieldHump.addItemListener(this) 139 | mTitleFieldPrefix.addItemListener(this) 140 | // 添加到group 141 | mTitleFieldGroup.add(mTitleFieldUnderline); 142 | mTitleFieldGroup.add(mTitleFieldHump) 143 | mTitleFieldGroup.add(mTitleFieldPrefix) 144 | // 添加到JPanel 145 | mPanelTitleField.add(mTitleFieldUnderline); 146 | mPanelTitleField.add(mTitleFieldHump) 147 | mPanelTitleField.add(mTitleFieldPrefix) 148 | // 添加到JPanel 149 | mPanelTitle.add(mTitleName) 150 | mPanelTitle.add(mTitleId) 151 | mPanelTitle.add(mTitleClick) 152 | mPanelTitle.add(mPanelTitleField) 153 | mPanelTitle.setSize(900, 30) 154 | // 添加到JFrame 155 | contentPane.add(mPanelTitle, 0) 156 | } 157 | 158 | /** 159 | * 添加底部 160 | */ 161 | internal fun initBottomPanel() { 162 | val viewField = mSelectedText getFieldName 3 163 | mLayoutInflaterField = JTextField(viewField, viewField.length) 164 | // 添加监听 165 | mButtonConfirm.addActionListener(this) 166 | mButtonCancel.addActionListener(this) 167 | mViewHolderCheck.addActionListener(this) 168 | // viewHolder 169 | mPanelViewHolder.add(mViewHolderCheck) 170 | // casts 171 | mNeedCastsCheck.isEnabled = !mIsButterKnife 172 | mPanelNeedCasts.add(mNeedCastsCheck) 173 | // 右边 174 | mPanelButtonRight.add(mButtonConfirm) 175 | mPanelButtonRight.add(mButtonCancel) 176 | // 添加到JPanel 177 | //mPanelInflater.add(mCheckAll); 178 | if (mIsButterKnife) { 179 | mPanelInflater.add(mBind) 180 | } 181 | mPanelInflater.add(mLayoutInflater) 182 | mPanelInflater.add(mLayoutInflaterField) 183 | // 添加到JFrame 184 | contentPane.add(mPanelInflater, 2) 185 | contentPane.add(mPanelViewHolder, 3) 186 | contentPane.add(mPanelNeedCasts, 4) 187 | contentPane.add(mPanelButtonRight, 5) 188 | } 189 | 190 | /** 191 | * 解析mElements,并添加到JPanel 192 | */ 193 | internal fun initContentPanel() { 194 | mContentJPanel.removeAll() 195 | // 设置内容 196 | for (i in elements.indices) { 197 | val element = elements[i] 198 | val itemJPanel = IdBean(GridLayout(1, 4, 10, 10), 199 | EmptyBorder(5, 10, 5, 10), 200 | JCheckBox(element.name), 201 | JLabel(element.id), 202 | JCheckBox(), 203 | JTextField(element.fieldName), 204 | element.isEnable, 205 | element.isClickable, 206 | element.isClickEnable) 207 | // 监听 208 | itemJPanel.setEnableActionListener { enableCheckBox -> 209 | element.isEnable = enableCheckBox.isSelected 210 | elementSize = if (enableCheckBox.isSelected) elementSize + 1 else elementSize - 1 211 | mTitleName.isSelected = elementSize == elements.size 212 | } 213 | itemJPanel.setClickActionListener { clickCheckBox -> 214 | element.isClickable = clickCheckBox.isSelected 215 | mOnClickSize = if (clickCheckBox.isSelected) mOnClickSize + 1 else mOnClickSize - 1 216 | mTitleClick.isSelected = mOnClickSize == elements.size 217 | } 218 | itemJPanel.setFieldFocusListener { fieldJTextField -> element.fieldName = fieldJTextField.text } 219 | mContentJPanel.add(itemJPanel) 220 | mContentConstraints.fill = GridBagConstraints.HORIZONTAL 221 | mContentConstraints.gridwidth = 0 222 | mContentConstraints.gridx = 0 223 | mContentConstraints.gridy = i 224 | mContentConstraints.weightx = 1.0 225 | mContentLayout.setConstraints(itemJPanel, mContentConstraints) 226 | } 227 | mContentJPanel.layout = mContentLayout 228 | jScrollPane = JBScrollPane(mContentJPanel) 229 | jScrollPane.revalidate() 230 | // 添加到JFrame 231 | contentPane.add(jScrollPane, 1) 232 | } 233 | 234 | /** 235 | * 设置Constraints 236 | */ 237 | internal fun setConstraints() { 238 | // 使组件完全填满其显示区域 239 | mConstraints.fill = GridBagConstraints.BOTH 240 | // 设置组件水平所占用的格子数,如果为0,就说明该组件是该行的最后一个 241 | mConstraints.gridwidth = 0 242 | // 第几列 243 | mConstraints.gridx = 0 244 | // 第几行 245 | mConstraints.gridy = 0 246 | // 行拉伸0不拉伸,1完全拉伸 247 | mConstraints.weightx = 1.0 248 | // 列拉伸0不拉伸,1完全拉伸 249 | mConstraints.weighty = 0.0 250 | // 设置组件 251 | mLayout.setConstraints(mPanelTitle, mConstraints) 252 | mConstraints.fill = GridBagConstraints.BOTH 253 | mConstraints.gridwidth = 1 254 | mConstraints.gridx = 0 255 | mConstraints.gridy = 1 256 | mConstraints.weightx = 1.0 257 | mConstraints.weighty = 1.0 258 | mLayout.setConstraints(jScrollPane, mConstraints) 259 | mConstraints.fill = GridBagConstraints.HORIZONTAL 260 | mConstraints.gridwidth = 0 261 | mConstraints.gridx = 0 262 | mConstraints.gridy = 2 263 | mConstraints.weightx = 1.0 264 | mConstraints.weighty = 0.0 265 | mLayout.setConstraints(mPanelInflater, mConstraints) 266 | mConstraints.fill = GridBagConstraints.HORIZONTAL 267 | mConstraints.gridwidth = 0 268 | mConstraints.gridx = 0 269 | mConstraints.gridy = 3 270 | mConstraints.weightx = 1.0 271 | mConstraints.weighty = 0.0 272 | mLayout.setConstraints(mPanelViewHolder, mConstraints) 273 | mConstraints.fill = GridBagConstraints.HORIZONTAL 274 | mConstraints.gridwidth = 0 275 | mConstraints.gridx = 0 276 | mConstraints.gridy = 4 277 | mConstraints.weightx = 1.0 278 | mConstraints.weighty = 0.0 279 | mLayout.setConstraints(mPanelNeedCasts, mConstraints) 280 | mConstraints.fill = GridBagConstraints.NONE 281 | mConstraints.gridwidth = 0 282 | mConstraints.gridx = 0 283 | mConstraints.gridy = 5 284 | mConstraints.weightx = 0.0 285 | mConstraints.weighty = 0.0 286 | mConstraints.anchor = GridBagConstraints.EAST 287 | mLayout.setConstraints(mPanelButtonRight, mConstraints) 288 | } 289 | 290 | /** 291 | * 显示dialog 292 | */ 293 | fun showDialog() { 294 | // 显示 295 | isVisible = true 296 | } 297 | 298 | /** 299 | * 设置JFrame参数 300 | */ 301 | internal fun setDialog() { 302 | // 设置标题 303 | title = if (mIsButterKnife) { 304 | Constant.Dialog.TITLE_BUTTERKNIFE 305 | } else { 306 | Constant.Dialog.TITLE_FINDVIEWBYID 307 | } 308 | // 设置布局管理 309 | layout = mLayout 310 | // 不可拉伸 311 | isResizable = false 312 | // 设置大小 313 | setSize(810, 405) 314 | // 自适应大小 315 | // pack(); 316 | // 设置居中,放在setSize后面 317 | setLocationRelativeTo(null) 318 | // 显示最前 319 | isAlwaysOnTop = true 320 | } 321 | 322 | /** 323 | * 关闭dialog 324 | */ 325 | fun cancelDialog() { 326 | isVisible = false 327 | dispose() 328 | } 329 | 330 | /** 331 | * 刷新JScrollPane内容 332 | */ 333 | private fun refreshJScrollPane() { 334 | remove(jScrollPane) 335 | initContentPanel() 336 | setConstraints() 337 | revalidate() 338 | } 339 | 340 | /** 341 | * 生成 342 | 343 | * @param isLayoutInflater 是否是LayoutInflater.from(this).inflate(R.layout.activity_main, null); 344 | * 345 | * @param text 自定义text 346 | * 347 | * @param isBind 是否是bind 348 | * 349 | * @param viewHolder 是否是viewHolder 350 | * 351 | * @param isButterKnife 是否是ButterKnife 352 | * 353 | * @param mNeedCasts 是否需要强转 354 | */ 355 | private fun setCreator(isLayoutInflater: Boolean, text: String, isBind: Boolean, viewHolder: Boolean, isButterKnife: Boolean, mNeedCasts: Boolean) { 356 | GenerateCreator(command = Constant.CREATOR_COMMAND_NAME, 357 | mDialog = this, 358 | mEditor = mEditor, 359 | mFile = mPsiFile, 360 | mClass = psiClass, 361 | mProject = psiClass.project, 362 | mElements = elements, 363 | mFactory = JavaPsiFacade.getElementFactory(psiClass.project), 364 | mSelectedText = mSelectedText, 365 | mIsLayoutInflater = isLayoutInflater, 366 | mLayoutInflaterText = text, 367 | mLayoutInflaterType = type, 368 | mIsButterKnife = isButterKnife, 369 | mIsBind = isBind, 370 | mViewHolder = viewHolder, 371 | mNeedCasts = mNeedCasts).execute() 372 | } 373 | 374 | override fun actionPerformed(e: ActionEvent) { 375 | when (e.actionCommand) { 376 | Constant.Dialog.BUTTON_CONFIRM -> { 377 | cancelDialog() 378 | if (mLayoutInflater.isSelected && mLayoutInflaterField.text.isEmpty()) { 379 | mEditor.showPopupBalloon(Constant.Action.SELECTED_LAYOUT_FIELD_TEXT_NO_NULL, 5) 380 | return 381 | } 382 | setCreator(mLayoutInflater.isSelected, mLayoutInflaterField.text, mBind.isSelected, mViewHolderCheck.isSelected, mIsButterKnife, mNeedCastsCheck.isSelected) 383 | } 384 | Constant.Dialog.BUTTON_CANCEL -> cancelDialog() 385 | Constant.FIELD_ON_CLICK -> { 386 | // 刷新 387 | for (element in elements) { 388 | element.isClickable = mTitleClick.isSelected 389 | } 390 | mOnClickSize = if (mTitleClick.isSelected) elements.size else 0 391 | refreshJScrollPane() 392 | } 393 | Constant.Dialog.TABLE_FIELD_VIEW_WIDGET -> { 394 | // 刷新 395 | for (element in elements) { 396 | element.isEnable = mTitleName.isSelected 397 | } 398 | elementSize = if (mTitleName.isSelected) elements.size else 0 399 | refreshJScrollPane() 400 | } 401 | Constant.Dialog.VIEWHOLDER_CHECK -> { 402 | mLayoutInflater.isEnabled = !mViewHolderCheck.isSelected 403 | mLayoutInflaterField.isEnabled = !mViewHolderCheck.isSelected 404 | mBind.isEnabled = !mViewHolderCheck.isSelected 405 | } 406 | } 407 | } 408 | 409 | override fun itemStateChanged(e: ItemEvent) { 410 | type = when (e.source) { 411 | mTitleFieldPrefix -> 3 412 | mTitleFieldHump -> 2 413 | else -> 1 414 | } 415 | for (element in elements) { 416 | if (element.isEnable) { 417 | // 设置类型 418 | element.fieldNameType = type 419 | // 置空 420 | element.fieldName = "" 421 | } 422 | } 423 | mLayoutInflaterField.text = mSelectedText getFieldName type 424 | refreshJScrollPane() 425 | } 426 | } 427 | --------------------------------------------------------------------------------