Android Studio template file group

Android Studio template file group

The file group template is a powerful Android development template based on the FreeMarker template language. It can be said that code snippet templates and file templates are tools to improve coding efficiency, while the file group template can be regarded as a template engine.

Effect picture display

Template renderings used in existing projects

Using templates when creating projects

[[185259]]

Example scenario

When developing Android, we often create a Demo project for many purposes, such as verifying a problem, learning how to use a framework, testing a lib library written by ourselves, etc. At this time, we may create an Activity, write some buttons in XML, and then write the event listening logic of the buttons in the Activity. In other words, we have to do so many operations to execute a piece of code. In order to simplify this repetitive operation, I wrote a DebugActivity class, which supports us to write a subclass to inherit it, and then write a few methods as shown below. When running, buttons will be dynamically created according to the methods, and the code logic of the method will be executed when the buttons are clicked.

  1. public void _test() {
  2.  
  3. T( "Popup Toast" );
  4.  
  5. }

Since this article mainly introduces templates, I will not go into the specific code technical details related to this scenario. If you are interested, you can take a look at the code of DebugActivity. It is only mentioned here to pave the way for simple template development.

Template location

There are some system preset templates in Android Studio Template, which we can modify directly or add new templates. Open the folder /Contents/plugins/android/lib/templates in the Android Studio installation directory and we can see the following directory structure, which is where the templates in AS are stored.

Our next work is here. To be on the safe side, we will create a new directory here. The templates we write ourselves are placed in the new directory. For example, I have created a directory called pk here.

Template Specification

Based on the above, we can directly open the /activies/EmptyActivity directory, as shown below

We can see that the red area above is the file structure of Template. Let's roughly explain the meaning of each file (folder)

  • globals.xml.ftl where the parameters are configured in the template (optional)
  • recipe.xml.ftl is the place where the template behavior is executed. After the template is imported, it determines what to do next (optional, but it is meaningless if not selected, because the template import is driven by behavior)
  • root is the directory for storing template files and imported resources. Template files can be any text format files such as .xml, .java, .gradle, etc. Resources are generally .png resource files we import (optional, same as above if not selected)
  • template_blank_activity.png The guide image when importing the template (optional)
  • template.xml Configuration file for the template engine (required)

We can see that the core parts are root, recipe.xml.ftl and template.xml. Next, we will focus on these three parts.

We can open the root directory and see that all the files except the image resource files end with .ftl, which is a standard FreeMarker file. FreeMarker is a template framework similar to Velocity. It is said to have better performance when processing multiple files, which is probably why Android Studio chooses Velocity as a single file template and FreeMarker as a file group template. If you are interested, you can go to the FreeMarker official website to learn about it. Its custom tag function is still very powerful, and I personally feel that it is more down-to-earth than Velocity.

Next, let's take a look at the contents of recipe.xml.ftl. Open it as follows

The syntax starting with <# is FreeMarker syntax, which can be understood by following the instructions, so I won't go into details. In fact, the most important part of this file is the following four tags:

  • copy is a simple copy, copying a file in the template root directory to a directory in the target project
  • instantiate is very similar to copy, but its most common function is that it does not simply copy through the IO flow, but generates the final target file through the FreeMarker framework according to the logical judgment and data introduction that FreeMarker can recognize in the template.
  • merge When there is a file in the target project, and we want to merge some parts of our template into this file, we should use merge, for example, when we add an Activity, we need to merge the configuration of AndroidManifest.xml. Currently, the supported merge formats are .xml and .gradle, but the support for .gradle is not very good, but it does not affect the development of the template. For the developers of this template engine, this may be the most troublesome part, but for us users, we don't need to think so much, just use it directly
  • open This is very simple, it specifies the file that the IDE should open after the template is imported

Then look at the content of template.xml

  1. <?xml version= "1.0" ?>
  2.  
  3. <template
  4.  
  5. format= "5"  
  6.  
  7. revision= "5"  
  8.  
  9. name = "Empty Activity"  
  10.  
  11. minApi= "7"  
  12.  
  13. minBuildApi= "14"  
  14.  
  15. description= "Creates a new empty activity" >
  16.  
  17. <category value= "Activity" />
  18.  
  19. <formfactor value= "Mobile" />
  20.  
  21. <parameter
  22.  
  23. id= "activityClass"  
  24.  
  25. name = "Activity Name"  
  26.  
  27. type= "string"  
  28.  
  29. constraints= "class|unique|nonempty"  
  30.  
  31. suggest= "${layoutToActivity(layoutName)}"  
  32.  
  33. default = "MainActivity"  
  34.  
  35. help= "The name of the activity class to create" />
  36.  
  37. <parameter
  38.  
  39. id= "generateLayout"  
  40.  
  41. name = "Generate Layout File"  
  42.  
  43. type= "boolean"  
  44.  
  45. default = "true"  
  46.  
  47. help= "If true, a layout file will be generated" />
  48.  
  49. <parameter
  50.  
  51. id= "layoutName"  
  52.  
  53. name = "Layout Name"  
  54.  
  55. type= "string"  
  56.  
  57. constraints= "layout|unique|nonempty"  
  58.  
  59. suggest= "${activityToLayout(activityClass)}"  
  60.  
  61. default = "activity_main"  
  62.  
  63. visibility= "generateLayout"  
  64.  
  65. help= "The name of the layout to create for the activity" />
  66.  
  67. <parameter
  68.  
  69. id= "isLauncher"  
  70.  
  71. name = "Launcher Activity"  
  72.  
  73. type= "boolean"  
  74.  
  75. default = "false"  
  76.  
  77. help= "If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
  78.  
  79.      
  80.  
  81. <parameter
  82.  
  83. id= "packageName"  
  84.  
  85. name = "Package name"  
  86.  
  87. type= "string"  
  88.  
  89. constraints= "package"  
  90.  
  91. default = "com.mycompany.myapp" />
  92.  
  93. <! -- 128x128 thumbnails relative to template.xml -->  
  94.  
  95. <thumbs>
  96.  
  97. <! -- default thumbnail is required -->  
  98.  
  99. <thumb>template_blank_activity.png</thumb>
  100.  
  101. </thumbs>
  102.  
  103. <globals file= "globals.xml.ftl" />
  104.  
  105. < execute file= "recipe.xml.ftl" />
  106.  
  107. </template>

When we import the template, AS will pop up a UI interface as shown below, asking us to fill in or select some data, such as entering the name of the Activity, selecting the SDK version, etc. This interface is based on this file.

There is a lot of content, so I will pick out some important ones to reduce the length.

  • template tag
    • name The name of the template when the template is introduced, depending on which template it chooses
    • description The title of the pop-up Dialog, corresponding to area 1
  • Category indicates which category the template belongs to. There will be a category selection when importing
  • parameter Each label corresponds to an input item in the Dialog interface
    • id The *** identifier of this parameter, which is also the value we introduce in .ftl. For example, if the defined id is username, it is $username when referenced.
    • name corresponds to the name of the input item on the Dialog
    • type corresponds to the type of the parameter. Dialog uses this to decide whether the corresponding input is a selection box, input box, drop-down box, etc.
    • constraints The constraints corresponding to the parameter. If there are multiple constraints, they should be separated by |
    • Suggested value. This input part is caused by cascading effect. If you change parameter A, parameter B will also change accordingly. It is determined by this parameter.
    • default The default value of the parameter
    • visibility visibility, to configure a boolean type parameter, generally pointing to another input source
    • help When the focus is on a certain input source, area 3 in the above figure limits the content here

Actual operation

After understanding the template specifications, we will not be so passive when writing templates. Now let's write the template shown at the beginning of the article by ourselves.

First, create the directory structure shown in the following figure under the custom template just mentioned

Then paste the following code into it (just find a random picture to replace it...)

globals.xml.ftl

recipe.xml.ftl

template.xml

  1. <?xml version= "1.0" ?>
  2.  
  3. <template
  4.  
  5. format= "5"  
  6.  
  7. revision= "5"  
  8.  
  9. name = "Debug Activity"  
  10.  
  11. minApi= "7"  
  12.  
  13. minBuildApi= "14"  
  14.  
  15. description= "Create a Debug Activity" >
  16.  
  17. <category value= "Activity" />
  18.  
  19. <formfactor value= "Mobile" />
  20.  
  21. <parameter
  22.  
  23. id= "activityClass"  
  24.  
  25. name = "Activity name"  
  26.  
  27. type= "string"  
  28.  
  29. constraints= "class|unique|nonempty"  
  30.  
  31. default = "SetupActivity"  
  32.  
  33. help = "Create the name of the Activity" />
  34.  
  35. <parameter
  36.  
  37. id= "addExample"  
  38.  
  39. name = "Whether to add a button usage example"  
  40.  
  41. type= "boolean"  
  42.  
  43. default = "false"  
  44.  
  45. help= "When selected, a test button will be automatically generated; otherwise, no test button will be generated" />
  46.  
  47.      
  48.  
  49. <parameter
  50.  
  51. id= "addJumpActivity"  
  52.  
  53. name = "Whether to add a jump to Activity example"  
  54.  
  55. type= "boolean"  
  56.  
  57. default = "false"  
  58.  
  59. help = "When selected, the logic related to jumping to Activity will be automatically generated; otherwise, it will not be generated" />
  60.  
  61. <parameter
  62.  
  63. id= "isLauncher"  
  64.  
  65. name = "Set as startup page"  
  66.  
  67. type= "boolean"  
  68.  
  69. default = "true"  
  70.  
  71. help = "Set this page as the startup page when selected; otherwise, do not set it" />
  72.  
  73.      
  74.  
  75. <parameter
  76.  
  77. id= "packageName"  
  78.  
  79. name = "package name"  
  80.  
  81. type= "string"  
  82.  
  83. constraints= "package"  
  84.  
  85. default = "com.mycompany.myapp"  
  86.  
  87. help= "Enter Application package name" />
  88.  
  89. <! -- 128x128 thumbnails relative to template.xml -->  
  90.  
  91. <thumbs>
  92.  
  93. <! -- default thumbnail is required -->  
  94.  
  95. <thumb>template_debug_activity.png</thumb>
  96.  
  97. </thumbs>
  98.  
  99. <globals file= "globals.xml.ftl" />
  100.  
  101. < execute file= "recipe.xml.ftl" />
  102.  
  103. </template>

AndroidManifest.xml.ftl

DebugActivity.java.ftl

  1. package ${packageName};
  2.  
  3. import android.app.Activity;
  4.  
  5. import android.content.Context;
  6.  
  7. import android.content.Intent;
  8.  
  9. import android.os.Bundle;
  10.  
  11. import android.util.Log;
  12.  
  13. import android. view . View ;
  14.  
  15. import android.widget.Button;
  16.  
  17. import android.widget.LinearLayout;
  18.  
  19. import android.widget.ScrollView;
  20.  
  21. import android.widget.Toast;
  22.  
  23. import java.lang.annotation.ElementType;
  24.  
  25. import java.lang.annotation.Retention;
  26.  
  27. import java.lang.annotation.RetentionPolicy;
  28.  
  29. import java.lang.annotation.Target;
  30.  
  31. import java.lang.reflect.Method;
  32.  
  33. import java.util.ArrayList;
  34.  
  35. import java.util.List;
  36.  
  37. /**
  38.  
  39. * Debug test class, quick debugging of Demo project<hr />
  40.  
  41. * Usage:<br />
  42.  
  43. * 1. Create a subclass to inherit this class<br />
  44.  
  45. * 2. Jump to Activity: Configure the {@link Jump} annotation in the subclass, and then configure the type of the jump activity in the annotation<br />
  46.  
  47. * 3. Click the button to trigger the method: declare a method with a name starting with "_" in the subclass (supports any modifier), and the text of the button generated is the method with the "_" truncated <br />
  48.  
  49. * 4. Method parameters support default parameters and single parameters<br />
  50.  
  51. * 5. If it is a single parameter, the parameter type must be Button or Button's parent class type. When the method is executed, the parameter will be assigned to the Button object<br />
  52.  
  53. * https://github.com/puke3615/DebugActivity<br />
  54.  
  55. * <p>
  56.  
  57. *
  58.  
  59. * @author zijiao
  60.  
  61. * @version 16/10/16
  62.  
  63. */
  64.  
  65. public abstract class DebugActivity extends Activity {
  66.  
  67. protected static final String FIXED_PREFIX = "_" ;
  68.  
  69. private final String TAG = getClass().getName();
  70.  
  71. private final List<ButtonItem> buttonItems = new ArrayList<>();
  72.  
  73. protected LinearLayout linearLayout;
  74.  
  75. protected Context context;
  76.  
  77. @Target(ElementType.TYPE)
  78.  
  79. @Retention(RetentionPolicy.RUNTIME)
  80.  
  81. public @interface Jump {
  82.  
  83. Class<? extends Activity>[] value() default {};
  84.  
  85. }
  86.  
  87. @Override
  88.  
  89. protected void onCreate(Bundle savedInstanceState) {
  90.  
  91. super.onCreate(savedInstanceState);
  92.  
  93. this.context = this;
  94.  
  95. ScrollView scrollView = new ScrollView(this);
  96.  
  97. setContentView(scrollView);
  98.  
  99. this.linearLayout = new LinearLayout(this);
  100.  
  101. this.linearLayout.setOrientation(LinearLayout.VERTICAL);
  102.  
  103. scrollView.addView(linearLayout);
  104.  
  105. try {
  106.  
  107. resolveConfig();
  108.  
  109. createButton();
  110.  
  111. } catch (Throwable e) {
  112.  
  113. error(e.getMessage());
  114.  
  115. }
  116.  
  117. }
  118.  
  119. private void createButton() {
  120.  
  121. for (ButtonItem buttonItem : buttonItems) {
  122.  
  123. linearLayout.addView(buildButton(buttonItem));
  124.  
  125. }
  126.  
  127. }
  128.  
  129. protected View buildButton(final ButtonItem buttonItem) {
  130.  
  131. final Button button = new Button(this);
  132.  
  133. button.setText( buttonItem.name );
  134.  
  135. button.setOnClickListener(new View .OnClickListener() {
  136.  
  137. @Override
  138.  
  139. public void onClick( View v) {
  140.  
  141. if (buttonItem.target != null ) {
  142.  
  143. to (buttonItem.target);
  144.  
  145. } else {
  146.  
  147. Method method = buttonItem.method;
  148.  
  149. method.setAccessible( true );
  150.  
  151. Class<?>[] parameterTypes = method.getParameterTypes();
  152.  
  153. int paramSize = parameterTypes.length;
  154.  
  155. switch (paramSize) {
  156.  
  157. case 0:
  158.  
  159. try {
  160.  
  161. method.invoke(DebugActivity.this);
  162.  
  163. } catch (Throwable e) {
  164.  
  165. e.printStackTrace();
  166.  
  167. error(e.getMessage());
  168.  
  169. }
  170.  
  171. break;
  172.  
  173. case 1:
  174.  
  175. if (parameterTypes[0].isAssignableFrom(Button.class)) {
  176.  
  177. try {
  178.  
  179. method.invoke(DebugActivity.this, button);
  180.  
  181. } catch (Throwable e) {
  182.  
  183. e.printStackTrace();
  184.  
  185. error(e.getMessage());
  186.  
  187. }
  188.  
  189. break;
  190.  
  191. }
  192.  
  193. default :
  194.  
  195. error(method.getName() + "Method parameter configuration error." );
  196.  
  197. break;
  198.  
  199. }
  200.  
  201. }
  202.  
  203. }
  204.  
  205. });
  206.  
  207. return button;
  208.  
  209. }
  210.  
  211. private void resolveConfig() {
  212.  
  213. Class<?> cls = getClass();
  214.  
  215. //Read jump configuration
  216.  
  217. if (cls.isAnnotationPresent(Jump.class)) {
  218.  
  219. Jump annotation = cls.getAnnotation(Jump.class);
  220.  
  221. for (Class<? extends Activity> activityClass : annotation.value()) {
  222.  
  223. buttonItems.add ( buildJumpActivityItem (activityClass));
  224.  
  225. }
  226.  
  227. }
  228.  
  229. //Read method
  230.  
  231. for (Method method : cls.getDeclaredMethods()) {
  232.  
  233. handleMethod(method);
  234.  
  235. }
  236.  
  237. }
  238.  
  239. protected void handleMethod(Method method) {
  240.  
  241. String methodName = method.getName();
  242.  
  243. if (methodName.startsWith(FIXED_PREFIX)) {
  244.  
  245. methodName = methodName.replaceFirst(FIXED_PREFIX, "" );
  246.  
  247. ButtonItem buttonItem = new ButtonItem();
  248.  
  249. buttonItem.method = method;
  250.  
  251. buttonItem. name = methodName;
  252.  
  253. buttonItems.add (buttonItem) ;
  254.  
  255. }
  256.  
  257. }
  258.  
  259. protected ButtonItem buildJumpActivityItem(Class<? extends Activity> activityClass) {
  260.  
  261. ButtonItem buttonItem = new ButtonItem();
  262.  
  263. buttonItem.name = "Jump to" + activityClass.getSimpleName();
  264.  
  265. buttonItem.target = activityClass;
  266.  
  267. return buttonItem;
  268.  
  269. }
  270.  
  271. public void L(Object s) {
  272.  
  273. Log.i(TAG, s + "" );
  274.  
  275. }
  276.  
  277. public void error(String errorMessage) {
  278.  
  279. T( "[error message]\n" + errorMessage);
  280.  
  281. }
  282.  
  283. public void T(Object message) {
  284.  
  285. Toast.makeText(context, String.valueOf(message), Toast.LENGTH_SHORT).show();
  286.  
  287. }
  288.  
  289. public void to (Class<? extends Activity> target) {
  290.  
  291. try {
  292.  
  293. startActivity(new Intent(this, target));
  294.  
  295. } catch (Exception e) {
  296.  
  297. e.printStackTrace();
  298.  
  299. error(e.getMessage());
  300.  
  301. }
  302.  
  303. }
  304.  
  305. public void T(String format, Object... values ) {
  306.  
  307. T(String.format(format, values ​​));
  308.  
  309. }
  310.  
  311. protected static class ButtonItem {
  312.  
  313. public String name ;
  314.  
  315. public Method method;
  316.  
  317. public Class<? extends Activity> target;
  318.  
  319. }
  320.  
  321. }

JumpActivity.java.ftl

SimpleActivity.java.ftl

  1. package ${packageName};
  2.  
  3. @DebugActivity.Jump({
  4.  
  5. <#if addJumpActivity>
  6.  
  7. JumpActivity.class,
  8.  
  9. <# else >
  10.  
  11. </#if>
  12.  
  13. })
  14.  
  15. public class ${activityClass} extends DebugActivity {
  16.  
  17. <#if addExample>
  18.  
  19. private int number = 0;
  20.  
  21. public void _no-parameter method call() {
  22.  
  23. T( "no parameter method call" );
  24.  
  25. }
  26.  
  27. public void _Method call with parameters (Button button) {
  28.  
  29. button.setText( "number is " + number++);
  30.  
  31. }
  32.  
  33. //The code cannot be executed, and a toast prompt will pop up directly to report an error
  34.  
  35. public void _error_parameters_call(String msg) {
  36.  
  37. T( "test" );
  38.  
  39. }
  40.  
  41. //The method name does not start with "_" , the button cannot be created successfully
  42.  
  43. public void void call() {
  44.  
  45. T( "test" );
  46.  
  47. }
  48.  
  49. //The crash will be caught and pop up as a toast
  50.  
  51. public void _CrashTest() {
  52.  
  53. int a = 1 / 0;
  54.  
  55. }
  56.  
  57. </#if>
  58.  
  59. }

OK, the template writing process is over. Next, restart Android Studio, and then click New Project all the way until you reach this interface. This is our customized DebugActivity template.

<<:  UI operations in the main thread are not absolutely safe

>>:  AR/VR/MR, what can Android developers do?

Recommend

Analysis of the private domain operation system of Mint Health App!

Mint Health has been driven by nutritional food p...

Financial APP, how to open source for users

Today, the financial APP market has reached a rel...

GITC 2014: Elites gather to soar to the sky

[[120831]] In recent years, with the support of n...

Why are fruits becoming sweeter and sweeter? Is this unhealthy?

Have you noticed that fruits are getting sweeter ...

Is it always fun to be on vacation? Not necessarily!

The May Day holiday is coming to an end in the bl...

What are the benefits of promoting WeChat mini programs?

1. Mini Programs combined with official accounts ...

How many steps does it take for humans to explore Mars? | Zhao Yuyu

There is no fire on Mars, so why is it called &qu...

How much does it cost to customize an office supplies mini program in Suining?

There is no fixed price for the customization of ...

No flowers to enjoy in May? Learn more about peonies

As the saying goes, "See peonies in the thre...