Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
An ImageView which supports a foreground drawable.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ForegroundImageView">
<attr name="android:foreground"/>
</declare-styleable>
</resources>
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
public class ForegroundImageView extends ImageView {
private Drawable foreground;
public ForegroundImageView(Context context) {
this(context, null);
}
public ForegroundImageView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundImageView);
Drawable foreground = a.getDrawable(R.styleable.ForegroundImageView_android_foreground);
if (foreground != null) {
setForeground(foreground);
}
a.recycle();
}
/**
* Supply a drawable resource that is to be rendered on top of all of the child
* views in the frame layout.
*
* @param drawableResId The drawable resource to be drawn on top of the children.
*/
public void setForegroundResource(int drawableResId) {
setForeground(getContext().getResources().getDrawable(drawableResId));
}
/**
* Supply a Drawable that is to be rendered on top of all of the child
* views in the frame layout.
*
* @param drawable The Drawable to be drawn on top of the children.
*/
public void setForeground(Drawable drawable) {
if (foreground == drawable) {
return;
}
if (foreground != null) {
foreground.setCallback(null);
unscheduleDrawable(foreground);
}
foreground = drawable;
if (drawable != null) {
drawable.setCallback(this);
if (drawable.isStateful()) {
drawable.setState(getDrawableState());
}
}
requestLayout();
invalidate();
}
@Override protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == foreground;
}
@Override public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (foreground != null) foreground.jumpToCurrentState();
}
@Override protected void drawableStateChanged() {
super.drawableStateChanged();
if (foreground != null && foreground.isStateful()) {
foreground.setState(getDrawableState());
}
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (foreground != null) {
foreground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
invalidate();
}
}
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (foreground != null) {
foreground.setBounds(0, 0, w, h);
invalidate();
}
}
@Override public void draw(Canvas canvas) {
super.draw(canvas);
if (foreground != null) {
foreground.draw(canvas);
}
}
}
@djdmbrwsk

This comment has been minimized.

Copy link

@djdmbrwsk djdmbrwsk commented Feb 12, 2016

I believe drawableStateChanged() should call invalidate();

  @Override protected void drawableStateChanged() {
    super.drawableStateChanged();
    if (foreground != null && foreground.isStateful()) {
      foreground.setState(getDrawableState());
      invalidate();
    }
  }
@arundua

This comment has been minimized.

Copy link

@arundua arundua commented May 18, 2016

hey can you explain why you created this custom Imageview , I am new to Android please explain

@vasileknik76

This comment has been minimized.

Copy link

@vasileknik76 vasileknik76 commented Jun 13, 2016

hey can you explain why you created this custom Imageview , I am new to Android please

I don't sure, but I think that reason is method setForeground appears in API 23. But this method may be usefull to create overlays. And this implementation can be used for device with android lower than Marshmallow.

@jaypoojara

This comment has been minimized.

Copy link

@jaypoojara jaypoojara commented Jun 21, 2016

it is working fine for android version 5.0 and above but not for android version below 5.0.
java.lang.ClassCastException: android.support.v7.widget.AppCompatImageView cannot be cast to quotes.pub.widgets.ForegroundImageView
this is the error it is showing to me.
please help me.
thank you.

thank you.

@eneim

This comment has been minimized.

Copy link

@eneim eneim commented Jun 22, 2016

@jaypoojara I believe you are using android.widget.ImageView in your xml meanwhile you are calling ForegroundImageView in your java class.

@budioktaviyan

This comment has been minimized.

Copy link

@budioktaviyan budioktaviyan commented Sep 3, 2016

Wow, thanks dude. it works for me 👍

@anish011193

This comment has been minimized.

Copy link

@anish011193 anish011193 commented Feb 22, 2017

It works. Yet, I have a textview below the imageview which gives the title of the image. The ripple effects doesn't appears on textview. Is there any option to apply ripple effect completely? Please provide steps... Thanks in advance.

@sevar83

This comment has been minimized.

Copy link

@sevar83 sevar83 commented May 23, 2017

For the record: ripple can be implemented by setting these on the parent layout:

android:clickable="true"
android:foreground="?selectableItemBackground"
@pietroleggero

This comment has been minimized.

Copy link

@pietroleggero pietroleggero commented May 26, 2017

THX

@luongvo

This comment has been minimized.

Copy link

@luongvo luongvo commented Feb 6, 2018

Thanks 👍

@pk-development

This comment has been minimized.

Copy link

@pk-development pk-development commented Jun 8, 2018

Also so works for Buttons with slight change.

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;

public class ForegroundButtonView extends android.support.v7.widget.AppCompatButton {
    private Drawable foreground;

    public ForegroundButtonView(Context context) {
        this(context, null);
    }

    public ForegroundButtonView(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundButtonView);
        Drawable foreground = a.getDrawable(R.styleable.ForegroundButtonView_android_foreground);
        if (foreground != null) {
            setForeground(foreground);
        }
        a.recycle();
    }

    /**
     * Supply a drawable resource that is to be rendered on top of all of the child
     * views in the frame layout.
     *
     * @param drawableResId The drawable resource to be drawn on top of the children.
     */
    public void setForegroundResource(int drawableResId) {
        setForeground(getContext().getResources().getDrawable(drawableResId));
    }

    /**
     * Supply a Drawable that is to be rendered on top of all of the child
     * views in the frame layout.
     *
     * @param drawable The Drawable to be drawn on top of the children.
     */
    public void setForeground(Drawable drawable) {
        if (foreground == drawable) {
            return;
        }
        if (foreground != null) {
            foreground.setCallback(null);
            unscheduleDrawable(foreground);
        }

        foreground = drawable;

        if (drawable != null) {
            drawable.setCallback(this);
            if (drawable.isStateful()) {
                drawable.setState(getDrawableState());
            }
        }
        requestLayout();
        invalidate();
    }

    @Override protected boolean verifyDrawable(Drawable who) {
        return super.verifyDrawable(who) || who == foreground;
    }

    @Override public void jumpDrawablesToCurrentState() {
        super.jumpDrawablesToCurrentState();
        if (foreground != null) foreground.jumpToCurrentState();
    }

    @Override protected void drawableStateChanged() {
        super.drawableStateChanged();
        if (foreground != null && foreground.isStateful()) {
            foreground.setState(getDrawableState());
        }
    }

    @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (foreground != null) {
            foreground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
            invalidate();
        }
    }

    @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (foreground != null) {
            foreground.setBounds(0, 0, w, h);
            invalidate();
        }
    }

    @Override public void draw(Canvas canvas) {
        super.draw(canvas);

        if (foreground != null) {
            foreground.draw(canvas);
        }
    }
}
@flawyte

This comment has been minimized.

Copy link

@flawyte flawyte commented Apr 13, 2020

For the record: ripple can be implemented by setting these on the parent layout:

android:clickable="true"
android:foreground="?selectableItemBackground"

Unfortunately this solution is only available on APIs >= 23, and if your image has rounded corners the ripple will go past them.
A solution that solves all problems is to use tint and tintMode as described in this Medium article, and it requires 0 Java/Kotlin code or external library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment