From 750502517c117f7e60e714b90eba4b5b2720ec60 Mon Sep 17 00:00:00 2001 From: Ozodrukh Date: Thu, 16 Mar 2017 14:52:27 +0500 Subject: [PATCH] Extension API improved to extend ViewRevealManagers added ViewTransformation API with default PathTransformation implementation for view clipping added ability to change ViewRevealManager in RevealViewGroup#setViewRevealManager(ViewRevealManager) added ability to change transformation method using ViewRevealManager#setViewTransformation(ViewTransformation) --- circualreveal/build.gradle | 10 +- .../codetail/animation/RevealViewGroup.java | 6 + .../animation/ViewAnimationUtils.java | 12 +- .../codetail/animation/ViewRevealManager.java | 175 ++++++++++++------ .../io/codetail/widget/RevealFrameLayout.java | 14 +- .../codetail/widget/RevealLinearLayout.java | 8 + 6 files changed, 153 insertions(+), 72 deletions(-) diff --git a/circualreveal/build.gradle b/circualreveal/build.gradle index 9561d03..3bfcbd3 100644 --- a/circualreveal/build.gradle +++ b/circualreveal/build.gradle @@ -1,15 +1,15 @@ apply plugin: 'com.android.library' -apply plugin: 'com.github.dcendents.android-maven' +apply plugin: 'maven' group = 'com.github.ozodrukh' android { - compileSdkVersion 23 - buildToolsVersion "23.0.3" + compileSdkVersion project.compileSDKVersion + buildToolsVersion project.buildToolsVersion defaultConfig { - minSdkVersion 15 - targetSdkVersion 23 + minSdkVersion project.minSDKVersion + targetSdkVersion project.targetSDKVersion } } diff --git a/circualreveal/src/main/java/io/codetail/animation/RevealViewGroup.java b/circualreveal/src/main/java/io/codetail/animation/RevealViewGroup.java index 858e924..ccd5efc 100644 --- a/circualreveal/src/main/java/io/codetail/animation/RevealViewGroup.java +++ b/circualreveal/src/main/java/io/codetail/animation/RevealViewGroup.java @@ -12,4 +12,10 @@ public interface RevealViewGroup { * @return Bridge between view and circular reveal animation */ ViewRevealManager getViewRevealManager(); + + /** + * + * @param manager + */ + void setViewRevealManager(ViewRevealManager manager); } \ No newline at end of file diff --git a/circualreveal/src/main/java/io/codetail/animation/ViewAnimationUtils.java b/circualreveal/src/main/java/io/codetail/animation/ViewAnimationUtils.java index 064699a..4b6aff3 100644 --- a/circualreveal/src/main/java/io/codetail/animation/ViewAnimationUtils.java +++ b/circualreveal/src/main/java/io/codetail/animation/ViewAnimationUtils.java @@ -1,7 +1,6 @@ package io.codetail.animation; import android.animation.Animator; -import android.animation.ObjectAnimator; import android.view.View; import io.codetail.animation.ViewRevealManager.ChangeViewLayerTypeAdapter; import io.codetail.view.BuildConfig; @@ -67,20 +66,21 @@ public static Animator createCircularReveal(View view, int centerX, int centerY, throw new IllegalArgumentException("Parent must be instance of RevealViewGroup"); } - RevealViewGroup viewGroup = (RevealViewGroup) view.getParent(); - ViewRevealManager rm = viewGroup.getViewRevealManager(); + final RevealViewGroup viewGroup = (RevealViewGroup) view.getParent(); + final ViewRevealManager rm = viewGroup.getViewRevealManager(); - if (!rm.hasCustomerRevealAnimator() && LOLLIPOP_PLUS) { + if (!rm.overrideNativeAnimator() && LOLLIPOP_PLUS) { return android.view.ViewAnimationUtils.createCircularReveal(view, centerX, centerY, startRadius, endRadius); } - RevealValues viewData = new RevealValues(view, centerX, centerY, startRadius, endRadius); - ObjectAnimator animator = rm.createAnimator(viewData); + final RevealValues viewData = new RevealValues(view, centerX, centerY, startRadius, endRadius); + final Animator animator = rm.dispatchCreateAnimator(viewData); if (layerType != view.getLayerType()) { animator.addListener(new ChangeViewLayerTypeAdapter(viewData, layerType)); } + return animator; } } diff --git a/circualreveal/src/main/java/io/codetail/animation/ViewRevealManager.java b/circualreveal/src/main/java/io/codetail/animation/ViewRevealManager.java index a5a3b9f..5ccdd90 100644 --- a/circualreveal/src/main/java/io/codetail/animation/ViewRevealManager.java +++ b/circualreveal/src/main/java/io/codetail/animation/ViewRevealManager.java @@ -14,78 +14,131 @@ import java.util.HashMap; import java.util.Map; +@SuppressWarnings("WeakerAccess") public class ViewRevealManager { public static final ClipRadiusProperty REVEAL = new ClipRadiusProperty(); - private Map targets = new HashMap<>(); + private final ViewTransformation viewTransformation; + private final Map targets = new HashMap<>(); + private final Map animators = new HashMap<>(); + + private final AnimatorListenerAdapter animatorCallback = new AnimatorListenerAdapter() { + @Override public void onAnimationStart(Animator animation) { + final RevealValues values = getValues(animation); + values.clip(true); + } + + @Override public void onAnimationCancel(Animator animation) { + endAnimation(animation); + } + + @Override public void onAnimationEnd(Animator animation) { + endAnimation(animation); + } + + private void endAnimation(Animator animation) { + final RevealValues values = getValues(animation); + values.clip(false); + + // Clean up after animation is done + targets.remove(values.target); + animators.remove(animation); + } + }; public ViewRevealManager() { + this(new PathTransformation()); + } + public ViewRevealManager(ViewTransformation transformation) { + this.viewTransformation = transformation; } - protected ObjectAnimator createAnimator(RevealValues data) { - ObjectAnimator animator = - ObjectAnimator.ofFloat(data, REVEAL, data.startRadius, data.endRadius); + Animator dispatchCreateAnimator(RevealValues data) { + final Animator animator = createAnimator(data); - animator.addListener(new AnimatorListenerAdapter() { - @Override public void onAnimationStart(Animator animation) { - RevealValues values = getValues(animation); - values.clip(true); - } + // Before animation is started keep them + targets.put(data.target(), data); + animators.put(animator, data); + return animator; + } - @Override public void onAnimationEnd(Animator animation) { - RevealValues values = getValues(animation); - values.clip(false); - targets.remove(values.target()); - } - }); + /** + * Create custom animator of circular reveal + * + * @param data RevealValues contains information of starting & ending points, animation target and + * current animation values + * @return Animator to manage reveal animation + */ + protected Animator createAnimator(RevealValues data) { + final ObjectAnimator animator = + ObjectAnimator.ofFloat(data, REVEAL, data.startRadius, data.endRadius); - targets.put(data.target(), data); + animator.addListener(getAnimatorCallback()); return animator; } - private static RevealValues getValues(Animator animator) { - return (RevealValues) ((ObjectAnimator) animator).getTarget(); + protected final AnimatorListenerAdapter getAnimatorCallback() { + return animatorCallback; + } + + /** + * @return Retruns Animator + */ + protected final RevealValues getValues(Animator animator) { + return animators.get(animator); } /** * @return Map of started animators */ - public final Map getTargets() { - return targets; + protected final RevealValues getValues(View view) { + return targets.get(view); } /** - * @return True if you don't want use Android native reveal animator - * in order to use your own custom one + * @return True if you don't want use Android native reveal animator in order to use your own + * custom one */ - protected boolean hasCustomerRevealAnimator() { + protected boolean overrideNativeAnimator() { return false; } /** - * @return True if animation was started and it is still running, - * otherwise returns False + * @return True if animation was started and it is still running, otherwise returns False */ public boolean isClipped(View child) { - RevealValues data = targets.get(child); + final RevealValues data = getValues(child); return data != null && data.isClipping(); } /** * Applies path clipping on a canvas before drawing child, - * you should save canvas state before transformation and + * you should save canvas state before viewTransformation and * restore it afterwards * * @param canvas Canvas to apply clipping before drawing * @param child Reveal animation target - * @return True if transformation was successfully applied on - * referenced child, otherwise child be not the target and - * therefore animation was skipped + * @return True if viewTransformation was successfully applied on referenced child, otherwise + * child be not the target and therefore animation was skipped */ - public boolean transform(Canvas canvas, View child) { + public final boolean transform(Canvas canvas, View child) { final RevealValues revealData = targets.get(child); - return revealData != null && revealData.applyTransformation(canvas, child); + + // Target doesn't has animation values + if (revealData == null) { + return false; + } + // Check whether target consistency + else if (revealData.target != child) { + throw new IllegalStateException("Inconsistency detected, contains incorrect target view"); + } + // View doesn't wants to be clipped therefore transformation is useless + else if (!revealData.clipping) { + return false; + } + + return viewTransformation.transform(canvas, child, revealData); } public static final class RevealValues { @@ -112,12 +165,6 @@ public static final class RevealValues { // Animation target View target; - // Android Canvas is tricky, we cannot clip circles directly with Canvas API - // but it is allowed using Path, therefore we use it :| - Path path = new Path(); - - Region.Op op = Region.Op.REPLACE; - public RevealValues(View target, int centerX, int centerY, float startRadius, float endRadius) { this.target = target; this.centerX = centerX; @@ -148,6 +195,31 @@ public void clip(boolean clipping) { public boolean isClipping() { return clipping; } + } + + /** + * Custom View viewTransformation extension used for applying different reveal + * techniques + */ + interface ViewTransformation { + + /** + * Apply view viewTransformation + * + * @param canvas Main canvas + * @param child Target to be clipped & revealed + * @return True if viewTransformation is applied, otherwise return fAlse + */ + boolean transform(Canvas canvas, View child, RevealValues values); + } + + public static class PathTransformation implements ViewTransformation { + + // Android Canvas is tricky, we cannot clip circles directly with Canvas API + // but it is allowed using Path, therefore we use it :| + private final Path path = new Path(); + + private Region.Op op = Region.Op.REPLACE; /** @see Canvas#clipPath(Path, Region.Op) */ public Region.Op op() { @@ -159,31 +231,18 @@ public void op(Region.Op op) { this.op = op; } - /** - * Applies path clipping on a canvas before drawing child, - * you should save canvas state before transformation and - * restore it afterwards - * - * @param canvas Canvas to apply clipping before drawing - * @param child Reveal animation target - * @return True if transformation was successfully applied on - * referenced child, otherwise child be not the target and - * therefore animation was skipped - */ - boolean applyTransformation(Canvas canvas, View child) { - if (child != target || !clipping) { - return false; - } - + @Override public boolean transform(Canvas canvas, View child, RevealValues values) { path.reset(); // trick to applyTransformation animation, when even x & y translations are running - path.addCircle(child.getX() + centerX, child.getY() + centerY, radius, Path.Direction.CW); + path.addCircle(child.getX() + values.centerX, child.getY() + values.centerY, values.radius, + Path.Direction.CW); canvas.clipPath(path, op); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { child.invalidateOutline(); } - return true; + return false; } } @@ -199,8 +258,8 @@ private static final class ClipRadiusProperty extends Property