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

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

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

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

budioktaviyan commented Sep 3, 2016

Wow, thanks dude. it works for me 👍

@anish011193
Copy link

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

pietroleggero commented May 26, 2017

THX

@luongvo
Copy link

luongvo commented Feb 6, 2018

Thanks 👍

@pk-development
Copy link

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