Preamble
Eclipse and Idea have their own visual editing tools for Android application markup. NetBeans is deprived of this happiness. The desire to create something similar for the usual NetBeans using relatively simple means led to the idea of ​​transferring the visual editing process to the application itself. There are several reasons for this:
- natural preview markup by means of Android itself;
- the ability to work autonomously without a desktop IDE (it may be interesting, first of all, to designers);
- "God - gods, and Caesar - Caesar." When implementing it is convenient to use already existing data structures of View-objects, class constants, etc ...
How does it work?
Mirror tree markup. The main idea - parallel to the tree of markup objects (View), which Android generates when loading a Layout, the program creates a mirror tree of its own objects (the classes with the same name prefixed with Z - Zview, ZButton, ...). Each of them has a link to the object - the original. Classes have inheritance, similar to View, and support all the functionality associated with traversing the tree, for example, when generating xml-markup files, creating lists of View parameters. In addition, the mirror tree is the main data structure for describing an open Layout: each object contains two important parameters — a vector of markup parameters for View and a mirror descriptor object for LayoutParams.
View options. The original variety of the View parameters is supported by a group of classes derived from ZParam, each of them implements all the specifics of the external and internal representation of the parameters of this type, converting, invoking its edit dialog, determining whether the values ​​correspond to the compiled file. Currently implemented types are int, hex (hexadecimal int), boolean, float, string, charSequence, dimen (dimension), id (resource identifier), image, color, enumInt (list of named integer constants), enumString, enumClass (list of named integer classes). In the latter case, a derived class is created for each enum class.
The parameter vector for each View is deserialized from the corresponding xml file located in the assets. To simplify the process of creating such files, the application has a component that, for all View, searches for getters / setters and creates a description of the proposed parameters for them. Then these files are edited manually according to the documentation.
LayoutParams markup options. There are no similar description files for markup parameters. Instead, for each View, LayoutParams is read (its type depends on the type of ancestor), then reflection uses the descriptor class of the same name - ZLP and its derivatives; each class creates a vector of parameters and writes values ​​read from the corresponding LayoutParams into it.
')
Compiled Layout XML file. In the Android application (apk) file, the Layout XML descriptors are in compiled form. A detailed format description is available at
justanapplication.wordpress.com/category/android/android-binary-xml . The undoubted advantages of the format are a limited number of internal data types for parameters, sufficient simplicity to parse it with homegrown tools (for example, to view a dump or extract parameters to a mirror markup tree). In addition, the internal Android parser is the android.content class. res.XmlBlock is able to create a View from a byte array containing data in a similar format.
Remark In the apk-file itself compiled files are contained in a compressed form. However, in our case it does not matter, because the XmlBlock parser gets them unzipped.
How it works?
When you start the application, using reflection, it polls the Android classes for collecting the necessary constants and forms the tables in the form of pairs <name-value>:
- resource identifiers from R.java (separately for different types - subclasses);
- class constants View;
- android.R.attr - internal constant names for all parameters;
- android.R.styleable - character symbols of the constants used in the parameters.
For all types of View from xml-files placed in assets, the vectors of object descriptors of parameters (classes ZParam) are deserialized.
Editing in general terms happens this way. The original unedited Layout is loaded via the resource id with the standard LayoutInflater. According to the loaded View, a mirror tree of objects is created; for each View, the original parameter vector is copied. Then its own CXmlResourceParser, built on the basis of the standard android.content.res.XmlResourceParser, reads the parameters of all the View and writes them into ZParam objects. Parameter objects whose values ​​have been read from the markup file are marked, so that they will always be highlighted in the general list of parameters. Event handlers of all View are configured on the program code of the editing dialog.
When changing any parameters, the own CXmlParser writes a byte array in the format of a compiled xml file, from which the XmlBlock parser creates a modified View, thus changing the Preview image. The same thing CXmlParser does when saving changes to a binary file for a long time. When loading a modified Layout, the same scheme works, only with a different pair of parsers: internal android.content. res.XmlBlock for loading View and own CXmlParser - for reading parameters.
For external use, the markup can be exported to a regular xml file. For this, we use our own tools of the ZView and ZParam class groups.
Placement of data . All data is located in the GUIWizard directory on the SD card. The application creates a directory named its own package. The generated xml files of the edited markup are written there with the names of the Layouts. Auxiliary binary files of compiled markup with the cxml extension are written to the data subdirectory. The generated parameter files for the View are written to the project directory with the names of their own classes (for example, android.widget.ImageView.xml).
Remark Initially, it was assumed that when editing, the changed parameters will be immediately recorded by the setters in the corresponding View, and if they are not present, they will be written directly into the private View fields. Similarly, to get the current field values, it was supposed to call getters. However, later it turned out that there are quite a few exceptions in the uniqueness of the “parameter-field-setter-getter”. For example, several setters may correspond to one parameter; one setter may take several parameters. Therefore, it was decided to completely abandon the interaction of objects ZView - View. Instead, after each parameter change, a compiled xml markup file is generated and written to a byte stream (i.e., it is not a file in the direct sense of the word). Then the XmlBlock parser creates a modified View for it.
Reflection, as without it?
The application makes extensive use of reflection, including for:
- reading the constants of the names of Android parameters, application resources and constants of the View classes;
- generating objects of the Zview, Zparam and Zlayout class groups depending on the class name of the current object;
- bypassing access restrictions in some Android classes.
Integration with NetBeans
A module embedded in NetBeans does a trivial job. When you click an additional button, it calls the contents of the SDCard / GUIWizard / <Android application package name> directory through ADB calls. The package name is extracted from the manifest file of the current project selected in the project window. All files with the .xml extension are copied to / res / layout and deleted. Old versions are renamed with the .orig extension. Sources of the module -
bitbucket.org/solus_rex/netbeans_guiwizard_pluginStatus quo
Project sources are available on
bitbucket.org/solus_rex/android_guiwizardTo debug parsing a compiled markup file, you can use the binary files contained in the application itself. To do this, you need to unzip the apk-file and copy the xml-files from res / layout to the GUIWizard / <application package> / data subdirectory (recall that they are contained there in compiled binary format). In the layout, you can perform byte-by-byte comparison of the source binary markup file and the file generated by the program when their dimensions match. To do this, the application, when writing the output file, sorts the table of names and parameter lists in the order in which they were in the source file.
The purpose of the current stage was to test the main ideas and implement them on the layout. Layout allows you to:
- view lists of constants;
- read your own data logs and exception stacks;
- generate files for describing the View parameters;
- create new empty layouts;
- view the Layout list (in all the lists, a short click executes the main command, a long one calls the context menu of the commands).
A short click on the Layout list brings up its Preview, and the last saved in a binary file opens, and if it is not found, the resource in the application itself.
A long click brings up a menu of commands executed above the Layout. In Preview, a short click on any element causes a list of its parameters, a long one - a message with the generated xml-tag. In the command menu, the “View List” item displays a list with the names and types of the View, formatted by nesting. A short click on this list brings up the already mentioned list of parameters, a long one brings up a context menu in which there is a command to add a new View as a descendant to the selected one.
A short click in the parameter list brings up a dialog corresponding to its type, a long one - a list of resources that can be assigned to it.
The question of adding new resources remains open - they must be protected in the original project.