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.
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 is a set of tools provided by Ashera to develop cross platform plugin. It automates the creation of plugin by providing the following:
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. |
The following are steps involved to create the AbsoluteLayout plugin:
Download codepoacher from the github link to a suitable location. e.g. D:/Java/github_ashera/code-poacher
D:/Java/github_ashera/AbsoluteLayout/codepoacher
config.properties for AbsoluteLayout given below:
name=AbsoluteLayout
uniqueid=absolutelayout
packageprefix=com.ashera
packageprefixpath=com/ashera
iospackageprefix=AS
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
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.
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.
The following example shows AbsoluteLayout developed above in action: