A plug-in for quick generation of Android shape -- NoCodeShape

Keywords: Android xml Java Programming

I. Introduction to NoCodeShape

NoCodeShape is an Android Studio plug-in that can generate shape.xml in Android through visual interface operation. The novice can better accept the corresponding attributes in Android, and the senior programmer can simplify the operation and generate shape.xml quickly.

II. Usage of NoCodeShape

1. Download and install

As with the general Android Studio plug-in download, you can directly choose preferences - > plugins to search the NoCodeShape search results, and then directly install and restart it.

You can also go to the jetbrains plug-in management website to download various versions of plug-ins
https://plugins.jetbrains.com/plugin/13325-nocodeshape/versions
Then Preferences - > plugins and Install plugins from disk

2. How to use

After creating a new shape.xml file, right-click NoCodeShape or press Common+U directly

Then select the attributes you want, and the relevant XML code will be generated along with the click event, and the corresponding shape shape will be displayed on the right side of Android Studio. If the corresponding shape.xml has related attributes, NoCodeShape will also generate an operation interface corresponding to the shape.xml attribute, which is very convenient.

Example:

III. implementation principle

Generally speaking, the implementation principle is not complicated, mainly because the interface related operation logic is relatively cumbersome.

For a newly generated shape.xml, only a new operation interface needs to pop up, and the user only needs to click the attributes of the corresponding module. The project uses the singleton pattern + Builder pattern to manage the various Shape attributes, and generates the singleton of Shape, Solid, Corners, Stroke and Gradient respectively. Inside the singleton, there is a Builder to undertake various types of specific attributes.

Through the operation of the interface, the internal Builder is filled with data. Finally, after completing all kinds of operations, all the attributes in the Builder of each type are extracted and a complete xml string is generated and pasted to the operation interface of Android Studio.

The implementation is mainly divided into two categories:

1. Splicing to generate xml string

String splicing is the most complex part. First, each shape type has more data, and some of its attributes have logical existence. Second, there are more processing formats when generating the final Android Studio xml string. All kinds of properties are inherited from BaseXml. There is a static internal Builder class in it. Take a relatively simple Solid class as an example, as shown below:

public class Solid extends BaseXml {

    private static Builder builder;
    private static Solid instance = null;

    public static Solid getInstance() {
        if (instance == null) {
            builder = new Builder();
            instance = new Solid();
        }
        return instance;
    }
    
 public static class Builder extends BaseBuilder {
        String color;
        String colorValue;

        public void setColor(String color) {
            this.colorValue = color;
            this.color = getAttrWithOutUnitStr("color", color);
        }
        @Override
        public String getBuilderString() {
            return StringUtils.getString(color);
        }

        @Override
        public void clearData() {
            StringUtils.clearObjectData(this);
        }

        @Override
        public void analysisAttribute(Attributes attributes) {
            Solid.getInstance().setChecked(true);
            setColor(attributes.getValue("android:color"));
        }
    }

Its class inherits from the abstract class BaseXml, and the code is as follows:

public abstract class BaseXml {
    private boolean isChecked = false;
    public String getCloser() {
        return " />";
    }
    public String getStartTag() { 
        return "";
    }
    public String generateXmlString() {
        return "";
    }
    protected String getLineFeedString() { 
        return "\n";
    }
    public boolean isChecked() {
        return isChecked;
    }
    public BaseXml setChecked(boolean checked) {
        isChecked = checked;
        return this;
    }
}

Abstract classes extract the basic operations commonly used in string splicing, such as returning the start tag of "< solid" and the end tag of "/ >"

Its internal BaseXml has the common operations of all the properties owned by the corresponding class. It inherits from the abstract class BaseBuilder. The code is as follows

public abstract class BaseBuilder {
    public abstract String getBuilderString();
    public abstract void clearData();
    public abstract void analysisAttribute(Attributes attributes);
    protected final String getAttrWithUnitStr(String attributeType, String value) {
        String unit;
        if (TextUtils.isEmpty(value)) {
            return "";
        }
        if (value.contains("px") || value.contains("dp")) {
            unit = "";
        } else {
            unit = DefaultData.UNIT;
        }
        return "android:" + attributeType + "=\"" + value + unit + "\"";
    }
    protected final String getAttrWithOutUnitStr(String attributeType, String value) {
        if (TextUtils.isEmpty(value)) {
            return "";
        }
        return "android:" + attributeType + "=\"" + value + "\"";
    }
    protected final String getValueOutUnit(String value) {
        if (TextUtils.isEmpty(value)) {
            return value;
        }
        return value.replace("dp", "").replace("px", "");
    }
}

BaseBuilder encapsulates some common operations of attributes, such as generating strings such as: Android: color = "ා񖓿ාffff", obtaining strings with units, etc.
Three abstract methods are provided:

   public abstract String getBuilderString(); //Get string spliced by all properties in Builder
   public abstract void clearData(); //Clear Builder internal property values
   public abstract void analysisAttribute(Attributes attributes); //Analyze the values in the XML data, which will be mentioned in the second big point "convert the original shape.xml string to the corresponding operation interface"

The above is the construction of basic data. refreshAndWriteData of CommonAction class will be called at the end of control interaction:

abstract class CommonAction {
    JComponent component;
    NoShapeDialog noShapeDialog;

    void refreshAndWriteData() {
        NoCodeShapeAction.callWriteData();
    }
}

Finally, the writeData() method in the basic Action will be called, and its specific logic is

 /**
     * Write data to xml file
     */
    private static void writeData() {
        final Document document = FileDocumentManager.getInstance().getDocument(file);
        if (document == null) {
            try {
                throw new Exception("Document The object is empty.");
            } catch (Exception e) {
                e.printStackTrace();
            }
            return;
        }
        new WriteCommandAction.Simple(project) {
            @Override
            protected void run() {
                document.setText(XMLString.getInstance().generateXmlString());
                //formatCode();
            }
        }.execute();
    }

Using XMLString.getInstance().generateXmlString() to get all the attributes of each operation type, splice it into a complete field of shape.xml file, and finally call the relevant commands of the plug-in system to paste the string into the corresponding input box of the system.

2. Convert the original shape.xml string to the corresponding operation interface

The above has described how the class splices and pastes the generated XML string to the Android Studio interface. In addition, NoCodeShape supports not only the newly generated shape.xml, but also the ability to modify the old shape.xml. Compared with the first step, it mainly involves the process of reading Android Studio xml document and converting it into corresponding operation interface. It mainly implements the following methods before initializing the operation interface:

 private void initSax() {
        String text = FileDocumentManager.getInstance().getDocument(file).getText();
        ShapeSaxHandler handler = new ShapeSaxHandler();
        try {
            handler.createViewList(text);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

Use ShapeSaxHandler to parse the elements in xml. Refer to FindViewByMe The analytic principle of.

The specific operation logic is as follows:

  public void createViewList(String string) throws ParserConfigurationException, SAXException, IOException {
        InputStream xmlStream = new ByteArrayInputStream(string.getBytes("UTF-8"));
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser parser = factory.newSAXParser();
        parser.parse(xmlStream, this);
    }
    @Override
    public void startDocument() throws SAXException {
        if (shapePartList == null) {
            shapePartList = new ArrayList<ShapePart>();
        }
    }
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        switch (qName) {
            case "shape":
                Shape.getInstance().getBuilder().analysisAttribute(attributes);
                break;
            case "stroke":
                Stroke.getInstance().getBuilder().analysisAttribute(attributes);
                break;
            case "solid":
               Solid.getInstance().getBuilder().analysisAttribute(attributes);
                break;
            case "gradient":
                Gradient.getInstance().getBuilder().analysisAttribute(attributes);
                break;
            case "corners":
                Corners.getInstance().getBuilder().analysisAttribute(attributes);
                break;
            default:
                break;
        }

The logic is very clear. It mainly judges startTag, and then makes an assignment to the corresponding type of Buidler by calling the method of public abstract void analysisattribute.

Take Stroke for example:

@Override
        public void analysisAttribute(Attributes attributes) {
            Stroke.getInstance().setChecked(true);
            setColor(attributes.getValue("android:color"));
            setDashGap(attributes.getValue("android:dashGap"));
            setWidth(attributes.getValue("android:width"));
            setDashWidth(attributes.getValue("android:dashWidth"));
        }

The main task is to get the properties and perform some operations (such as selecting or assigning) on the initialized interface.

Four, summary

This plug-in is the first time that I have made a relatively practical plug-in. It is all written by using idle events of work. It has been nearly a month before and after. It has gained a lot, but it has stepped on various kinds of pits. In the development process, due to the lack of relevant documents, it's still a little snack power to read the official documents, but step by step, I have also grown up. Among them, the pit has also been leveled, but due to the lack of some basic technologies, such as not familiar with Java GUI interface programming, leading to a large period of time in the development process with the interface against, so later have the opportunity to learn more about Java interface programming, and strive to make page interaction better.

In addition, due to its own development energy, the plug-in cannot be perfect. At present, there are several problems in the plug-in:

1. The corresponding logic in Gradient needs to be further optimized

2. Size and padding are not supported yet (they are used less in consideration of their own environment, so they are not supported yet)

3. The local color string processing is not supported after the color selector is opened by default

4. There are a large number of interface operation logic codes, which need to be optimized

Finally, I hope that you can put forward relevant opinions or suggestions in the use process, and also welcome to join in the development together, so as to make the plug-in more perfect.

Project address:

https://github.com/VomPom/NoCodeShape

Posted by kfir91 on Wed, 27 Nov 2019 01:16:23 -0800