TransitionDrawable with vector drawables - android

I have TransitionDrawable defined in xml like this:
transition.xml
<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/ic_disabled" />
<item android:drawable="#drawable/ic_enabled" />
</transition>
I use it to animate state changes of checkbox:
val stateDrawable = ContextCompat.getDrawable(this, R.drawable.transition) as TransitionDrawable
checkbox.buttonDrawable = stateDrawable
checkbox.setOnCheckedChangeListener { icon, checked ->
if (checked) {
stateDrawable.startTransition(300)
} else {
stateDrawable.reverseTransition(300)
}
}
If #drawable/ic_disabled and #drawable/ic_enabled are png files, everything works fine. But if they are vector drawables, transition doesn't work. What am I missing? Does TransitionDrawable not support vector drawables?

I know this is old, but I had the same issue... You gotta convert the Vector to a BitmapDrawable before adding to the TransitionDrawable. Here's an example
TransitionDrawable td = new TransitionDrawable(new Drawable[]{
getBitmapDrawableFromVectorDrawable(this, R.drawable.vector1),
getBitmapDrawableFromVectorDrawable(this, R.drawable.vector2)
});
td.setCrossFadeEnabled(true); // probably want this
// set as checkbox button drawable...
Utility Methods // see https://stackoverflow.com/a/38244327/114549
public static BitmapDrawable getBitmapDrawableFromVectorDrawable(Context context, int drawableId) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return (BitmapDrawable) ContextCompat.getDrawable(context, drawableId);
}
return new BitmapDrawable(context.getResources(), getBitmapFromVectorDrawable(context, drawableId));
}
public static Bitmap getBitmapFromVectorDrawable(Context context, int drawableId) {
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
drawable = (DrawableCompat.wrap(drawable)).mutate();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}

Since all I wanted was to change the image with fade, I implemented a simple version of TransitionDrawable that seem to work for fixed size vector drawables.
public class SimpleTransitionDrawable extends Drawable {
private static final int TRANSITION_STARTING = 0;
private static final int TRANSITION_RUNNING = 1;
private static final int TRANSITION_NONE = 2;
private int state = TRANSITION_NONE;
private long startTimeMillis;
private int duration;
private #Nullable Drawable source;
private Drawable target;
public void setTarget(Drawable target) {
this.source = this.target;
this.target = target;
target.setBounds(0, 0, target.getIntrinsicWidth(), target.getIntrinsicHeight());
}
public void startTransition(int durationMillis) {
duration = durationMillis;
state = TRANSITION_STARTING;
invalidateSelf();
}
#Override
public void draw(Canvas canvas) {
int alpha;
switch (state) {
case TRANSITION_STARTING:
startTimeMillis = SystemClock.uptimeMillis();
alpha = 0;
state = TRANSITION_RUNNING;
break;
case TRANSITION_RUNNING:
float normalized = (float) (SystemClock.uptimeMillis() - startTimeMillis) / duration;
alpha = (int) (0xff * Math.min(normalized, 1.0f));
break;
default:
if (target == null) return;
alpha = 0xff;
break;
}
if (source != null && alpha < 0xff) {
source.setAlpha(0xff - alpha);
source.draw(canvas);
}
if (alpha > 0) {
target.setAlpha(alpha);
target.draw(canvas);
}
if (alpha == 0xff) {
state = TRANSITION_NONE;
return;
}
invalidateSelf();
}
#Override public void setAlpha(int alpha) {}
#Override public void setColorFilter(ColorFilter colorFilter) {}
#Override public int getOpacity() {return PixelFormat.TRANSLUCENT;}
}
Initialize it like this:
ImageView view = findViewById(R.id.image_view);
SimpleTransitionDrawable drawable = new SimpleTransitionDrawable();
view.setImageDrawable(drawable);
Then change the drawable with fade:
drawable.setTarget(AppCompatResources.getDrawable(this, R.drawable.my_vector));
drawable.startTransition(350);

Following the same approach made by #aaronvargas https://stackoverflow.com/a/54583929/1508956
I've created some extensions function which encapsulate the implementation and also makes easier the usability
fun Drawable.getBitmapDrawableFromVectorDrawable(context: Context): BitmapDrawable {
return BitmapDrawable(context.resources, getBitmapFromVectorDrawable())
}
fun Drawable.getBitmapFromVectorDrawable(): Bitmap {
val bitmap = Bitmap.createBitmap(
intrinsicWidth,
intrinsicHeight, Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
setBounds(0, 0, canvas.width, canvas.height)
draw(canvas)
return bitmap
}
fun ImageView.setTransitionDrawable(vectorID: Int, firstColor: Int, secondColor: Int): TransitionDrawable? {
val drawables = arrayOfNulls<Drawable>(2)
val firstDrawable = ContextCompat.getDrawable(context, vectorID) ?: return null
DrawableCompat.setTint(firstDrawable, ContextCompat.getColor(context, firstColor))
drawables[0] = firstDrawable.getBitmapDrawableFromVectorDrawable(context)
val secondDrawable = ContextCompat.getDrawable(context, vectorID) ?: return null
DrawableCompat.setTint(secondDrawable, ContextCompat.getColor(context, secondColor))
drawables[1] = secondDrawable.getBitmapDrawableFromVectorDrawable(context)
val transition = TransitionDrawable(drawables)
transition.isCrossFadeEnabled = true
setImageDrawable(transition)
return transition
}
Initialization:
val transition = imageView.setTransitionDrawable(resourceID, R.color.firstColor, R.color.secondColor)
transition?.startTransition(3000)

Related

Constant ripple effect in android

What I'm trying to do is to have a constant ripple effect on the background of a LinearLayout. Why? Basically, this LinearLayout indicates live users watching this item. So I want the background to have a constant ripple animation similar to some apps that have a live indicator with a ripple effect on the background of that indicator. I hope my question was clear.
Example:
I want this effect to be happing constantly
Hi i tried to code something like this and below is what i come close to. You can always tweek numbers to slow down the animation and other things.
1) Create a ripple drawable background in your res/drawable named temp_ripple.xml
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#color/colorPrimary"
>
<item android:id="#android:id/mask"
android:drawable="#android:color/holo_green_dark"
>
</item>
<item
android:drawable="#android:color/holo_orange_dark">
</item>
</ripple>
2) assign the background to possible view candidate like below, here AppCompatButton to android:background="#drawable/temp_ripple"
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatButton
android:id="#+id/btnLive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:background="#drawable/temp_ripple"
android:foreground="?selectableItemBackground"
android:text="12.5k Live"
android:textColor="#android:color/white" />
</RelativeLayout>
3) Get the ripple drawable from the view and create a runnable running after 2 sec to repeat the animation by setting the states of the ripple drawable in click listener of the button
package com.example.android.treasureHunt
import android.content.res.ColorStateList
import android.graphics.drawable.RippleDrawable
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.MotionEvent
import android.view.View
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.temp_activity.*
class TempActivity : AppCompatActivity(R.layout.temp_activity) {
val handler = Handler()
lateinit var runnable: Runnable
var count = 0
lateinit var rippleDrawable: RippleDrawable
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
rippleDrawable = btnLive.background as RippleDrawable
setLiveCountListener()
btnLive.setOnClickListener {
Log.d("TAG++", "button clicked")
rippleDrawable.state = intArrayOf(
android.R.attr.state_pressed,
android.R.attr.state_enabled
)
}
}
private fun setLiveCountListener() {
runnable = Runnable {
rippleDrawable.state = intArrayOf()
btnLive.performClick()
//to perform another runnable after some time creating a race condition
handler.postDelayed(runnable, 2000)
//condition to breakout from loop
if (count == 10) {
handler.removeCallbacks(runnable)
}
Log.d("TAG++", "Loop running")
}
//trigger the start of the ui thread
handler.postDelayed(runnable, 2000)
}
}
After taking many hours I have created custom class for infinite ripple view as per you want using this lib with customisation.
InfiniteRippleLayout
public class InfiniteRippleLayout extends FrameLayout {
/**
* Author:Hardik Talaviya
* Date: 2020.02.15 1:30 PM
* Describe:
*/
private static final int DEFAULT_DURATION = 350;
private static final int DEFAULT_FADE_DURATION = 75;
private static final float DEFAULT_ALPHA = 0.2f;
private static final int DEFAULT_COLOR = Color.BLACK;
private static final int DEFAULT_BACKGROUND = Color.TRANSPARENT;
private static final boolean DEFAULT_DELAY_CLICK = true;
private static final boolean DEFAULT_PERSISTENT = false;
private static final boolean DEFAULT_SEARCH_ADAPTER = false;
private static final boolean DEFAULT_RIPPLE_OVERLAY = false;
private static final int DEFAULT_ROUNDED_CORNERS = 0;
private static final int FADE_EXTRA_DELAY = 50;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Rect bounds = new Rect();
private int rippleColor;
private boolean rippleOverlay;
private int rippleDuration;
private int rippleAlpha;
private boolean rippleDelayClick;
private int rippleFadeDuration;
private boolean ripplePersistent;
private Drawable rippleBackground;
private boolean rippleInAdapter;
private float rippleRoundedCorners;
private float radius;
private AdapterView parentAdapter;
private View childView;
private AnimatorSet rippleAnimator;
private Point currentCoords = new Point();
private int layerType;
private int positionInAdapter;
/*
* Animations
*/
private Property<InfiniteRippleLayout, Float> radiusProperty
= new Property<InfiniteRippleLayout, Float>(Float.class, "radius") {
#Override
public Float get(InfiniteRippleLayout object) {
return object.getRadius();
}
#Override
public void set(InfiniteRippleLayout object, Float value) {
object.setRadius(value);
}
};
private Property<InfiniteRippleLayout, Integer> circleAlphaProperty
= new Property<InfiniteRippleLayout, Integer>(Integer.class, "rippleAlpha") {
#Override
public Integer get(InfiniteRippleLayout object) {
return object.getRippleAlpha();
}
#Override
public void set(InfiniteRippleLayout object, Integer value) {
object.setRippleAlpha(value);
}
};
public InfiniteRippleLayout(Context context) {
this(context, null, 0);
}
public InfiniteRippleLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public InfiniteRippleLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setWillNotDraw(false);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.InfiniteRippleLayout);
rippleColor = a.getColor(R.styleable.InfiniteRippleLayout_mrl_rippleColor, DEFAULT_COLOR);
rippleOverlay = a.getBoolean(R.styleable.InfiniteRippleLayout_mrl_rippleOverlay, DEFAULT_RIPPLE_OVERLAY);
rippleDuration = a.getInt(R.styleable.InfiniteRippleLayout_mrl_rippleDuration, DEFAULT_DURATION);
rippleAlpha = (int) (255 * a.getFloat(R.styleable.InfiniteRippleLayout_mrl_rippleAlpha, DEFAULT_ALPHA));
rippleDelayClick = a.getBoolean(R.styleable.InfiniteRippleLayout_mrl_rippleDelayClick, DEFAULT_DELAY_CLICK);
rippleFadeDuration = a.getInteger(R.styleable.InfiniteRippleLayout_mrl_rippleFadeDuration, DEFAULT_FADE_DURATION);
rippleBackground = new ColorDrawable(a.getColor(R.styleable.InfiniteRippleLayout_mrl_rippleBackground, DEFAULT_BACKGROUND));
ripplePersistent = a.getBoolean(R.styleable.InfiniteRippleLayout_mrl_ripplePersistent, DEFAULT_PERSISTENT);
rippleInAdapter = a.getBoolean(R.styleable.InfiniteRippleLayout_mrl_rippleInAdapter, DEFAULT_SEARCH_ADAPTER);
rippleRoundedCorners = a.getDimensionPixelSize(R.styleable.InfiniteRippleLayout_mrl_rippleRoundedCorners, DEFAULT_ROUNDED_CORNERS);
a.recycle();
paint.setColor(rippleColor);
paint.setAlpha(rippleAlpha);
enableClipPathSupportIfNecessary();
startRipple();
}
#Override
public final void addView(View child, int index, ViewGroup.LayoutParams params) {
if (getChildCount() > 0) {
throw new IllegalStateException("MaterialRippleLayout can host only one child");
}
//noinspection unchecked
childView = child;
super.addView(child, index, params);
}
#Override
public void setOnClickListener(OnClickListener onClickListener) {
if (childView == null) {
throw new IllegalStateException("MaterialRippleLayout must have a child view to handle clicks");
}
childView.setOnClickListener(onClickListener);
}
#Override
public void setOnLongClickListener(OnLongClickListener onClickListener) {
if (childView == null) {
throw new IllegalStateException("MaterialRippleLayout must have a child view to handle clicks");
}
childView.setOnLongClickListener(onClickListener);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return !findClickableViewInChild(childView, (int) event.getX(), (int) event.getY());
}
private void startRipple() {
float endRadius = getEndRadius();
cancelAnimations();
rippleAnimator = new AnimatorSet();
rippleAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
if (!ripplePersistent) {
setRadius(0);
setRippleAlpha(rippleAlpha);
}
if (rippleDelayClick) {
startRipple();
}
childView.setPressed(false);
}
});
ObjectAnimator ripple = ObjectAnimator.ofFloat(this, radiusProperty, radius, endRadius);
ripple.setDuration(rippleDuration);
ripple.setInterpolator(new DecelerateInterpolator());
ObjectAnimator fade = ObjectAnimator.ofInt(this, circleAlphaProperty, rippleAlpha, 0);
fade.setDuration(rippleFadeDuration);
fade.setInterpolator(new AccelerateInterpolator());
fade.setStartDelay(rippleDuration - rippleFadeDuration - FADE_EXTRA_DELAY);
if (ripplePersistent) {
rippleAnimator.play(ripple);
} else if (getRadius() > endRadius) {
fade.setStartDelay(0);
rippleAnimator.play(fade);
} else {
rippleAnimator.playTogether(ripple, fade);
}
rippleAnimator.start();
}
private void cancelAnimations() {
if (rippleAnimator != null) {
rippleAnimator.cancel();
rippleAnimator.removeAllListeners();
}
}
private float getEndRadius() {
final int width = getWidth();
final int height = getHeight();
final int halfWidth = width / 2;
final int halfHeight = height / 2;
final float radiusX = halfWidth > currentCoords.x ? width - currentCoords.x : currentCoords.x;
final float radiusY = halfHeight > currentCoords.y ? height - currentCoords.y : currentCoords.y;
return (float) Math.sqrt(Math.pow(radiusX, 2) + Math.pow(radiusY, 2)) * 1.2f;
}
private AdapterView findParentAdapterView() {
if (parentAdapter != null) {
return parentAdapter;
}
ViewParent current = getParent();
while (true) {
if (current instanceof AdapterView) {
parentAdapter = (AdapterView) current;
return parentAdapter;
} else {
try {
current = current.getParent();
} catch (NullPointerException npe) {
throw new RuntimeException("Could not find a parent AdapterView");
}
}
}
}
private boolean adapterPositionChanged() {
if (rippleInAdapter) {
int newPosition = findParentAdapterView().getPositionForView(InfiniteRippleLayout.this);
final boolean changed = newPosition != positionInAdapter;
positionInAdapter = newPosition;
if (changed) {
cancelAnimations();
childView.setPressed(false);
setRadius(0);
}
return changed;
}
return false;
}
private boolean findClickableViewInChild(View view, int x, int y) {
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
final Rect rect = new Rect();
child.getHitRect(rect);
final boolean contains = rect.contains(x, y);
if (contains) {
return findClickableViewInChild(child, x - rect.left, y - rect.top);
}
}
} else if (view != childView) {
return (view.isEnabled() && (view.isClickable() || view.isLongClickable() || view.isFocusableInTouchMode()));
}
return view.isFocusableInTouchMode();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
bounds.set(0, 0, w, h);
rippleBackground.setBounds(bounds);
}
#Override
public boolean isInEditMode() {
return true;
}
/*
* Drawing
*/
#Override
public void draw(Canvas canvas) {
final boolean positionChanged = adapterPositionChanged();
currentCoords = new Point(getWidth() / 2, getHeight() / 2);
if (rippleOverlay) {
if (!positionChanged) {
rippleBackground.draw(canvas);
}
super.draw(canvas);
if (!positionChanged) {
if (rippleRoundedCorners != 0) {
Path clipPath = new Path();
RectF rect = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
clipPath.addRoundRect(rect, rippleRoundedCorners, rippleRoundedCorners, Path.Direction.CW);
canvas.clipPath(clipPath);
}
canvas.drawCircle(currentCoords.x, currentCoords.y, radius, paint);
}
} else {
if (!positionChanged) {
rippleBackground.draw(canvas);
canvas.drawCircle(currentCoords.x, currentCoords.y, radius, paint);
}
super.draw(canvas);
}
}
private float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
invalidate();
}
public int getRippleAlpha() {
return paint.getAlpha();
}
public void setRippleAlpha(Integer rippleAlpha) {
paint.setAlpha(rippleAlpha);
invalidate();
}
/**
* {#link Canvas#clipPath(Path)} is not supported in hardware accelerated layers
* before API 18. Use software layer instead
* <p/>
* https://developer.android.com/guide/topics/graphics/hardware-accel.html#unsupported
*/
private void enableClipPathSupportIfNecessary() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (rippleRoundedCorners != 0) {
layerType = getLayerType();
setLayerType(LAYER_TYPE_SOFTWARE, null);
} else {
setLayerType(layerType, null);
}
}
}
}
attributes.xml
Add this attributes in your res->values->attributes.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="InfiniteRippleLayout">
<attr name="mrl_rippleColor" format="color" localization="suggested" />
<attr name="mrl_rippleOverlay" format="boolean" localization="suggested" />
<attr name="mrl_rippleAlpha" format="float" localization="suggested" />
<attr name="mrl_rippleDuration" format="integer" localization="suggested" />
<attr name="mrl_rippleFadeDuration" format="integer" localization="suggested" />
<attr name="mrl_rippleBackground" format="color" localization="suggested" />
<attr name="mrl_rippleDelayClick" format="boolean" localization="suggested" />
<attr name="mrl_ripplePersistent" format="boolean" localization="suggested" />
<attr name="mrl_rippleInAdapter" format="boolean" localization="suggested" />
<attr name="mrl_rippleRoundedCorners" format="dimension" localization="suggested" />
</declare-styleable>
</resources>
Add effect using below xml code
<com.broooapps.curvegraphview.InfiniteRippleLayout
android:layout_width="match_parent"
android:layout_height="150dp"
app:mrl_rippleAlpha="0.2"
app:mrl_rippleColor="#585858"
app:mrl_rippleDelayClick="true"
app:mrl_rippleDuration="1100"
app:mrl_rippleOverlay="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="HARDIK TALAVIYA"
android:textColor="#000"
android:textSize="20sp" />
</com.broooapps.curvegraphview.InfiniteRippleLayout>
Result
I hope this can help you!

Ripple effect over shape background on Button

[Please note that I use Xamarin.Droid which is a cross-platform framework using Mono.NET C#, the code is really close to Android's Java and I do accept Java responses as it is easy to translate to C# ]
I subclassed a Button and I'm applying a shape with a color and a leftDrawable. Everything is working fine except the fact that I have lost the Ripple effect when the button is pressed.
I've tried to add ?attr/selectableItemBackground but with code since I don't have any XML for this subclass, and the ripple effect still doesn't show up.
Here's my button subclass
public class LTButton : Android.Support.V7.Widget.AppCompatButton
{
Context context;
public LTButton(Context pContext, IAttributeSet attrs) : base(pContext, attrs)
{
context = pContext;
Elevation = 0;
}
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Init();
}
void Init()
{
// Adding a left drawable
Drawable img = ContextCompat.GetDrawable(context, Resource.Drawable.chevron_right);
img.SetBounds(0, 0, 70, 70);
SetCompoundDrawables(img, null, null, null);
TextSize = 16;
// The shape with background color and rounded corners
var shape = new GradientDrawable();
// This is a full rounded button
shape.SetCornerRadius(Height / 2);
shape.SetColor(ContextCompat.GetColor(context, Resource.Color.LTGreen));
Background = shape;
// Foreground => To have a ripple effect
int[] attrs = { Android.Resource.Attribute.SelectableItemBackground };
TypedArray ta = context.ObtainStyledAttributes(attrs);
Drawable selectableDrawable = ta.GetDrawable(0);
ta.Recycle();
Foreground = selectableDrawable;
}
}
Create an XML file for a ripple drawable something like this :
my_button_background_v21.xml
<?xml version="1.0" encoding="UTF-8" ?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:color="#color/black"
tools:targetApi="lollipop">
<item>
<shape android:shape="rectangle">
<corners android:radius="100dp" />
<solid android:color="#color/LTGreen" />
</shape>
</item>
<item android:id="#android:id/mask">
<shape android:shape="rectangle">
<corners android:radius="100dp" />
<solid android:color="#color/black" />
</shape>
</item>
Then in your custom button's onDraw method add the ripple effect something like this:
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Drawable img = ContextCompat.GetDrawable(context, Resource.Drawable.chevron_right);
img.SetBounds(0, 0, 70, 70);
SetCompoundDrawables(img, null, null, null);
TextSize = 16;
if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop)
{
SetBackgroundResource(Resource.Layout.my_button_background);
}
else
{
SetBackgroundResource(Resource.Layout.my_button_background_v21);
}
}
Use java you can do like this:
Custom a RippleButton
public class RippleButton extends Button {
private RippleDrawable rippleDrawable;
private Paint paint;
public RippleButton(Context context) {
this(context,null);
}
public RippleButton(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public RippleButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
rippleDrawable = new RippleDrawable();
//Set refresh interface, View has been implemented ---> source button inheritance child drawable.callback
rippleDrawable.setCallback(this);
//rippleDrawable
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//Set the refresh area ---> source code
rippleDrawable.setBounds(0,0,getWidth(),getHeight());
}
#Override
protected boolean verifyDrawable(Drawable who) {
return who == rippleDrawable || super.verifyDrawable(who);
}
#Override
protected void onDraw(Canvas canvas) {
rippleDrawable.draw(canvas);
super.onDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
rippleDrawable.onTouch(event);
return true;
}
}
Custom a RippleDrawable :
public class RippleDrawable extends Drawable {
private Paint mPaint;
private Bitmap bitmap;
private int rippleColor;
private float mRipplePointX = 0;
private float mRipplePointY = 0;
private float mRippleRadius = 0;
private int mAlpha = 200;
private float mCenterPointX,mCenterPointY;
private float mClickPointX;
private float mClickPointY;
//Maximum radius
private float MaxRadius;
//Starting radius
private float startRadius;
//End radius
private float endRadius;
//Record whether to raise your finger--->boolean
private boolean mUpDone;
//Record whether the animation is finished
private boolean mEnterDone;
/**Enter animation*/
//Enter the progress value of the animation
private float mProgress;
//Each incremental time
private float mEnterIncrement = 16f/360;
//Enter the animation add interpolator
private DecelerateInterpolator mEnterInterpolator = new DecelerateInterpolator(2);
private Runnable runnable = new Runnable() {
#Override
public void run() {
mEnterDone = false;
mCircleAlpha = 255;
mProgress = mProgress + mEnterIncrement;
if (mProgress > 1){
onEnterPrograss(1);
enterDone();
return;
}
float interpolation = mEnterInterpolator.getInterpolation(mProgress);
onEnterPrograss(interpolation);
scheduleSelf(this, SystemClock.uptimeMillis() + 16);
}
};
/**How to enter the animation refresh
* #parms realProgress */
public void onEnterPrograss(float realPrograss){
mRippleRadius = getCenter(startRadius,endRadius,realPrograss);
mRipplePointX = getCenter(mClickPointX,mCenterPointX,realPrograss);
mRipplePointY = getCenter(mClickPointY,mCenterPointY,realPrograss);
mBgAlpha = (int) getCenter(0,182,realPrograss);
invalidateSelf();
}
private void enterDone() {
mEnterDone = true;
if(mUpDone)
startExitRunnable();
}
/**Exit animation*/
//Exit the progress value of the animation
private float mExitProgress;
//Each incremental time
private float mExitIncrement = 16f/280;
//Exit animation add interpolator
private AccelerateInterpolator mExitInterpolator = new AccelerateInterpolator(2);
private Runnable exitRunnable = new Runnable() {
#Override
public void run() {
if (!mEnterDone){
return;
}
mExitProgress = mExitProgress + mExitIncrement;
if (mExitProgress > 1){
onExitPrograss(1);
exitDone();
return;
}
float interpolation = mExitInterpolator.getInterpolation(mExitProgress);
onExitPrograss(interpolation);
scheduleSelf(this, SystemClock.uptimeMillis() + 16);
}
};
/**Exit animation refresh method
* #parms realProgress */
public void onExitPrograss(float realPrograss){
//Set background
mBgAlpha = (int) getCenter(182,0,realPrograss);
//Set the circular area
mCircleAlpha = (int) getCenter(255,0,realPrograss);
invalidateSelf();
}
private void exitDone() {
mEnterDone = false;
}
//Set the gradient effect including radius / bg color / center position, etc.
public float getCenter(float start,float end,float prograss){
return start + (end - start)*prograss;
}
public RippleDrawable() {
//Anti-aliased brush
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//Set anti-aliasing
mPaint.setAntiAlias(true);
//Set anti-shake
mPaint.setDither(true);
setRippleColor(0x60000000);
}
//private int mPaintAlpha = 255;
//Background transparency
private int mBgAlpha;
//Transparency of the circular area
private int mCircleAlpha;
#Override
public void draw(Canvas canvas) {
//Get the transparency of user settings
int preAlpha = mPaint.getAlpha();
//current background
int bgAlpha = (int) (preAlpha * (mBgAlpha/255f));
//bg + prebg Operational background
int maxCircleAlpha = getCircleAlpha(preAlpha,bgAlpha);
int circleAlpha = (int) (maxCircleAlpha * (mCircleAlpha/255f));
mPaint.setAlpha(bgAlpha);
canvas.drawColor(mPaint.getColor());
mPaint.setAlpha(circleAlpha);
canvas.drawCircle(mRipplePointX,mRipplePointY,mRippleRadius,mPaint);
//Set the initial transparency to ensure that the next entry into the operation will not go wrong
mPaint.setAlpha(preAlpha);
}
public int getCircleAlpha(int preAlpha,int bgAlpha){
int dAlpha = preAlpha - bgAlpha;
return (int) ((dAlpha*255f)/(255f - bgAlpha));
}
public void onTouch(MotionEvent event){
switch (event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
//press
mClickPointX = event.getX();
mClickPointY = event.getY();
onTouchDown(mClickPointX, mClickPointY);
break;
case MotionEvent.ACTION_MOVE:
//move
//onTouchMove(moveX,moveY);
break;
case MotionEvent.ACTION_UP:
//up
onTouchUp();
break;
case MotionEvent.ACTION_CANCEL:
//exit
//onTouchCancel();
break;
}
}
public void onTouchDown(float x,float y){
//Log.i("onTouchDown====",x + "" + y );
//unscheduleSelf(runnable);
mUpDone = false;
mRipplePointX = x;
mRipplePointY = y;
mRippleRadius = 0;
startEnterRunnable();
}
public void onTouchMove(float x,float y){
}
public void onTouchUp(){
mUpDone = true;
if (mEnterDone){
startExitRunnable();
}
}
public void onTouchCancel(){
mUpDone = true;
if (mEnterDone){
startExitRunnable();
}
}
/**
* Open to enter animation
* */
public void startEnterRunnable(){
mProgress = 0;
//mEnterDone = false;
unscheduleSelf(exitRunnable);
unscheduleSelf(runnable);
scheduleSelf(runnable,SystemClock.uptimeMillis());
}
/**
* Open exit animation
* */
public void startExitRunnable(){
mExitProgress = 0;
unscheduleSelf(runnable);
unscheduleSelf(exitRunnable);
scheduleSelf(exitRunnable,SystemClock.uptimeMillis());
}
public int changeColorAlpha(int color,int alpha){
//Set transparency
int a = (color >> 24) & 0xFF;
a = a * alpha/255;
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
int argb = Color.argb(a, red, green, blue);
return argb;
}
//Take the center point of the drawn area
#Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mCenterPointX = bounds.centerX();
mCenterPointY = bounds.centerY();
MaxRadius = Math.max(mCenterPointX,mCenterPointY);
startRadius = MaxRadius * 0.1f;
endRadius = MaxRadius * 0.8f;
}
#Override
public void setAlpha(int alpha) {
mAlpha = alpha;
onColorOrAlphaChange();
}
#Override
public int getAlpha() {
return mAlpha;
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
//Filter effect
if (mPaint.getColorFilter() != colorFilter){
mPaint.setColorFilter(colorFilter);
invalidateSelf();
}
}
#Override
public int getOpacity() {
//Return transparency
if (mAlpha == 255){
return PixelFormat.OPAQUE;
}else if (mAlpha == 0){
return PixelFormat.TRANSPARENT;
}else{
return PixelFormat.TRANSLUCENT;
}
}
public void setRippleColor(int rippleColor) {
this.rippleColor = rippleColor;
onColorOrAlphaChange();
}
private void onColorOrAlphaChange() {
//Set brush color
mPaint.setColor(rippleColor);
if (mAlpha != 255){
int pAlpha = mPaint.getAlpha();
int realAlpha = (int) (pAlpha * (mAlpha/255f));
//Set transparency
mPaint.setAlpha(realAlpha);
}
invalidateSelf();
}
}
If be helpful ,please mark answer to help others.
Thanks to #G.Hakim and this thread, I was able to reproduce my background, and add the ripple effect with just XML.
my_button_background.xml
<?xml version="1.0" encoding="UTF-8" ?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:color="#color/black"
tools:targetApi="lollipop">
<item>
<shape android:shape="rectangle">
<corners android:radius="100dp" />
<solid android:color="#color/LTGreen" />
</shape>
</item>
<item android:id="#android:id/mask">
<shape android:shape="rectangle">
<corners android:radius="100dp" />
<solid android:color="#color/black" />
</shape>
</item>
</ripple>
MyButton.cs
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Drawable img = ContextCompat.GetDrawable(context, Resource.Drawable.chevron_right);
img.SetBounds(0, 0, 70, 70);
SetCompoundDrawables(img, null, null, null);
TextSize = 16;
if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop)
{
SetBackgroundResource(Resource.Layout.my_button_background);
}
else
{
SetBackgroundResource(Resource.Layout.my_button_background_v21);
}
}

How can you compare two RippleDrawables?

I am using android Espresso. I would like to know how I can check if the drawable being used on a view is the same as what should be used as stated in the specs. I am trying to compare the ConstantStates of the drawable use on the view, and the one in the Resources, but I am not getting anywhere.
Is there a way to do this? Or is this check entirely not needed when it comes to automated testing?
For comparing two images I already used this code:
public class ImageComparator {
public static Matcher<View> withImageResource(final int imageResourceId) {
return new TypeSafeMatcher<View>() {
#Override
public void describeTo(Description description) {
description.appendText("with drawable from resource id: " + imageResourceId);
}
#Override
public boolean matchesSafely(View view) {
Drawable actualDrawable = ((ImageView) view).getDrawable();
final Drawable correctDrawable = view.getResources().getDrawable(imageResourceId);
if (actualDrawable == null) {
return correctDrawable == null;
}
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return areImagesTheSameAfterSdk21(actualDrawable, correctDrawable);
} else {
return areImagesTheSameBeforeSdk21(actualDrawable, correctDrawable);
}
}
};
}
protected static boolean areImagesTheSameBeforeSdk21(Drawable actualDrawable,
Drawable correctDrawable) {
Drawable.ConstantState actualDrawableConstantState = actualDrawable.getConstantState();
Drawable.ConstantState correctDrawableConstantState = correctDrawable.getConstantState();
return actualDrawableConstantState.equals(correctDrawableConstantState);
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
protected static boolean areImagesTheSameAfterSdk21(Drawable actualDrawable,
Drawable correctDrawable) {
Bitmap correctBitmap = drawableToBitmap(correctDrawable);
Bitmap actualBitmap = drawableToBitmap(actualDrawable);
return correctBitmap.sameAs(actualBitmap);
}
private static Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
Bitmap bitmap = Bitmap
.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
Hope it will help

Getting imageview size for Bitmap creation

I am a programmer with a Windows background and I am new to Java and Android stuff.
I want to create a widget (not an app) which displays a chart. After a long research I know I can do this with Canvas, imageviews and Bitmaps. The canvas which I paint on should be the same as the Widget Size.
How do I know the widget size (or imageview size) so that I can supply it to the function?
Bitmap.createBitmap(width_xx, height_yy, Config.ARGB_8888);
Code Snippet:
In the timer run method:
#Override
public void run() {
Bitmap bitmap = Bitmap.createBitmap(??, ??, Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
// Create a new paint
Paint p = new Paint();
p.setAntiAlias(true);
p.setStrokeWidth(1);
// Draw circle
// Here I can use the width and height to scale the circle
canvas.drawCircle(50, 50, 7, p);
remoteViews.setImageViewBitmap(R.id.imageView, bitmap);
From what I've learnt, you can only calculate widget dimensions on Android 4.1+.
When on a lower API, you'll have to use static dimensions.
About widget dimensions: App Widget Design Guidelines
int w = DEFAULT_WIDTH, h = DEFAULT_HEIGHT;
if ( Build.VERSION.SDK_INT >= 16 ) {
Bundle options = appWidgetManager.getAppWidgetOptions(widgetId);
int maxW = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH);
int maxH = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);
int minW = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
int minH = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
if ( context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ) {
w = maxW;
h = minH;
} else {
w = minW;
h = maxH;
}
}
Have a look at the method:
public void onAppWidgetOptionsChanged (Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions)
It will be called each time you start/resize the widget.
Getting the widget width/height can be done as follows:
newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)
newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)
newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)
I am currently using this:
private void run() {
int width = 400, height = 400;
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
Paint p = new Paint();
p.setColor(Color.WHITE);
p.setStyle(Paint.Style.STROKE);
p.setStrokeWidth(1);
p.setAntiAlias(true);
c.drawCircle(width/2, height/2, radius, p);
remoteViews.setImageViewBitmap(R.id.imageView, bitmap);
ComponentName clockWidget = new ComponentName(context,
Clock_22_analog.class);
AppWidgetManager appWidgetManager = AppWidgetManager
.getInstance(context);
appWidgetManager.updateAppWidget(clockWidget, remoteViews);
}
You can use this
Bitmap image1, image2;
Bitmap bitmap = Bitmap.createBitmap(image1.getWidth(), image1.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
You can create a custom widget and set the size of wight on its onMeasure() method. And also save the size at that time so that you can use it further for image creation...
I've not worked on Widgets, but I have some experience getting ImageView's size.
Here is some code I use:
public class ViewSizes {
public int width;
public int height;
public boolean isEmpty() {
boolean result = false;
if (0 >= width || 0 >= height) {
result = true;
}
return result;
}
}
That's just a dummy class containing the size parameters.
public static ViewSizes getSizes(View view) {
ViewSizes sizes = new ViewSizes();
sizes.width = view.getWidth();
sizes.height = view.getHeight();
if (sizes.isEmpty()) {
LayoutParams params = view.getLayoutParams();
if (null != params) {
int widthSpec = View.MeasureSpec.makeMeasureSpec(params.width, View.MeasureSpec.AT_MOST);
int heightSpec = View.MeasureSpec.makeMeasureSpec(params.height, View.MeasureSpec.AT_MOST);
view.measure(widthSpec, heightSpec);
}
sizes.width = view.getMeasuredWidth();
sizes.height = view.getMeasuredHeight();
}
return sizes;
}
This method calculates the width forcing a measure cycle if such has not already happened.
public static boolean loadPhoto(ImageView view, String url, float aspectRatio) {
boolean processed = false;
ViewSizes sizes = ViewsUtils.getSizes(view);
if (!sizes.isEmpty()) {
int width = sizes.width - 2;
int height = sizes.height - 2;
if (ASPECT_RATIO_UNDEFINED != aspectRatio) {
if (height * aspectRatio > width) {
height = (int) (width / aspectRatio);
} else if (height * aspectRatio < width) {
width = (int) (height * aspectRatio);
}
}
// Do you bitmap processing here
processed = true;
}
return processed;
}
This one is probably useless for you. I give just as an example - I have an ImageView and image URL, which should be parametrized with image and height.
public class PhotoLayoutListener implements OnGlobalLayoutListener {
private ImageView view;
private String url;
private float aspectRatio;
public PhotoLayoutListener(ImageView view, String url, float aspectRatio) {
this.view = view;
this.url = url;
this.aspectRatio = aspectRatio;
}
boolean handled = false;
#Override
public void onGlobalLayout() {
if (!handled) {
PhotoUtils.loadPhoto(view, url, aspectRatio);
handled = true;
}
ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
removeLayoutListenerPre16(viewTreeObserver, this);
} else {
removeLayoutListenerPost16(viewTreeObserver, this);
}
}
}
#SuppressWarnings("deprecation")
private void removeLayoutListenerPre16(ViewTreeObserver observer, OnGlobalLayoutListener listener){
observer.removeGlobalOnLayoutListener(listener);
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void removeLayoutListenerPost16(ViewTreeObserver observer, OnGlobalLayoutListener listener){
observer.removeOnGlobalLayoutListener(listener);
}
}
This is just a layout listener - I want to process the image loading once the layout phase has finished.
public static void setImage(ImageView view, String url, boolean forceLayoutLoading, float aspectRatio) {
if (null != view && null != url) {
final ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (forceLayoutLoading || !PhotoUtils.loadPhoto(view, url, aspectRatio)) {
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new PhotoLayoutListener(view, url, aspectRatio));
}
}
}
}
This is the method I actually call. I give it the view and URL. The methods takes care of loading - if it can determine the view's size it starts loading immediately. Otherwise it just assigns a layout listener and start the loading process once the layout is finished.
You could strip away some parameters - forceLoading / aspectRatio should be irrelevant for you. After that change the PhotoUtils.loadPhoto method in order to create the bitmap with the width / height it has calculated.
Like Julian told us, you can get them like that with a bitmap of your image:
int width = bitmap.getWidth();
int height = bitmap.getHeight();

overlay two images in android to set an imageview

I am trying to overlay two images in my app, but they seem to crash at my canvas.setBitmap() line. What am I doing wrong?
private void test() {
Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.t);
Bitmap mBitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.tt);
Bitmap bmOverlay = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), mBitmap.getConfig());
Canvas canvas = new Canvas();
canvas.setBitmap(bmOverlay);
canvas.drawBitmap(mBitmap, new Matrix(), null);
canvas.drawBitmap(mBitmap2, new Matrix(), null);
testimage.setImageBitmap(bmOverlay);
}
You can skip the complex Canvas manipulation and do this entirely with Drawables, using LayerDrawable. You have one of two choices: You can either define it in XML then simply set the image, or you can configure a LayerDrawable dynamically in code.
Solution #1 (via XML):
Create a new Drawable XML file, let's call it layer.xml:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/t" />
<item android:drawable="#drawable/tt" />
</layer-list>
Now set the image using that Drawable:
testimage.setImageDrawable(getResources().getDrawable(R.layout.layer));
Solution #2 (dynamic):
Resources r = getResources();
Drawable[] layers = new Drawable[2];
layers[0] = r.getDrawable(R.drawable.t);
layers[1] = r.getDrawable(R.drawable.tt);
LayerDrawable layerDrawable = new LayerDrawable(layers);
testimage.setImageDrawable(layerDrawable);
(I haven't tested this code so there may be a mistake, but this general outline should work.)
ok just so you know there is a program out there that's called DroidDraw. It can help you draw objects and try them one on top of the other. I tried your solution but I had animation under the smaller image so that didn't work. But then I tried to place one image in a relative layout that's suppose to be under first and then on top of that I drew the other image that is suppose to overlay and everything worked great. So RelativeLayout, DroidDraw and you are good to go :) Simple, no any kind of jiggery pockery :) and here is a bit of code for ya:
The logo is going to be on top of shazam background image.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="#+id/widget30"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<ImageView
android:id="#+id/widget39"
android:layout_width="219px"
android:layout_height="225px"
android:src="#drawable/shazam_bkgd"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
>
</ImageView>
<ImageView
android:id="#+id/widget37"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/shazam_logo"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
>
</ImageView>
</RelativeLayout>
You can use the code below to solve the problem or download demo here
Create two functions to handle each.
First, the canvas is drawn and the images are drawn on top of each other from point (0,0)
On button click
public void buttonMerge(View view) {
Bitmap bigImage = BitmapFactory.decodeResource(getResources(), R.drawable.img1);
Bitmap smallImage = BitmapFactory.decodeResource(getResources(), R.drawable.img2);
Bitmap mergedImages = createSingleImageFromMultipleImages(bigImage, smallImage);
img.setImageBitmap(mergedImages);
}
Function to create an overlay.
private Bitmap createSingleImageFromMultipleImages(Bitmap firstImage, Bitmap secondImage){
Bitmap result = Bitmap.createBitmap(firstImage.getWidth(), firstImage.getHeight(), firstImage.getConfig());
Canvas canvas = new Canvas(result);
canvas.drawBitmap(firstImage, 0f, 0f, null);
canvas.drawBitmap(secondImage, 10, 10, null);
return result;
}
Read more
Its a bit late answer, but it covers merging images from urls using Picasso
MergeImageView
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Build;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.widget.ImageView;
import com.squareup.picasso.Picasso;
import java.io.IOException;
import java.util.List;
public class MergeImageView extends ImageView {
private SparseArray<Bitmap> bitmaps = new SparseArray<>();
private Picasso picasso;
private final int DEFAULT_IMAGE_SIZE = 50;
private int MIN_IMAGE_SIZE = DEFAULT_IMAGE_SIZE;
private int MAX_WIDTH = DEFAULT_IMAGE_SIZE * 2, MAX_HEIGHT = DEFAULT_IMAGE_SIZE * 2;
private String picassoRequestTag = null;
public MergeImageView(Context context) {
super(context);
}
public MergeImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MergeImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MergeImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
public boolean isInEditMode() {
return true;
}
public void clearResources() {
if (bitmaps != null) {
for (int i = 0; i < bitmaps.size(); i++)
bitmaps.get(i).recycle();
bitmaps.clear();
}
// cancel picasso requests
if (picasso != null && AppUtils.ifNotNullEmpty(picassoRequestTag))
picasso.cancelTag(picassoRequestTag);
picasso = null;
bitmaps = null;
}
public void createMergedBitmap(Context context, List<String> imageUrls, String picassoTag) {
picasso = Picasso.with(context);
int count = imageUrls.size();
picassoRequestTag = picassoTag;
boolean isEven = count % 2 == 0;
// if url size are not even make MIN_IMAGE_SIZE even
MIN_IMAGE_SIZE = DEFAULT_IMAGE_SIZE + (isEven ? count / 2 : (count / 2) + 1);
// set MAX_WIDTH and MAX_HEIGHT to twice of MIN_IMAGE_SIZE
MAX_WIDTH = MAX_HEIGHT = MIN_IMAGE_SIZE * 2;
// in case of odd urls increase MAX_HEIGHT
if (!isEven) MAX_HEIGHT = MAX_WIDTH + MIN_IMAGE_SIZE;
// create default bitmap
Bitmap bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_wallpaper),
MIN_IMAGE_SIZE, MIN_IMAGE_SIZE, false);
// change default height (wrap_content) to MAX_HEIGHT
int height = Math.round(AppUtils.convertDpToPixel(MAX_HEIGHT, context));
setMinimumHeight(height * 2);
// start AsyncTask
for (int index = 0; index < count; index++) {
// put default bitmap as a place holder
bitmaps.put(index, bitmap);
new PicassoLoadImage(index, imageUrls.get(index)).execute();
// if you want parallel execution use
// new PicassoLoadImage(index, imageUrls.get(index)).(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private class PicassoLoadImage extends AsyncTask<String, Void, Bitmap> {
private int index = 0;
private String url;
PicassoLoadImage(int index, String url) {
this.index = index;
this.url = url;
}
#Override
protected Bitmap doInBackground(String... params) {
try {
// synchronous picasso call
return picasso.load(url).resize(MIN_IMAGE_SIZE, MIN_IMAGE_SIZE).tag(picassoRequestTag).get();
} catch (IOException e) {
}
return null;
}
#Override
protected void onPostExecute(Bitmap output) {
super.onPostExecute(output);
if (output != null)
bitmaps.put(index, output);
// create canvas
Bitmap.Config conf = Bitmap.Config.RGB_565;
Bitmap canvasBitmap = Bitmap.createBitmap(MAX_WIDTH, MAX_HEIGHT, conf);
Canvas canvas = new Canvas(canvasBitmap);
canvas.drawColor(Color.WHITE);
// if height and width are equal we have even images
boolean isEven = MAX_HEIGHT == MAX_WIDTH;
int imageSize = bitmaps.size();
int count = imageSize;
// we have odd images
if (!isEven) count = imageSize - 1;
for (int i = 0; i < count; i++) {
Bitmap bitmap = bitmaps.get(i);
canvas.drawBitmap(bitmap, bitmap.getWidth() * (i % 2), bitmap.getHeight() * (i / 2), null);
}
// if images are not even set last image width to MAX_WIDTH
if (!isEven) {
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmaps.get(count), MAX_WIDTH, MIN_IMAGE_SIZE, false);
canvas.drawBitmap(scaledBitmap, scaledBitmap.getWidth() * (count % 2), scaledBitmap.getHeight() * (count / 2), null);
}
// set bitmap
setImageBitmap(canvasBitmap);
}
}
}
xml
<com.example.MergeImageView
android:id="#+id/iv_thumb"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
Example
List<String> urls = new ArrayList<>();
String picassoTag = null;
// add your urls
((MergeImageView)findViewById(R.id.iv_thumb)).
createMergedBitmap(MainActivity.this, urls,picassoTag);
this is my solution:
public Bitmap Blend(Bitmap topImage1, Bitmap bottomImage1, PorterDuff.Mode Type) {
Bitmap workingBitmap = Bitmap.createBitmap(topImage1);
Bitmap topImage = workingBitmap.copy(Bitmap.Config.ARGB_8888, true);
Bitmap workingBitmap2 = Bitmap.createBitmap(bottomImage1);
Bitmap bottomImage = workingBitmap2.copy(Bitmap.Config.ARGB_8888, true);
Rect dest = new Rect(0, 0, bottomImage.getWidth(), bottomImage.getHeight());
new BitmapFactory.Options().inPreferredConfig = Bitmap.Config.ARGB_8888;
bottomImage.setHasAlpha(true);
Canvas canvas = new Canvas(bottomImage);
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(Type));
paint.setFilterBitmap(true);
canvas.drawBitmap(topImage, null, dest, paint);
return bottomImage;
}
usage :
imageView.setImageBitmap(Blend(topBitmap, bottomBitmap, PorterDuff.Mode.SCREEN));
or
imageView.setImageBitmap(Blend(topBitmap, bottomBitmap, PorterDuff.Mode.OVERLAY));
and the results :
Overlay mode :
Screen mode:

Categories

Resources