-
-
Save kevinvanmierlo/c46f66027e3ae37ebea85a8d2e12aaba to your computer and use it in GitHub Desktop.
<?xml version="1.0" encoding="utf-8"?> | |
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent"> | |
<ImageView | |
android:id="@+id/image" | |
android:layout_width="match_parent" | |
android:layout_height="200dp" | |
android:layout_gravity="center" | |
android:scaleType="centerCrop" | |
android:background="#000000"/> | |
</FrameLayout> |
package nl.kevinvanmierlo.testtransitiondrawable; | |
import android.graphics.Canvas; | |
import android.graphics.ColorFilter; | |
import android.graphics.PixelFormat; | |
import android.graphics.Rect; | |
import android.graphics.drawable.Drawable; | |
import android.os.SystemClock; | |
import androidx.annotation.NonNull; | |
import androidx.annotation.Nullable; | |
public class CrossFadeDrawable extends Drawable implements Drawable.Callback { | |
private Drawable previousDrawable; | |
private Drawable currentDrawable; | |
private float fadeDuration = 300f; | |
private long startTimeMillis = 0L; | |
private boolean animating = false; | |
private int alpha = 0xFF; | |
private boolean crossFade = false; | |
public CrossFadeDrawable(@NonNull Drawable previousDrawable, @NonNull Drawable currentDrawable) { | |
this.previousDrawable = previousDrawable; | |
this.currentDrawable = currentDrawable; | |
previousDrawable.setCallback(this); | |
currentDrawable.setCallback(this); | |
} | |
public void startTransition(float duration) { | |
fadeDuration = duration; | |
startTransition(); | |
} | |
public void startTransition() { | |
animating = true; | |
startTimeMillis = SystemClock.uptimeMillis(); | |
invalidateSelf(); | |
} | |
private float getNormalizedTime() { | |
if(startTimeMillis == 0L) { | |
return 0f; | |
} | |
return Math.min(1f, (SystemClock.uptimeMillis() - startTimeMillis) / fadeDuration); | |
} | |
@Override | |
public void draw(@NonNull Canvas canvas) { | |
if(!animating) { | |
if(previousDrawable != null) { | |
previousDrawable.draw(canvas); | |
} else { | |
currentDrawable.draw(canvas); | |
} | |
} else { | |
float normalized = getNormalizedTime(); | |
if (normalized >= 1f) { | |
previousDrawable.setCallback(null); | |
animating = false; | |
previousDrawable = null; | |
currentDrawable.draw(canvas); | |
} else { | |
if(crossFade) { | |
int partialAlpha = (int) (alpha * normalized); | |
if (previousDrawable != null) { | |
previousDrawable.setAlpha(255 - partialAlpha); | |
previousDrawable.draw(canvas); | |
previousDrawable.setAlpha(alpha); | |
} | |
currentDrawable.setAlpha(partialAlpha); | |
currentDrawable.draw(canvas); | |
currentDrawable.setAlpha(alpha); | |
} else { | |
if(previousDrawable != null) { | |
previousDrawable.draw(canvas); | |
} else { | |
currentDrawable.draw(canvas); | |
} | |
} | |
invalidateSelf(); | |
} | |
} | |
} | |
@Override | |
public void setAlpha(int alpha) { | |
this.alpha = alpha; | |
} | |
@Override | |
public int getAlpha() { | |
return this.alpha; | |
} | |
@Override | |
public void setColorFilter(@Nullable ColorFilter colorFilter) { | |
} | |
@Override | |
protected void onBoundsChange(Rect bounds) { | |
super.onBoundsChange(bounds); | |
if(previousDrawable != null) { | |
previousDrawable.setBounds(bounds); | |
} | |
if(currentDrawable != null) { | |
currentDrawable.setBounds(bounds); | |
} | |
} | |
@Override | |
public int getIntrinsicWidth() { | |
if(!animating && previousDrawable != null) { | |
return previousDrawable.getIntrinsicWidth(); | |
} else { | |
return currentDrawable.getIntrinsicWidth(); | |
} | |
} | |
@Override | |
public int getIntrinsicHeight() { | |
if(!animating && previousDrawable != null) { | |
return previousDrawable.getIntrinsicHeight(); | |
} else { | |
return currentDrawable.getIntrinsicHeight(); | |
} | |
} | |
@Override | |
public int getOpacity() { | |
return PixelFormat.TRANSPARENT; | |
} | |
/** | |
* Enables or disables the cross fade of the drawables. When cross fade | |
* is disabled, the first drawable is always drawn opaque. With cross | |
* fade enabled, the first drawable is drawn with the opposite alpha of | |
* the second drawable. Cross fade is disabled by default. | |
* | |
* @param enabled True to enable cross fading, false otherwise. | |
*/ | |
public void setCrossFadeEnabled(boolean enabled) { | |
crossFade = enabled; | |
} | |
/** | |
* Indicates whether the cross fade is enabled for this transition. | |
* | |
* @return True if cross fading is enabled, false otherwise. | |
*/ | |
public boolean isCrossFadeEnabled() { | |
return crossFade; | |
} | |
@Override | |
public void invalidateDrawable(@NonNull Drawable who) { | |
invalidateSelf(); | |
} | |
@Override | |
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { | |
scheduleSelf(what, when); | |
} | |
@Override | |
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { | |
unscheduleSelf(what); | |
} | |
} |
package nl.kevinvanmierlo.testtransitiondrawable; | |
import android.graphics.drawable.Drawable; | |
import com.bumptech.glide.load.DataSource; | |
import com.bumptech.glide.request.transition.NoTransition; | |
import com.bumptech.glide.request.transition.Transition; | |
import com.bumptech.glide.request.transition.TransitionFactory; | |
public class CrossFadeFactory implements TransitionFactory<Drawable> { | |
@Override | |
public Transition<Drawable> build(DataSource dataSource, boolean isFirstResource) { | |
if(dataSource == DataSource.MEMORY_CACHE) { | |
return NoTransition.get(); | |
} | |
return new CrossFadeTransition(); | |
} | |
} |
package nl.kevinvanmierlo.testtransitiondrawable; | |
import android.graphics.Color; | |
import android.graphics.drawable.ColorDrawable; | |
import android.graphics.drawable.Drawable; | |
import com.bumptech.glide.request.transition.Transition; | |
public class CrossFadeTransition implements Transition<Drawable> { | |
@Override | |
public boolean transition(Drawable current, ViewAdapter adapter) { | |
Drawable previous = adapter.getCurrentDrawable(); | |
if(previous == null) { | |
previous = new ColorDrawable(Color.TRANSPARENT); | |
} | |
CrossFadeDrawable crossFadeDrawable = new CrossFadeDrawable(previous, current); | |
crossFadeDrawable.setCrossFadeEnabled(true); | |
crossFadeDrawable.startTransition(); | |
adapter.setDrawable(crossFadeDrawable); | |
return true; | |
} | |
} |
package nl.kevinvanmierlo.testtransitiondrawable; | |
import android.os.Bundle; | |
import android.widget.ImageView; | |
import androidx.annotation.Nullable; | |
import androidx.appcompat.app.AppCompatActivity; | |
import com.bumptech.glide.Glide; | |
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; | |
public class MainActivity extends AppCompatActivity { | |
@Override | |
protected void onCreate(@Nullable Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
ImageView imageView = findViewById(R.id.image); | |
Glide.with(this) | |
.load("landscapeurl") | |
.centerCrop() | |
.placeholder(R.drawable.portrait_placeholder) | |
.transition(DrawableTransitionOptions.with(new CrossFadeFactory())) | |
.into(imageView); | |
} | |
} |
@gabin8 You're right. I'm looking into it when I have time, but could not get it working this quickly. But it has something to do with GifDrawable which implements Animatable and the custom classes not doing anything with it. I've looked into the original crossfade factory, transition and TransitionDrawable. The animation is probably started from ImageViewTarget, but I haven't found how it should work, because TransitionDrawable is not Animatable.
@gabin8 Found out what's wrong, update the gist and GIF's should be working now! I'll also update my pull request with the new code.
@gabin8 Found out what's wrong, update the gist and GIF's should be working now! I'll also update my pull request with the new code.
Thanks! The gif playing works fine . I would also recommend you to add null checks to onBoundsChange method:
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
if (previousDrawable != null && currentDrawable != null) {
previousDrawable.setBounds(bounds);
currentDrawable.setBounds(bounds);
}
}
because I've caught some NPE in some cases
@gabin8 Yes you're right! I've written it in Kotlin first, so it already had nullable checks, but forgot to add it in Java. Probably should add null checks in the constructor (since neither can be null) and in the onBoundsChange for the previousDrawable (since that's the only drawable that can get null after the animation is finished). I'll see when I have time to add it here and in the current pull request.
@gabin8 I updated the file, so it checks for null. I also added a NonNull annotation to the constructor to show that both values cannot be null.
Hi @kevinvanmierlo
I've tried your gist for GIF's and they stopped playing. Is there something wrong?