New Android Container View

Ashera allow new container widgets to be created using a technique called code poaching. Code poaching is a technique where you poach cross platform code from the Android framework so that it can be used in other cross platforms.

Code Poaching - Introduction

Android framework is based on java. However the layouts in Android cannot be used in a java platform such as SWT, iOS based on j2objc or browser based on teavm. SWT, teavm and j2objc understand java code.

The only way to use Android layouts in java is to factor out the onMeasure and onlayout methods in Android and stub the other method calls such that a call to these methods do not fail. By doing this, we can reuse the layouting mechanism of Android in iOS, SWT and browser. This is the idea with gave birth to the Ashera project.

Lets us take the example of AbsoluteLayout in Android. This is deprecated but taken as case study to show how a cross platform container view can be developed in Ashera.

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.inspector.InspectableProperty;
import android.widget.RemoteViews.RemoteView;
/**
 * A layout that lets you specify exact locations (x/y coordinates) of its
 * children. Absolute layouts are less flexible and harder to maintain than
 * other types of layouts without absolute positioning.
 *
 * 

XML attributes

See {@link * android.R.styleable#ViewGroup ViewGroup Attributes}, {@link * android.R.styleable#View View Attributes}

* * @deprecated Use {@link android.widget.FrameLayout}, {@link android.widget.RelativeLayout} * or a custom layout instead. */ @Deprecated @RemoteView public class AbsoluteLayout extends ViewGroup { public AbsoluteLayout(Context context) { this(context, null); } public AbsoluteLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public AbsoluteLayout(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public AbsoluteLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); int maxHeight = 0; int maxWidth = 0; // Find out how big everyone wants to be measureChildren(widthMeasureSpec, heightMeasureSpec); // Find rightmost and bottom-most child for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() != GONE) { int childRight; int childBottom; AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) child.getLayoutParams(); childRight = lp.x + child.getMeasuredWidth(); childBottom = lp.y + child.getMeasuredHeight(); maxWidth = Math.max(maxWidth, childRight); maxHeight = Math.max(maxHeight, childBottom); } } // Account for padding too maxWidth += mPaddingLeft + mPaddingRight; maxHeight += mPaddingTop + mPaddingBottom; // Check against minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0), resolveSizeAndState(maxHeight, heightMeasureSpec, 0)); } /** * Returns a set of layout parameters with a width of * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} * and with the coordinates (0, 0). */ @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() != GONE) { AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) child.getLayoutParams(); int childLeft = mPaddingLeft + lp.x; int childTop = mPaddingTop + lp.y; child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); } } } @Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new AbsoluteLayout.LayoutParams(getContext(), attrs); } // Override to allow type-checking of LayoutParams. @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof AbsoluteLayout.LayoutParams; } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } @Override public boolean shouldDelayChildPressedState() { return false; } /** * Per-child layout information associated with AbsoluteLayout. * See * {@link android.R.styleable#AbsoluteLayout_Layout Absolute Layout Attributes} * for a list of all child view attributes that this class supports. */ public static class LayoutParams extends ViewGroup.LayoutParams { /** * The horizontal, or X, location of the child within the view group. */ @InspectableProperty(name = "layout_x") public int x; /** * The vertical, or Y, location of the child within the view group. */ @InspectableProperty(name = "layout_y") public int y; /** * Creates a new set of layout parameters with the specified width, * height and location. * * @param width the width, either {@link #MATCH_PARENT}, {@link #WRAP_CONTENT} or a fixed size in pixels * @param height the height, either {@link #MATCH_PARENT}, {@link #WRAP_CONTENT} or a fixed size in pixels * @param x the X location of the child * @param y the Y location of the child */ public LayoutParams(int width, int height, int x, int y) { super(width, height); this.x = x; this.y = y; } /** * Creates a new set of layout parameters. The values are extracted from * the supplied attributes set and context. The XML attributes mapped * to this set of layout parameters are: * *
    *
  • layout_x: the X location of the child
  • *
  • layout_y: the Y location of the child
  • *
  • All the XML attributes from * {@link android.view.ViewGroup.LayoutParams}
  • *
* * @param c the application environment * @param attrs the set of attributes from which to extract the layout * parameters values */ public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AbsoluteLayout_Layout); x = a.getDimensionPixelOffset( com.android.internal.R.styleable.AbsoluteLayout_Layout_layout_x, 0); y = a.getDimensionPixelOffset( com.android.internal.R.styleable.AbsoluteLayout_Layout_layout_y, 0); a.recycle(); } /** * {@inheritDoc} */ public LayoutParams(ViewGroup.LayoutParams source) { super(source); } @Override public String debug(String output) { return output + "Absolute.LayoutParams={width=" + sizeToString(width) + ", height=" + sizeToString(height) + " x=" + x + " y=" + y + "}"; } } }

In the above code, onLayout and onMeasure methods needs to be factored out and bundled into other platforms such as SWT, iOS and browser.

Code poacher - Understand

Code Poacher is a set of tools provided by Ashera to develop cross platform plugin. It automates the creation of plugin by providing the following:

  • Template for the plugin
  • Code poacher to migrate an Android library to Java
  • Generate plugin.xml
  • Generate widget.xml which will aid in creation of attributes for a widget

The folder structure of Code Poacher project is given below:

                                CodePoacher
├──  src/main/java/com/ashera/codepoacher/
│   ├── CodePoacher.java
│   ├── PluginXmlGenerator.java
│   ├── WidgetGenerator.java
│   └── WidgetXmlGenerator.java
├── configimpl/
│   └── ...
├── plugin/
│   └── ...	
├── resources/	
│   └── ...
└──	templates/
     └── ...

The folder structure of absolute layout - codepoacher folder is given below:
codepoacher/
├── codegen/
│   └── WidgetConfig.xml
├── config/
│   ├── absolutelayout.code
│   ├── absolutelayout.config
│   ├── config-absolutelayout.xml
│   ├── copyfiles.properties
│   └── files.properties
└──	 config.properties

The following table lists the function of each file:

# Name Description
1 CodePoacher.java Tool to migrate android source file to java by factoring out onMeasure and onLayout methods.
2 WidgetXmlGenerator.java Tool to generate xml file which will be fed to Widgetgenerator.java.
3 Widgetgenerator.java Tool which takes list of xml files in config director and generates java/ts/xml files.
4 PluginXmlGenerator.java File to create/update plugin.xml and package.json.
5 WidgetConfig.xml File which contain the source from which widget attributes information can be extracted into an intermediate xml file.
6 files.properties The list of files containing android source in the format <id>= <url>
7 absolutelayout.config Property file containing methods to be extracted from Android source.
8 absolutelayout.code Extra code which needs to be added to final java file.
9 templates Templates for the widget which is generated i.e. choose from one of these BaseCompositeTemplate.java, BaseHasWidgetsTemplate.java, BaseHasWidgetStubTemplate.java, BaseWidgetStubTemplate.java, BaseWidgetTemplate.java
10 config.properties Configuration information for the code poacher tool.

Absolute Layout

The following are steps involved to create the AbsoluteLayout plugin:

1 - Download

Download codepoacher from the github link to a suitable location. e.g. D:/Java/github_ashera/code-poacher

2 - Create config.properties at path e.g D:/Java/github_ashera/AbsoluteLayout/codepoacher

config.properties for AbsoluteLayout given below:

name=AbsoluteLayout
uniqueid=absolutelayout
packageprefix=com.ashera
packageprefixpath=com/ashera
iospackageprefix=AS

3 - Create plugin project

Create plugin by running ant build at plugin/build.xml with vm arguments -DprojectBaseDir=D:/Java/github_ashera/AbsoluteLayout. The plugin project will be created at <baseDir>/<name>. i.e. D:/Java/github_ashera/AbsoluteLayout

4 - Create AndroidJ file

Prepare files.properties, copy.properties and absolutelayout.config inside config directory at path e.g D:/Java/github_ashera/AbsoluteLayout/codepoacher as below:

files.properties for AbsoluteLayout given below:

absolutelayout=https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/AbsoluteLayout.java?format=TEXT
absolutelayout.config given below:
copyto=D:/Java/github_ashera/AbsoluteLayout/swt/src/main/java/r/android/widget/AbsoluteLayout.java

absolutelayout.include.methods=onMeasure,onLayout,checkLayoutParams,generateDefaultLayoutParams
absolutelayout.exclude.methods=
absolutelayout.include.fields=type:int,type:boolean
absolutelayout.exclude.fields=
absolutelayout.include.classes=AbsoluteLayout,LayoutParams
absolutelayout.exclude.classes=
absolutelayout.include.variable=
absolutelayout.exlude.variable=
absolutelayout.include.imports=android.view.ViewGroup,android.view.View
absolutelayout.exclude.imports=

layoutparams.include.methods=LayoutParams-int-int-int-int,LayoutParams-ViewGroup.LayoutParams
layoutparams.exclude.methods=
copy.properties for AbsoluteLayout given below:
copy.files.0=../AbsoluteLayout/swt/src/main/java/r/android/widget/AbsoluteLayout.java:../AbsoluteLayout/ios/src/main/java/r/android/widget/AbsoluteLayout.java
copy.files.1=../AbsoluteLayout/swt/src/main/java/r/android/widget/AbsoluteLayout.java:../AbsoluteLayout/browser/src/main/java/r/android/widget/AbsoluteLayout.java

Run CodePoacher.java with vm arguments -DbaseDir=D:/Java/github_ashera/AbsoluteLayout. This will take the above configuration files and generate AbsoluteLayout.java in swt, ios and browser platform folder.

5 - Create Widget file

The next step is to generate the Ashera widget wrapper for the android widget.

Prepare WidgetConfig.xml as below:

<WidgetConfigs>
    <WidgetConfig name="AbsoluteLayout">
        <Source type="androiddocurl" url="https://developer.android.com/reference/android/widget/AbsoluteLayout" id="1"></Source>
		<Source type="javasource" url="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/AbsoluteLayout.java?format=TEXT" id="1"></Source>
		<Source type="attrxml" url="https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/attrs.xml?format=TEXT" id="1"></Source>
    </WidgetConfig>
</WidgetConfigs>
Run WidgetXmlGenerator.java with vm arguments -DbaseDir=D:/Java/github_ashera/AbsoluteLayout. This will generate a xml file inside config directory. The xml file contains basic information. The xml file needs to be modified. The final xml file is given below:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Widgets>
    <Widget    
        template="BaseHasWidgetsTemplate.java" name="AbsoluteLayout" classname="AbsoluteLayoutImpl" os="android" prefix="" nativeclassname="android.widget.AbsoluteLayout" jstemplate="widgettemplate.js" createDefault="asnativewidget|nativecreate|" group="AbsoluteLayout" packageName="android.widget" generateXmlTestCase="true">
        <CustomAttribute generatorUrl="https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/attrs.xml?format=TEXT" inherited="true" name="layout_x" type="dimension" code="code:layoutParams.x=(int)objValue;" javaType="int" getterCode="code:layoutParams.x"/>
        <CustomAttribute generatorUrl="https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/attrs.xml?format=TEXT" inherited="true" name="layout_y" type="dimension" code="code:layoutParams.y=(int)objValue;" javaType="int" getterCode="code:layoutParams.y"/>
        <ReplaceString name="-2, -2"  replace="-2, -2, 0, 0" target="java"/>
        <ReplaceString name="import androidx\.core\.view\.\*;"  replace="" target="java"/>
		<Widget baseDirectory="D:/Java/github_ashera/AbsoluteLayout/swt" os="swt" prefix="r." createDefault="nativecreate|asnativewidget|" generateXmlTestCase="true" ></Widget>
		<Widget baseDirectory="D:/Java/github_ashera/AbsoluteLayout/ios" os="ios" prefix="r." createDefault="nativecreate|asnativewidget|" generateXmlTestCase="true" ></Widget>
		<Widget baseDirectory="D:/Java/github_ashera/AbsoluteLayout/browser" os="web" prefix="r." createDefault="nativecreate|asnativewidget|" generateXmlTestCase="true" ></Widget>
	</Widget>
</Widgets>

Run WidgetGenerator.java with vm arguments -DbaseDir=D:/Java/github_ashera/AbsoluteLayout. This will take the above xml file and generate com.ashera.absolutelayout.AbsoluteLayoutImpl.java for all platforms.

Run PluginXmlGenerator.java with vm arguments -DbaseDir=D:/Java/github_ashera/AbsoluteLayout. This will update plugin.xml for all platforms and generate the main package.json and plugin.xml required.

Preview - Absolute Layout in action

The following example shows AbsoluteLayout developed above in action: