Mobile Development 15 min read

Turn Any Android App Grayscale with a Few Custom Views

This article explains how to apply a full‑screen grayscale effect to Android apps by using a single CSS rule for web pages and custom View subclasses—GrayImageView, GrayTextView, GrayLinearLayout, and GrayFrameLayout—plus an Activity onCreateView override to replace the root content view, complete with code snippets and practical tips.

AI Code to Success
AI Code to Success
AI Code to Success
Turn Any Android App Grayscale with a Few Custom Views

Web quick grayscale

For a web page a single CSS rule can render the whole document in grayscale:

html { filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1); -webkit-filter: grayscale(100%); }

Android grayscale using ColorMatrix

Android UI is drawn on a Canvas. By applying a ColorMatrixColorFilter with saturation set to 0 to a Paint and drawing the view on a saved layer, every pixel is converted to gray.

Custom view example – ImageView

public class GrayImageView extends AppCompatImageView {
    private Paint mPaint = new Paint();
    public GrayImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        ColorMatrix cm = new ColorMatrix();
        cm.setSaturation(0);
        mPaint.setColorFilter(new ColorMatrixColorFilter(cm));
    }
    @Override
    public void draw(Canvas canvas) {
        canvas.saveLayer(null, mPaint, Canvas.ALL_SAVE_FLAG);
        super.draw(canvas);
        canvas.restore();
    }
}

The same logic works for TextView and Button – just extend AppCompatTextView or AppCompatButton and copy the constructor and draw() implementation.

Reducing boilerplate – a gray container

Instead of replacing every widget, wrap the UI in a custom layout that applies the gray Paint to both draw() and dispatchDraw(). All child views inherit the effect automatically.

public class GrayLinearLayout extends LinearLayout {
    private Paint mPaint = new Paint();
    public GrayLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        ColorMatrix cm = new ColorMatrix();
        cm.setSaturation(0);
        mPaint.setColorFilter(new ColorMatrixColorFilter(cm));
    }
    @Override
    public void draw(Canvas canvas) {
        canvas.saveLayer(null, mPaint, Canvas.ALL_SAVE_FLAG);
        super.draw(canvas);
        canvas.restore();
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {
        canvas.saveLayer(null, mPaint, Canvas.ALL_SAVE_FLAG);
        super.dispatchDraw(canvas);
        canvas.restore();
    }
}

Replace the root layout in XML with com.example.view.GrayLinearLayout and the whole screen becomes grayscale.

Universal solution – intercepting the activity content view

Every Activity hosts a view with id android.R.id.content. By overriding Activity.onCreateView() we can replace the default FrameLayout with a custom GrayFrameLayout that contains the same gray drawing logic.

public class GrayFrameLayout extends FrameLayout {
    private Paint mPaint = new Paint();
    public GrayFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        ColorMatrix cm = new ColorMatrix();
        cm.setSaturation(0);
        mPaint.setColorFilter(new ColorMatrixColorFilter(cm));
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {
        canvas.saveLayer(null, mPaint, Canvas.ALL_SAVE_FLAG);
        super.dispatchDraw(canvas);
        canvas.restore();
    }
    @Override
    public void draw(Canvas canvas) {
        canvas.saveLayer(null, mPaint, Canvas.ALL_SAVE_FLAG);
        super.draw(canvas);
        canvas.restore();
    }
}
public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        if ("FrameLayout".equals(name)) {
            int count = attrs.getAttributeCount();
            for (int i = 0; i < count; i++) {
                if ("id".equals(attrs.getAttributeName(i))) {
                    String idVal = getResources().getResourceName(
                        Integer.parseInt(attrs.getAttributeValue(i).substring(1)));
                    if ("android:id/content".equals(idVal)) {
                        GrayFrameLayout gray = new GrayFrameLayout(context, attrs);
                        // Preserve possible window background
                        gray.setBackgroundDrawable(getWindow().getDecorView().getBackground());
                        return gray;
                    }
                }
            }
        }
        return super.onCreateView(name, context, attrs);
    }
}

This replacement works for the activity UI, dialogs (which also use the content view) and WebView content because the gray filter is applied at the canvas level.

Handling window background

If the activity window defines a background via theme, the background must be transferred to the custom layout. The background can be a color or a drawable; the following code extracts it from the current theme:

TypedValue a = new TypedValue();
getTheme().resolveAttribute(android.R.attr.windowBackground, a, true);
if (a.type >= TypedValue.TYPE_FIRST_COLOR_INT && a.type <= TypedValue.TYPE_LAST_COLOR_INT) {
    int color = a.data; // background is a solid color
    grayFrameLayout.setBackgroundColor(color);
} else {
    Drawable d = getResources().getDrawable(a.resourceId);
    grayFrameLayout.setBackground(d);
}

Edge cases and future compatibility

If future Android versions change the class used for android.R.id.content, the same interception logic can be adapted by checking the view name and id.

Because the gray filter is applied in draw() and dispatchDraw(), any child view—including custom views, WebView, and dialog windows—automatically inherits the effect.

Verification

The technique was applied to the open‑source project https://github.com/jenly1314/WanAndroid . Adding the GrayFrameLayout logic to the base activity rendered the entire app—images, text, and web content—in grayscale without modifying individual layouts.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AndroidCanvasLayoutInflaterGrayscaleCustomViewAppCompatColorMatrix
AI Code to Success
Written by

AI Code to Success

Focused on hardcore practical AI technologies (OpenClaw, ClaudeCode, LLMs, etc.) and HarmonyOS development. No hype—just real-world tips, pitfall chronicles, and productivity tools. Follow to transform workflows with code.

0 followers
Reader feedback

How this landed with the community

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.