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. - public void _test() {
-
- T( "Popup Toast" );
-
- }
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 - <?xml version= "1.0" ?>
-
- <template
-
- format= "5"
-
- revision= "5"
-
- name = "Empty Activity"
-
- minApi= "7"
-
- minBuildApi= "14"
-
- description= "Creates a new empty activity" >
-
- <category value= "Activity" />
-
- <formfactor value= "Mobile" />
-
- <parameter
-
- id= "activityClass"
-
- name = "Activity Name"
-
- type= "string"
-
- constraints= "class|unique|nonempty"
-
- suggest= "${layoutToActivity(layoutName)}"
-
- default = "MainActivity"
-
- help= "The name of the activity class to create" />
-
- <parameter
-
- id= "generateLayout"
-
- name = "Generate Layout File"
-
- type= "boolean"
-
- default = "true"
-
- help= "If true, a layout file will be generated" />
-
- <parameter
-
- id= "layoutName"
-
- name = "Layout Name"
-
- type= "string"
-
- constraints= "layout|unique|nonempty"
-
- suggest= "${activityToLayout(activityClass)}"
-
- default = "activity_main"
-
- visibility= "generateLayout"
-
- help= "The name of the layout to create for the activity" />
-
- <parameter
-
- id= "isLauncher"
-
- name = "Launcher Activity"
-
- type= "boolean"
-
- default = "false"
-
- help= "If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" />
-
-
-
- <parameter
-
- id= "packageName"
-
- name = "Package name"
-
- type= "string"
-
- constraints= "package"
-
- default = "com.mycompany.myapp" />
-
- <!
-
- <thumbs>
-
- <!
-
- <thumb>template_blank_activity.png</thumb>
-
- </thumbs>
-
- <globals file= "globals.xml.ftl" />
-
- < execute file= "recipe.xml.ftl" />
-
- </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 - <?xml version= "1.0" ?>
-
- <template
-
- format= "5"
-
- revision= "5"
-
- name = "Debug Activity"
-
- minApi= "7"
-
- minBuildApi= "14"
-
- description= "Create a Debug Activity" >
-
- <category value= "Activity" />
-
- <formfactor value= "Mobile" />
-
- <parameter
-
- id= "activityClass"
-
- name = "Activity name"
-
- type= "string"
-
- constraints= "class|unique|nonempty"
-
- default = "SetupActivity"
-
- help = "Create the name of the Activity" />
-
- <parameter
-
- id= "addExample"
-
- name = "Whether to add a button usage example"
-
- type= "boolean"
-
- default = "false"
-
- help= "When selected, a test button will be automatically generated; otherwise, no test button will be generated" />
-
-
-
- <parameter
-
- id= "addJumpActivity"
-
- name = "Whether to add a jump to Activity example"
-
- type= "boolean"
-
- default = "false"
-
- help = "When selected, the logic related to jumping to Activity will be automatically generated; otherwise, it will not be generated" />
-
- <parameter
-
- id= "isLauncher"
-
- name = "Set as startup page"
-
- type= "boolean"
-
- default = "true"
-
- help = "Set this page as the startup page when selected; otherwise, do not set it" />
-
-
-
- <parameter
-
- id= "packageName"
-
- name = "package name"
-
- type= "string"
-
- constraints= "package"
-
- default = "com.mycompany.myapp"
-
- help= "Enter Application package name" />
-
- <!
-
- <thumbs>
-
- <!
-
- <thumb>template_debug_activity.png</thumb>
-
- </thumbs>
-
- <globals file= "globals.xml.ftl" />
-
- < execute file= "recipe.xml.ftl" />
-
- </template>
AndroidManifest.xml.ftl DebugActivity.java.ftl - package ${packageName};
-
- import android.app.Activity;
-
- import android.content.Context;
-
- import android.content.Intent;
-
- import android.os.Bundle;
-
- import android.util.Log;
-
- import android. view . View ;
-
- import android.widget.Button;
-
- import android.widget.LinearLayout;
-
- import android.widget.ScrollView;
-
- import android.widget.Toast;
-
- import java.lang.annotation.ElementType;
-
- import java.lang.annotation.Retention;
-
- import java.lang.annotation.RetentionPolicy;
-
- import java.lang.annotation.Target;
-
- import java.lang.reflect.Method;
-
- import java.util.ArrayList;
-
- import java.util.List;
-
- /**
-
- * Debug test class, quick debugging of Demo project<hr />
-
- * Usage:<br />
-
- * 1. Create a subclass to inherit this class<br />
-
- * 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 />
-
- * 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 />
-
- * 4. Method parameters support default parameters and single parameters<br />
-
- * 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 />
-
- * https://github.com/puke3615/DebugActivity<br />
-
- * <p>
-
- *
-
- * @author zijiao
-
- * @version 16/10/16
-
- */
-
- public abstract class DebugActivity extends Activity {
-
- protected static final String FIXED_PREFIX = "_" ;
-
- private final String TAG = getClass().getName();
-
- private final List<ButtonItem> buttonItems = new ArrayList<>();
-
- protected LinearLayout linearLayout;
-
- protected Context context;
-
- @Target(ElementType.TYPE)
-
- @Retention(RetentionPolicy.RUNTIME)
-
- public @interface Jump {
-
- Class<? extends Activity>[] value() default {};
-
- }
-
- @Override
-
- protected void onCreate(Bundle savedInstanceState) {
-
- super.onCreate(savedInstanceState);
-
- this.context = this;
-
- ScrollView scrollView = new ScrollView(this);
-
- setContentView(scrollView);
-
- this.linearLayout = new LinearLayout(this);
-
- this.linearLayout.setOrientation(LinearLayout.VERTICAL);
-
- scrollView.addView(linearLayout);
-
- try {
-
- resolveConfig();
-
- createButton();
-
- } catch (Throwable e) {
-
- error(e.getMessage());
-
- }
-
- }
-
- private void createButton() {
-
- for (ButtonItem buttonItem : buttonItems) {
-
- linearLayout.addView(buildButton(buttonItem));
-
- }
-
- }
-
- protected View buildButton(final ButtonItem buttonItem) {
-
- final Button button = new Button(this);
-
- button.setText( buttonItem.name );
-
- button.setOnClickListener(new View .OnClickListener() {
-
- @Override
-
- public void onClick( View v) {
-
- if (buttonItem.target != null ) {
-
- to (buttonItem.target);
-
- } else {
-
- Method method = buttonItem.method;
-
- method.setAccessible( true );
-
- Class<?>[] parameterTypes = method.getParameterTypes();
-
- int paramSize = parameterTypes.length;
-
- switch (paramSize) {
-
- case 0:
-
- try {
-
- method.invoke(DebugActivity.this);
-
- } catch (Throwable e) {
-
- e.printStackTrace();
-
- error(e.getMessage());
-
- }
-
- break;
-
- case 1:
-
- if (parameterTypes[0].isAssignableFrom(Button.class)) {
-
- try {
-
- method.invoke(DebugActivity.this, button);
-
- } catch (Throwable e) {
-
- e.printStackTrace();
-
- error(e.getMessage());
-
- }
-
- break;
-
- }
-
- default :
-
- error(method.getName() + "Method parameter configuration error." );
-
- break;
-
- }
-
- }
-
- }
-
- });
-
- return button;
-
- }
-
- private void resolveConfig() {
-
- Class<?> cls = getClass();
-
- //Read jump configuration
-
- if (cls.isAnnotationPresent(Jump.class)) {
-
- Jump annotation = cls.getAnnotation(Jump.class);
-
- for (Class<? extends Activity> activityClass : annotation.value()) {
-
- buttonItems.add ( buildJumpActivityItem (activityClass));
-
- }
-
- }
-
- //Read method
-
- for (Method method : cls.getDeclaredMethods()) {
-
- handleMethod(method);
-
- }
-
- }
-
- protected void handleMethod(Method method) {
-
- String methodName = method.getName();
-
- if (methodName.startsWith(FIXED_PREFIX)) {
-
- methodName = methodName.replaceFirst(FIXED_PREFIX, "" );
-
- ButtonItem buttonItem = new ButtonItem();
-
- buttonItem.method = method;
-
- buttonItem. name = methodName;
-
- buttonItems.add (buttonItem) ;
-
- }
-
- }
-
- protected ButtonItem buildJumpActivityItem(Class<? extends Activity> activityClass) {
-
- ButtonItem buttonItem = new ButtonItem();
-
- buttonItem.name = "Jump to" + activityClass.getSimpleName();
-
- buttonItem.target = activityClass;
-
- return buttonItem;
-
- }
-
- public void L(Object s) {
-
- Log.i(TAG, s + "" );
-
- }
-
- public void error(String errorMessage) {
-
- T( "[error message]\n" + errorMessage);
-
- }
-
- public void T(Object message) {
-
- Toast.makeText(context, String.valueOf(message), Toast.LENGTH_SHORT).show();
-
- }
-
- public void to (Class<? extends Activity> target) {
-
- try {
-
- startActivity(new Intent(this, target));
-
- } catch (Exception e) {
-
- e.printStackTrace();
-
- error(e.getMessage());
-
- }
-
- }
-
- public void T(String format, Object... values ) {
-
- T(String.format(format, values ));
-
- }
-
- protected static class ButtonItem {
-
- public String name ;
-
- public Method method;
-
- public Class<? extends Activity> target;
-
- }
-
- }
JumpActivity.java.ftl SimpleActivity.java.ftl - package ${packageName};
-
- @DebugActivity.Jump({
-
- <#if addJumpActivity>
-
- JumpActivity.class,
-
- <# else >
-
- </#if>
-
- })
-
- public class ${activityClass} extends DebugActivity {
-
- <#if addExample>
-
- private int number = 0;
-
- public void _no-parameter method call() {
-
- T( "no parameter method call" );
-
- }
-
- public void _Method call with parameters (Button button) {
-
- button.setText( "number is " + number++);
-
- }
-
- //The code cannot be executed, and a toast prompt will pop up directly to report an error
-
- public void _error_parameters_call(String msg) {
-
- T( "test" );
-
- }
-
- //The method name does not start with "_" , the button cannot be created successfully
-
- public void void call() {
-
- T( "test" );
-
- }
-
- //The crash will be caught and pop up as a toast
-
- public void _CrashTest() {
-
- int a = 1 / 0;
-
- }
-
- </#if>
-
- }
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. |