Custom components / custom widgets
Tips
Auto.js Pro also includes built-in examples for custom widgets. See Examples → UI widgets.
This page explains how to create reusable custom components/widgets in Auto.js Pro v8 (Rhino UI). The key API is:
$ui.registerWidget(name, widget)
You can package a piece of layout + interaction into a new XML tag (e.g. <counter/>, <userCard/>) and reuse it across pages. Common capabilities include:
- Custom props (attributes in XML such as
title="...",value="...") - Custom events (notify the outside when something changes)
- Instance methods (e.g.
setValue(),reset()) - Composing sub-layouts (inflate more XML inside the widget)
Terminology: in this doc, “component / widget / custom control” all mean a reusable UI abstraction. It can be a single Android View, or a composite component made of multiple Views.
1. Minimal example: register and use a custom widget
Goal: create a <hello/> widget that displays one line of text.
"ui";
// 1) Register widget: `name` becomes the XML tag name
$ui.registerWidget("hello", function (ctx, attrs) {
// ctx: Android Context
// attrs: XML attributes collection (may vary across versions)
// Simplest: return an inflated View
return $ui.inflate(
<frame padding="12dp">
<text id="t" text="Hello" textSize="16sp" />
</frame>,
);
});
// 2) Use widget
$ui.layout(
<vertical padding="16dp">
<hello />
</vertical>,
);2. Reading XML attributes (props)
Typically you want to pass props from XML, for example:
<hello text="Hi" />The idea is: read from attrs, then apply the value to internal Views. In Auto.js Pro, attrs may look different across versions (sometimes it behaves like a plain object, sometimes it has methods like get). To keep code more robust, use a compatibility getter.
function getAttr(attrs, name, defaultValue) {
try {
// Common case: attrs[name]
if (attrs && typeof attrs === "object" && name in attrs) return attrs[name];
// Some implementations provide get(name)
if (attrs && typeof attrs.get === "function") {
const v = attrs.get(name);
return v == null ? defaultValue : v;
}
} catch (e) {}
return defaultValue;
}Then in your widget:
$ui.registerWidget("hello", function (ctx, attrs) {
const text = getAttr(attrs, "text", "Hello");
const v = $ui.inflate(
<frame padding="12dp">
<text id="t" textSize="16sp" />
</frame>,
);
v.t.setText(String(text));
return v;
});3. Exposing instance methods to the outside
If you want the outside script to call:
ui.myHello.setText("Hi");Attach methods to the returned View object. In Auto.js, when you give the widget an id, you can access the instance via ui.{id} (e.g. ui.myHello). So adding methods onto the View is the most direct approach.
$ui.registerWidget("hello", function (ctx, attrs) {
const v = $ui.inflate(
<frame padding="12dp">
<text id="t" textSize="16sp" />
</frame>,
);
v.setText = function (s) {
// UI updates must be on UI thread
ui.post(() => v.t.setText(String(s)));
};
return v;
});4. Custom events: notify the outside
A common requirement is notifying the outside when something happens inside the widget.
4.1 Option A: assign a callback (simplest)
Passing function references directly via XML is often inconvenient (XML expression support varies). A stable pattern is: after you get the widget instance, assign a callback field.
Outside:
ui.counter.onChange = function (value) {
toastLog("value=" + value);
};Inside the widget:
if (typeof v.onChange === "function") v.onChange(nextValue);4.2 Option B: reuse View events (recommended for click-like events)
If your widget itself is clickable, let the outside bind events as usual:
ui.counter.on("click", () => {});Then the widget only needs to return a normal View and ensure it can receive click events.
5. Full example: Counter widget (props + methods + events)
This widget exposes:
- Props:
title,value - Methods:
getValue(),setValue(v),reset() - Event:
onChange(value)
"ui";
function getAttr(attrs, name, defaultValue) {
try {
if (attrs && typeof attrs === "object" && name in attrs) return attrs[name];
if (attrs && typeof attrs.get === "function") {
const v = attrs.get(name);
return v == null ? defaultValue : v;
}
} catch (e) {}
return defaultValue;
}
$ui.registerWidget("counter", function (ctx, attrs) {
const title = String(getAttr(attrs, "title", "Counter"));
const initialValue = Number(getAttr(attrs, "value", 0)) || 0;
const v = $ui.inflate(
<vertical padding="12dp" bg="#ffffff">
<text id="title" textSize="16sp" textColor="#222222" />
<horizontal marginTop="8dp" gravity="center_vertical">
<button id="minus" text="-" w="48dp" h="40dp" />
<text id="value" textSize="18sp" marginLeft="12dp" marginRight="12dp" />
<button id="plus" text="+" w="48dp" h="40dp" />
<button id="resetBtn" text="Reset" marginLeft="12dp" h="40dp" />
</horizontal>
</vertical>,
);
let value = initialValue;
v.title.setText(title);
v.value.setText(String(value));
function emitChange() {
if (typeof v.onChange === "function") {
try {
v.onChange(value);
} catch (e) {
console.error(e);
}
}
}
function setValue(next) {
value = Number(next) || 0;
v.value.setText(String(value));
emitChange();
}
// Public methods
v.getValue = () => value;
v.setValue = (next) => ui.post(() => setValue(next));
v.reset = () => ui.post(() => setValue(initialValue));
// Internal interaction
v.minus.on("click", () => setValue(value - 1));
v.plus.on("click", () => setValue(value + 1));
v.resetBtn.on("click", () => setValue(initialValue));
return v;
});
$ui.layout(
<vertical padding="16dp">
<counter id="counter" title="My counter" value="3" />
<text id="log" marginTop="12dp" text="Ready" />
<horizontal marginTop="12dp">
<button id="btnSet10" text="Set to 10" />
<button id="btnRead" text="Read value" marginLeft="12dp" />
</horizontal>
</vertical>,
);
ui.counter.onChange = (v) => {
ui.log.setText("value = " + v);
};
ui.btnSet10.on("click", () => ui.counter.setValue(10));
ui.btnRead.on("click", () => toastLog("current = " + ui.counter.getValue()));6. Common pitfalls & best practices
6.1 UI thread
- All View updates must happen on the UI thread.
- If you need to update the widget from a worker thread, use
$ui.post(...)/$ui.run(...).
6.2 Avoid heavy work inside widgets
- Put network requests / file IO / heavy computation on worker threads.
- After the worker finishes,
ui.postback to update UI.
6.3 Naming and conflicts
- Widget
ids may conflict with existinguiproperties (e.g.layout,post,run). If it conflicts, use$ui.findView(id). - Prefer meaningful lowercase tag names for custom widgets (e.g.
counter,userCard).
6.4 Reuse and state
- Keep state encapsulated inside the widget (like
valuein the example), and expose only necessary methods/events. - For “controlled components”, expose
setValue()and manage the state from outside.
7. Related links
$ui.registerWidgetAPI: see the$ui.registerWidget(...)section insrc/en/v8/ui/api.md- UI guidelines:
src/en/v8/ui/guidelines.md - Build UI with WebView:
src/en/v8/ui/webview.md
lang: en-US
title: Custom widgets
description: Auto.js Pro v8 custom widget guide - learn how to create and use custom widgets to extend UI capabilities.
keywords:
- Auto.js Pro
- custom widget
- widget extension
- custom View
- widget development
- UI extension
- v8 API
date: 2022-10-22
order: 4
Tips
Auto.js Pro also includes built-in examples for custom widgets. See Examples → UI widgets.
More content will be added soon.
