-
-
Save JakeWharton/0a251d67649305d84e8a to your computer and use it in GitHub Desktop.
<?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); | |
} | |
} | |
} |
hey can you explain why you created this custom Imageview , I am new to Android please explain
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.
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.
@jaypoojara I believe you are using android.widget.ImageView
in your xml meanwhile you are calling ForegroundImageView
in your java class.
Wow, thanks dude. it works for me 👍
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.
For the record: ripple can be implemented by setting these on the parent layout:
android:clickable="true"
android:foreground="?selectableItemBackground"
THX
Thanks 👍
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);
}
}
}
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.
I believe
drawableStateChanged()
should callinvalidate();