Mobile Development 16 min read

Dynamic Android UI: Adding Views Programmatically, Drawable Subclasses, and NinePatch Handling

The article explains how Android developers can build dynamic user interfaces by programmatically creating and adding views, replace XML drawables with Drawable subclasses such as StateListDrawable, GradientDrawable, and LayerDrawable, and generate NinePatchChunk data at runtime to use server‑delivered .9.png images without pre‑processing.

Tencent Music Tech Team
Tencent Music Tech Team
Tencent Music Tech Team
Dynamic Android UI: Adding Views Programmatically, Drawable Subclasses, and NinePatch Handling

Android developers are accustomed to defining UI layouts with XML files in the layout directory. While XML offers clear structure and preview capabilities, certain scenarios require runtime‑dependent layouts that cannot be predefined. This article introduces three aspects of dynamic layout creation using Java code.

Adding views to the UI without XML layout files.

Using Drawable subclasses to replace XML drawable resources.

Understanding and constructing NinePatchChunk for backend‑delivered .9.png images.

Dynamic View Addition

The basic step is to create a container view programmatically and add child views to it. Common Android UI elements such as Button , ImageView , RelativeLayout , and LinearLayout all inherit from View . They can be categorized as either controls (directly inheriting from View ) or containers (inheriting from ViewGroup ).

Example code to create a root RelativeLayout and set it as the content view:

RelativeLayout root = new RelativeLayout(this);
root.setBackgroundColor(Color.WHITE);
setContentView(root, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

Adding a centered button:

Button button1 = new Button(this);
button1.setId(View.generateViewId());
button1.setText("Button1");
button1.setBackgroundColor(Color.RED);
LayoutParams btnParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
btnParams.addRule(RelativeLayout.CENTER_IN_PARENT, 1);
root.addView(button1, btnParams);

The process consists of three steps: (1) instantiate the view, (2) create appropriate LayoutParams based on the container type, and (3) add the view to the container with addView .

A more complex example adds a LinearLayout below the button, then places a TextView and another Button inside it with a 2:3 width ratio using layout_weight :

// Add LinearLayout below and to the right of button1
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
LayoutParams lParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
lParams.addRule(RelativeLayout.BELOW, button1.getId());
lParams.addRule(RelativeLayout.RIGHT_OF, button1.getId());
root.addView(linearLayout, lParams);

// Add TextView with weight 2
TextView textView = new TextView(this);
textView.setText("TextView");
textView.setTextSize(28);
textView.setBackgroundColor(Color.BLUE);
LinearLayout.LayoutParams tParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT);
tParams.weight = 2;
linearLayout.addView(textView, tParams);

// Add Button with weight 3
Button button2 = new Button(this);
button2.setText("Button2");
button2.setBackgroundColor(Color.RED);
LinearLayout.LayoutParams bParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT);
bParams.weight = 3;
linearLayout.addView(button2, bParams);

When using relative rules, the referenced view must have a non‑zero ID; otherwise the rule is ignored. Generating IDs with View.generateViewId() avoids collisions.

A common pitfall occurs when trying to place an ImageView outside the bounds of a RelativeLayout . The issue stems from default rightMargin being zero during onMeasure and onLayout . Adding params.rightMargin = -1 * width resolves the problem.

Drawable Subclasses

XML drawable resources (e.g., selector , shape ) are often referenced from layout XML. To replace them with code, use the corresponding Drawable subclasses:

StateListDrawable : Implements selector behavior for different view states. StateListDrawable selector = new StateListDrawable(); selector.addState(new int[]{android.R.attr.state_pressed}, drawablePress); selector.addState(new int[]{android.R.attr.state_enabled}, drawableEnable); selector.addState(new int[]{android.R.attr.state_selected}, drawableSelected); selector.addState(new int[]{android.R.attr.state_focused}, drawableFocused); selector.addState(new int[]{}, drawableNormal);

GradientDrawable : Creates gradient backgrounds. GradientDrawable drawable = new GradientDrawable(); drawable.setOrientation(Orientation.TOP_BOTTOM); drawable.setColors(colors); // colors is an int[] with at least two colors

LayerDrawable : Used for complex drawables such as progress bars. LayerDrawable layerDrawable = (LayerDrawable) getProgressDrawable(); layerDrawable.setDrawableByLayerId(android.R.id.background, backgroundDrawable); ClipDrawable clipProgress = new ClipDrawable(progressDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL); layerDrawable.setDrawableByLayerId(android.R.id.progress, clipProgress); ClipDrawable clipSecondary = new ClipDrawable(secondaryProgressDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL); layerDrawable.setDrawableByLayerId(android.R.id.secondaryProgress, clipSecondary);

Further Drawable subclasses can be explored in the Android documentation.

Handling .9.png (NinePatch) Images

.9.png images contain stretchable region metadata processed by aapt . When delivering NinePatch images from a server, the raw PNG must be converted with aapt s -i xx.9.png -o xx.png so that the resulting file includes the required NinePatchChunk . The client can then load it via:

Bitmap bitmap = BitmapFactory.decodeFile(filePath);
NinePatchDrawable npd = new NinePatchDrawable(context.getResources(), bitmap, bitmap.getNinePatchChunk(), new Rect(), null);

To avoid the extra aapt step, one can construct the NinePatch chunk manually. The article shows the deserialization method from NinePatchChunk.java and explains the structure of the chunk (arrays mDivX , mDivY , mColor and padding information).

Example code that builds a center‑stretch NinePatch chunk at runtime:

Bitmap bitmap = BitmapFactory.decodeFile(filepath);
int[] xRegions = new int[]{bitmap.getWidth() / 2, bitmap.getWidth() / 2 + 1};
int[] yRegions = new int[]{bitmap.getHeight() / 2, bitmap.getHeight() / 2 + 1};
int NO_COLOR = 0x00000001;
int colorSize = 9;
int bufferSize = xRegions.length * 4 + yRegions.length * 4 + colorSize * 4 + 32;
ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize).order(ByteOrder.nativeOrder());
byteBuffer.put((byte)1); // non‑zero flag
byteBuffer.put((byte)2); // mDivX length
byteBuffer.put((byte)2); // mDivY length
byteBuffer.put((byte)colorSize); // mColor length
byteBuffer.putInt(0); // skip 8 bytes
byteBuffer.putInt(0);
byteBuffer.putInt(0); // padding left
byteBuffer.putInt(0); // padding right
byteBuffer.putInt(0); // padding top
byteBuffer.putInt(0); // padding bottom
byteBuffer.putInt(0); // skip
byteBuffer.putInt(xRegions[0]);
byteBuffer.putInt(xRegions[1]);
byteBuffer.putInt(yRegions[0]);
byteBuffer.putInt(yRegions[1]);
for (int i = 0; i < colorSize; i++) {
    byteBuffer.putInt(NO_COLOR);
}
byte[] ninePatchChunk = byteBuffer.array();

With the generated ninePatchChunk , developers can create a NinePatchDrawable without pre‑processed resources. The article also references open‑source libraries that implement this functionality.

References include CSDN blog posts, Android source files ( NinePatchChunk.java , ResourceTypes.h ), and StackOverflow discussions.

javaAndroidNinePatchDrawabledynamic layout
Tencent Music Tech Team
Written by

Tencent Music Tech Team

Public account of Tencent Music's development team, focusing on technology sharing and communication.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.