Why doesn't `android:foreground` attribute work?

Short answer

This is due to a bug which existing in Android since API level 23.


More details on the behavior

Here is the list of all the XML attributes and corresponding methods related to setting foreground drawables to views with the API level they are introduced through FrameLayout. However, these are later moved into View in API level 23.

╔════════════════════════════╦═════════════════════════════════════════════════╦═════════════╗
║       XML attribute        ║                     Method                      ║   Added in  ║
║                            ║                                                 ║ (API level) ║
╠════════════════════════════╬═════════════════════════════════════════════════╬═════════════╣
║ android:foreground         ║ setForeground(Drawable)                         ║ 1           ║
╠════════════════════════════╬═════════════════════════════════════════════════╬═════════════╣
║ android:foregroundGravity  ║ setForegroundGravity(int gravity)               ║ 1           ║
╠════════════════════════════╬═════════════════════════════════════════════════╬═════════════╣
║ android:foregroundTint     ║ setForegroundTintMode(PorterDuff.Mode tintMode) ║ 21          ║
╠════════════════════════════╬═════════════════════════════════════════════════╬═════════════╣
║ android:foregroundTintMode ║ setForegroundTintMode(PorterDuff.Mode tintMode) ║ 21          ║
╚════════════════════════════╩═════════════════════════════════════════════════╩═════════════╝
  • Android doc says setForeground(Drawable) is added in API 1 and setForegroundTintList (ColorStateList tint) and setForegroundTintMode (PorterDuff.Mode tintMode) are added in API level 21 to View. Actually they were there in FrameLayout until it moved in API 23.

  • In API level < 23, you will get a warning even though it is not required. You can just suppress it. See this.


Now take a look at how these properties work on different versions.

╔═══════════╦══════════════════╦══════════════════╗
║ API level ║      By code     ║     Using XML    ║
╠═══════════╬══════════════════╬══════════════════╣
║ <23       ║ FrameLayout only ║ FrameLayout only ║
╠═══════════╬══════════════════╬══════════════════╣
║ >=23      ║ FrameLayout only ║ All views        ║
╚═══════════╩══════════════════╩══════════════════╝


The cause of the bug

When these properties moved to View in API level 23, they did some strange modifications on it which can be called a bug. While loading properties from XML, it checks whether the View is a FrameLayout which is not present inside the methods we can use for the same purpose.

View constructor, API level 23:

case R.styleable.View_foreground:
    if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) {
        setForeground(a.getDrawable(attr));
    }
    break;

To use android:foreground on Android 5.1 i.e. API level 22, you are not using android:foreground correctly.

As it's name clearly indicating that you can set drawable on the top/foreground of any content like overlay i.e you can put some view in FrameLayout in that you can use android:foreground. Inside this FrameLayout add your ImageView.

Documentation:

Defines the drawable to draw over the content. This can be used as an overlay. The foreground drawable participates in the padding of the content if the gravity is set to fill.

Below is usage example:

<FrameLayout
    android:id="@+id/share"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:foreground="@drawable/ic_launcher_background>

    // your ImageView here
    <ImageView
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

</FrameLayout>

Note: For API level > 23 it will work without FrameLayout.

I hope this will help you.


It looks like at one time (API <23) that android:foreground would work with only FrameLayout as VicJordan suggests. However, for API 23+ it appears that android:foreground will work for any view type. See this selection from the View.java source:

case R.styleable.View_foreground:
    if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) {
        setForeground(a.getDrawable(attr));
}

Here is an example of android:foreground working on API 28 with the following layout:

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:foreground="@drawable/ic_launcher_foreground"
        android:src="@android:drawable/ic_delete"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

enter image description here

Yet, on API 22, we see this:

enter image description here

No foreground image. So, android:foreground works only when the API level is 23+. I agree that this is not really documented, but that is just the way it is.

Update: API 23 seems to have an issue with android:foreground, so let's say android:foreground works on API 24+ for general views.

Second update: Came across a couple of other posts addressing this same issue regarding setForeground() here and here. In the accepted answer to the second question, CommonsWare identifies this as a "documentation bug."