Skip to content

Instantly share code, notes, and snippets.

@JakeWharton
Created July 10, 2014 16:42
Show Gist options
  • Save JakeWharton/0a251d67649305d84e8a to your computer and use it in GitHub Desktop.
Save JakeWharton/0a251d67649305d84e8a to your computer and use it in GitHub Desktop.
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
Copy link

I believe drawableStateChanged() should call invalidate();

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

@arundua
Copy link

arundua commented May 18, 2016

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

@vasileknik76
Copy link

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
Copy link

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
Copy link

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
Copy link

Wow, thanks dude. it works for me 👍

@anish011193
Copy link

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
Copy link

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
Copy link

THX

@luongvo
Copy link

luongvo commented Feb 6, 2018

Thanks 👍

@pk-development
Copy link

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
Copy link

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