[FREE/OS] Simple Corners


⬛ 🖼️ SimpleCorners

An extension for MIT App Inventor 2.

Developed by TIMAI2 using Fast. Credits to @eAcademy for their work on the image rounded corners and border, @PatrykF for the async method, and thanks to @herman5 and @kevinkun for ideas.

Why start at version 2 ? Well it started off with version 1, which was simple, as you will see from the code, but then I decided to add another function to set an image to an image component, along with rounded corners and a border. That is where the fun started, again see the code, and how I ended up with version 2!

The first main function will allow you to set a background colour, rounded corners and a border width and colour to most of the view components in AppInventor. Tested so far, Label, Button, Textbox, HVArrangements, Canvas.

You can still apply a background image to components that accept one, but this is applied to an upper layer, so covers the rounded corners, border and background colour. To overcome this, you can modify an existing image in two ways (Using an image editing program or extension): extend the 'canvas' around the image, setting it to transparent, or add rounded corners (of the same pixel size) to an image.

The second main function is to add an image to an image component from a variety of sources: http/https urls, app assets, ASD, local storage, and create rounded corners, a border and border colour. This only works with the image component.

Note that the image component has a "breakpoint" in its code to resize larger images, e.g. an 800x800 image will be resized to 256x256, in order to fit the viewport. Start off with your image component with its width and height set to automatic, then adjust as required.

:memo: Specifications


:package: Package: uk.co.metricrat.simplecorners
:floppy_disk: Size: 11.71 KB
:gear: Version: 2.0
:mobile_phone: Minimum API Level: 14
:date: Updated On: 2026-09-03T23:00:00Z
:laptop: Built & documented using: FAST v7.0.0-premium

Methods:

SimpleCorners has total 2 methods.

1. Create

creates rounded corners for buttons, labels, textboxes, HVArrangements, and canvases

2. Image

creates rounded corners for image - from just about anywhere - in image component

3. Example Usage

4. Extension

SimpleCorners

5. Source Code

simplecorners.java
package uk.co.metricrat.simplecorners;

import android.content.*;
import android.graphics.*;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.*;
import android.view.*;
import android.widget.*;
import com.google.appinventor.components.annotations.*;
import com.google.appinventor.components.annotations.Component;
import com.google.appinventor.components.runtime.*;
import com.google.appinventor.components.runtime.Image;
import java.io.*;
import java.io.File;
import java.net.*;


@Component(
	version = 2,
	versionName = "2.0",
	description = "Developed by TIMAI2 using Fast. Credits to eAcademy and PatrykF for their work on the image rounded corners and border, PatrykF for the async method, and thanks to Herman5 and " +
			"Kevinkun for ideas.",
	iconName = "icon.png"
)
public class SimpleCorners extends AndroidNonvisibleComponent {

	private final ComponentContainer container;
	private final Context context;

	public SimpleCorners(ComponentContainer container) {
		super(container.$form());
		this.container = container;
		context = (Context) container.$context();
	}

	@SimpleFunction("creates rounded corners for buttons, labels, textboxes, HVArrangements, and canvases")
	public void Create(AndroidViewComponent component, int bgColour, int cornerRadius, int borderWidth, int borderColour) {
		View view = component.getView();
		GradientDrawable gradDraw = new GradientDrawable();
		//sets background colour
		gradDraw.setColor(bgColour);
		//sets borderWidth and colour
		gradDraw.setStroke(setPixels(borderWidth), borderColour);
		//sets rounded corners
		gradDraw.setCornerRadius(setPixels(cornerRadius));
		view.setBackground(gradDraw);
	} //END of Create function

	@SimpleFunction("creates rounded corners for image in image component")
	public void Image(Image imageComponent, String imageUrl, int cornerRadius, int borderWidth, int borderColour) throws IOException, URISyntaxException {
		ImageView imageView = (ImageView) imageComponent.getView();
		if (imageUrl.startsWith("https") || imageUrl.startsWith("http")) {
		// use async get image from url
			String finalImageUrl = imageUrl;
			new Thread(() -> {
				InputStream is = null;
				Bitmap cBitmap = null;
				try {
					is = new URL(finalImageUrl).openStream();
					Bitmap bitmap = BitmapFactory.decodeStream(is);
					if(bitmap != null) {
						cBitmap = newCornerBitmap(bitmap, setPixels(cornerRadius), borderWidth, borderColour);
					}
					Bitmap finalCBitmap = cBitmap;
					imageView.post(() -> {
						if (bitmap != null) {
							imageView.setImageBitmap(finalCBitmap);
						}
					});
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					try {
						if (is != null) is.close();
					} catch (Exception ignored) {}
				}
			}).start();
		} else if (imageUrl.startsWith("/")) {
			if (imageUrl.startsWith("//")) {
				if (imageUrl.startsWith("file:///android_asset")) {
					//get a realpath for asset by creating cacheFile and use target2
					try {
						String target = AssetToCache(imageUrl);
						DoConversion(target, imageView, cornerRadius, borderWidth, borderColour);
					} catch (Exception e) {
						Toast.makeText(context, e.getMessage(), Toast.LENGTH_SHORT).show();
					}
				} else {
					String pkgName = context.getPackageName();
					String filepath = "file:///storage/emulated/0/Android/data/" + pkgName + "/files/assets/" + imageUrl.replace("//","");
					String target = new URL(filepath).toURI().getPath();
					DoConversion(target, imageView, cornerRadius, borderWidth, borderColour);
				}
			} else {
				// prefix with "file://" then use target1 to set filepath
				imageUrl = "file://" + imageUrl;
				String target = new URL(imageUrl).toURI().getPath();
				DoConversion(target,imageView,cornerRadius,borderWidth,borderColour);
			}
		} else if (imageUrl.startsWith("file://")) {
			if (imageUrl.startsWith("file:///android_asset")) {
				//get a realpath for asset by creating cacheFile and use target2
				try {
					String target = AssetToCache(imageUrl);
					DoConversion(target,imageView,cornerRadius,borderWidth,borderColour);
				} catch (Exception e) {
					Toast.makeText(context, e.getMessage(), Toast.LENGTH_SHORT).show();
				}
			} else {
			// use target1
			String target = new URL(imageUrl).toURI().getPath();
			DoConversion(target,imageView,cornerRadius,borderWidth,borderColour);
			}
		}
	} //END of Image function

	// function for conversion
	private void DoConversion(String target, ImageView iView, int corRad, int borWth, int borCol) {
		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inScaled = false;
		options.inPreferredConfig = Bitmap.Config.ARGB_8888;
		Bitmap bitmap = BitmapFactory.decodeFile(target, options);
		Bitmap cBitmap = newCornerBitmap(bitmap, setPixels(corRad), borWth, borCol);
		iView.setImageBitmap(cBitmap);
	}

	// function to convert dp to pixels
	public int setPixels(int element) {
		return Math.round((float)element * this.form.deviceDensity());
	}

	// credits to @eAcademy and @PatrykF on Appinventor community
	// creates new bitmap with rounded corners, borderWidth and colour
	public Bitmap newCornerBitmap(Bitmap source, float round, int borderWidth, int borderColour) {
		//get the device Density
		float density = this.form.deviceDensity();
		//set the new bitmap's width, height and round to pixels from dp (<>)
		int width = setPixels(source.getWidth());
		int height = setPixels(source.getHeight());
		float newRound = round*density;
		//resize the source bitmap using device density
		source = Bitmap.createScaledBitmap(source, width, height, false);
		Bitmap bmOut = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
		Canvas canvas = new Canvas(bmOut);
		// Transparent background
		canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
		float halfBorder = borderWidth / 2.0f;
		// Keep the complete border inside the bitmap bounds
		float left = halfBorder;
		float top = halfBorder;
		float right = width - halfBorder;
		float bottom = height - halfBorder;
		float radius = Math.max(0, newRound - halfBorder);
		Rect srcRect = new Rect(0, 0,width, height);
		RectF dstRect = new RectF(left,top,right,bottom);
		// -----------------------------
		// 1. Clip bitmap to rounded shape
		// -----------------------------
		Path path = new Path();
		path.addRoundRect(dstRect,radius,radius,Path.Direction.CW);
		canvas.save();
		canvas.clipPath(path);
		Paint bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
		canvas.drawBitmap(source,srcRect,dstRect,bitmapPaint);
		canvas.restore();
		// -----------------------------
		// 2. Draw rounded border
		// -----------------------------
		if (borderWidth > 0) {
			Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
			borderPaint.setStyle(Paint.Style.STROKE);
			borderPaint.setStrokeWidth(borderWidth);
			borderPaint.setColor(borderColour);
			canvas.drawRoundRect(dstRect,radius,radius,borderPaint);
		}
		return bmOut;
	}

	// gets asset filename in compiled app and creates new file in cache
	private String AssetToCache(String assetFilePath) throws IOException {
		String assetFileName = assetFilePath.replace("file:///android_asset/", "");
		InputStream is = context.getAssets().open(assetFileName);
		File cacheFile = new File(context.getCacheDir(), assetFileName);
		try (FileOutputStream outputStream = new FileOutputStream(cacheFile)) {
			byte[] buffer = new byte[1024];
			int length;
			while ((length = is.read(buffer)) > 0) {
				outputStream.write(buffer, 0, length);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		return cacheFile.getAbsolutePath();
	}

} //END


Enjoy :smiley: , and all feedback and comments welcome.

Available for use in any MIT AppInventor projects or competitions

#####################################################################
This work by TIMAI2 is licensed under a
Creative Commons Attribution-ShareAlike 4.0 Unported License with attribution.
Please use name = TIMAI2 and link to this source page when giving credit.
#####################################################################

7 Likes

:clap: :clap: :clap:

1 Like