Add RT-enabled reveal animator
Bug: 16161431
Also re-writes RevealAnimator to avoid using any listeners internally,
removing the logic around shadowing the update listeners.
Change-Id: I6ed8126398eed971a87f20bccb7584c9acafbb6c
diff --git a/core/java/android/animation/RevealAnimator.java b/core/java/android/animation/RevealAnimator.java
index 77a536a..a1cbd45 100644
--- a/core/java/android/animation/RevealAnimator.java
+++ b/core/java/android/animation/RevealAnimator.java
@@ -16,10 +16,9 @@
package android.animation;
+import android.view.RenderNodeAnimator;
import android.view.View;
-import java.util.ArrayList;
-
/**
* Reveals a View with an animated clipping circle.
* The clipping is implemented efficiently by talking to a private reveal API on View.
@@ -28,114 +27,178 @@
* @hide
*/
public class RevealAnimator extends ValueAnimator {
- private final static String LOGTAG = "RevealAnimator";
- private ValueAnimator.AnimatorListener mListener;
- private ValueAnimator.AnimatorUpdateListener mUpdateListener;
- private RevealCircle mReuseRevealCircle = new RevealCircle(0);
- private RevealAnimator(final View clipView, final int x, final int y,
- float startRadius, float endRadius, final boolean inverseClip) {
- setObjectValues(new RevealCircle(startRadius), new RevealCircle(endRadius));
- setEvaluator(new RevealCircleEvaluator(mReuseRevealCircle));
+ private View mClipView;
+ private int mX, mY;
+ private boolean mInverseClip;
+ private float mStartRadius, mEndRadius;
+ private float mDelta;
+ private boolean mMayRunAsync;
- mUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- RevealCircle circle = (RevealCircle) animation.getAnimatedValue();
- float radius = circle.getRadius();
- clipView.setRevealClip(true, inverseClip, x, y, radius);
- }
- };
- mListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- clipView.setRevealClip(false, false, 0, 0, 0);
- }
+ // If this is null, we are running on the UI thread driven by the base
+ // ValueAnimator class. If this is not null, forward requests on to this
+ // Animator instead.
+ private RenderNodeAnimator mRtAnimator;
- @Override
- public void onAnimationEnd(Animator animation) {
- clipView.setRevealClip(false, false, 0, 0, 0);
- }
- };
- addUpdateListener(mUpdateListener);
- addListener(mListener);
+ public RevealAnimator(View clipView, int x, int y,
+ float startRadius, float endRadius, boolean inverseClip) {
+ mClipView = clipView;
+ mStartRadius = startRadius;
+ mEndRadius = endRadius;
+ mDelta = endRadius - startRadius;
+ mX = x;
+ mY = y;
+ mInverseClip = inverseClip;
+ super.setValues(PropertyValuesHolder.ofFloat("radius", startRadius, endRadius));
}
- public static RevealAnimator ofRevealCircle(View clipView, int x, int y,
- float startRadius, float endRadius, boolean inverseClip) {
- RevealAnimator anim = new RevealAnimator(clipView, x, y,
- startRadius, endRadius, inverseClip);
+ @Override
+ void animateValue(float fraction) {
+ super.animateValue(fraction);
+ fraction = getAnimatedFraction();
+ float radius = mStartRadius + (mDelta * fraction);
+ mClipView.setRevealClip(true, mInverseClip, mX, mY, radius);
+ }
+
+ @Override
+ protected void endAnimation(AnimationHandler handler) {
+ mClipView.setRevealClip(false, false, 0, 0, 0);
+ super.endAnimation(handler);
+ }
+
+ @Override
+ public void setAllowRunningAsynchronously(boolean mayRunAsync) {
+ mMayRunAsync = mayRunAsync;
+ }
+
+ private boolean canRunAsync() {
+ if (!mMayRunAsync) {
+ return false;
+ }
+ if (mUpdateListeners != null && mUpdateListeners.size() > 0) {
+ return false;
+ }
+ // TODO: Have RNA support this
+ if (getRepeatCount() != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void start() {
+ if (mRtAnimator != null) {
+ mRtAnimator.end();
+ mRtAnimator = null;
+ }
+ if (canRunAsync()) {
+ mRtAnimator = new RenderNodeAnimator(mX, mY, mInverseClip, mStartRadius, mEndRadius);
+ mRtAnimator.setDuration(getDuration());
+ mRtAnimator.setInterpolator(getInterpolator());
+ mRtAnimator.setTarget(mClipView);
+ // TODO: Listeners
+ mRtAnimator.start();
+ } else {
+ super.start();
+ }
+ }
+
+ @Override
+ public void cancel() {
+ if (mRtAnimator != null) {
+ mRtAnimator.cancel();
+ } else {
+ super.cancel();
+ }
+ }
+
+ @Override
+ public void end() {
+ if (mRtAnimator != null) {
+ mRtAnimator.end();
+ } else {
+ super.end();
+ }
+ }
+
+ @Override
+ public void resume() {
+ if (mRtAnimator != null) {
+ // TODO: Support? Reject?
+ } else {
+ super.resume();
+ }
+ }
+
+ @Override
+ public void pause() {
+ if (mRtAnimator != null) {
+ // TODO: see resume()
+ } else {
+ super.pause();
+ }
+ }
+
+ @Override
+ public boolean isRunning() {
+ if (mRtAnimator != null) {
+ return mRtAnimator.isRunning();
+ } else {
+ return super.isRunning();
+ }
+ }
+
+ @Override
+ public boolean isStarted() {
+ if (mRtAnimator != null) {
+ return mRtAnimator.isStarted();
+ } else {
+ return super.isStarted();
+ }
+ }
+
+ @Override
+ public void reverse() {
+ if (mRtAnimator != null) {
+ // TODO support
+ } else {
+ super.reverse();
+ }
+ }
+
+ @Override
+ public ValueAnimator clone() {
+ RevealAnimator anim = (RevealAnimator) super.clone();
+ anim.mRtAnimator = null;
return anim;
}
+ // ----------------------------------------
+ // All the things we don't allow
+ // ----------------------------------------
- /**
- * {@inheritDoc}
- */
@Override
- public void removeAllUpdateListeners() {
- super.removeAllUpdateListeners();
- addUpdateListener(mUpdateListener);
+ public void setValues(PropertyValuesHolder... values) {
+ throw new IllegalStateException("Cannot change the values of RevealAnimator");
}
- /**
- * {@inheritDoc}
- */
@Override
- public void removeAllListeners() {
- super.removeAllListeners();
- addListener(mListener);
+ public void setFloatValues(float... values) {
+ throw new IllegalStateException("Cannot change the values of RevealAnimator");
}
- /**
- * {@inheritDoc}
- */
@Override
- public ArrayList<AnimatorListener> getListeners() {
- ArrayList<AnimatorListener> allListeners =
- (ArrayList<AnimatorListener>) super.getListeners().clone();
- allListeners.remove(mListener);
- return allListeners;
+ public void setIntValues(int... values) {
+ throw new IllegalStateException("Cannot change the values of RevealAnimator");
}
- private class RevealCircle {
- float mRadius;
-
- public RevealCircle(float radius) {
- mRadius = radius;
- }
-
- public void setRadius(float radius) {
- mRadius = radius;
- }
-
- public float getRadius() {
- return mRadius;
- }
+ @Override
+ public void setObjectValues(Object... values) {
+ throw new IllegalStateException("Cannot change the values of RevealAnimator");
}
- private class RevealCircleEvaluator implements TypeEvaluator<RevealCircle> {
-
- private RevealCircle mRevealCircle;
-
- public RevealCircleEvaluator() {
- }
-
- public RevealCircleEvaluator(RevealCircle reuseCircle) {
- mRevealCircle = reuseCircle;
- }
-
- @Override
- public RevealCircle evaluate(float fraction, RevealCircle startValue,
- RevealCircle endValue) {
- float currentRadius = startValue.mRadius
- + ((endValue.mRadius - startValue.mRadius) * fraction);
- if (mRevealCircle == null) {
- return new RevealCircle(currentRadius);
- } else {
- mRevealCircle.setRadius(currentRadius);
- return mRevealCircle;
- }
- }
+ @Override
+ public void setEvaluator(TypeEvaluator value) {
+ throw new IllegalStateException("Cannot change the evaluator of RevealAnimator");
}
}
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index e3380a9..b13838a 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -206,7 +206,7 @@
/**
* The set of listeners to be sent events through the life of an animation.
*/
- private ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
+ ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
/**
* The property/value sets being animated.
@@ -1064,8 +1064,9 @@
/**
* Called internally to end an animation by removing it from the animations list. Must be
* called on the UI thread.
+ * @hide
*/
- private void endAnimation(AnimationHandler handler) {
+ protected void endAnimation(AnimationHandler handler) {
handler.mAnimations.remove(this);
handler.mPendingAnimations.remove(this);
handler.mDelayedAnims.remove(this);
@@ -1373,4 +1374,42 @@
}
return returnVal;
}
+
+ /**
+ * <p>Whether or not the ValueAnimator is allowed to run asynchronously off of
+ * the UI thread. This is a hint that informs the ValueAnimator that it is
+ * OK to run the animation off-thread, however ValueAnimator may decide
+ * that it must run the animation on the UI thread anyway. For example if there
+ * is an {@link AnimatorUpdateListener} the animation will run on the UI thread,
+ * regardless of the value of this hint.</p>
+ *
+ * <p>Regardless of whether or not the animation runs asynchronously, all
+ * listener callbacks will be called on the UI thread.</p>
+ *
+ * <p>To be able to use this hint the following must be true:</p>
+ * <ol>
+ * <li>{@link #getAnimatedFraction()} is not needed (it will return undefined values).</li>
+ * <li>The animator is immutable while {@link #isStarted()} is true. Requests
+ * to change values, duration, delay, etc... may be ignored.</li>
+ * <li>Lifecycle callback events may be asynchronous. Events such as
+ * {@link Animator.AnimatorListener#onAnimationEnd(Animator)} or
+ * {@link Animator.AnimatorListener#onAnimationRepeat(Animator)} may end up delayed
+ * as they must be posted back to the UI thread, and any actions performed
+ * by those callbacks (such as starting new animations) will not happen
+ * in the same frame.</li>
+ * <li>State change requests ({@link #cancel()}, {@link #end()}, {@link #reverse()}, etc...)
+ * may be asynchronous. It is guaranteed that all state changes that are
+ * performed on the UI thread in the same frame will be applied as a single
+ * atomic update, however that frame may be the current frame,
+ * the next frame, or some future frame. This will also impact the observed
+ * state of the Animator. For example, {@link #isStarted()} may still return true
+ * after a call to {@link #end()}. Using the lifecycle callbacks is preferred over
+ * queries to {@link #isStarted()}, {@link #isRunning()}, and {@link #isPaused()}
+ * for this reason.</li>
+ * </ol>
+ * @hide
+ */
+ public void setAllowRunningAsynchronously(boolean mayRunAsync) {
+ // It is up to subclasses to support this, if they can.
+ }
}
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 4b53c8e..2a06336 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -125,6 +125,12 @@
property.getNativeContainer(), paintField, finalValue));
}
+ public RenderNodeAnimator(int x, int y, boolean inverseClip,
+ float startRadius, float endRadius) {
+ init(nCreateRevealAnimator(new WeakReference<>(this),
+ x, y, inverseClip, startRadius, endRadius));
+ }
+
private void init(long ptr) {
mNativePtr = new VirtualRefBasePtr(ptr);
}
@@ -190,7 +196,7 @@
@Override
public void cancel() {
if (!mFinished) {
- nCancel(mNativePtr.get());
+ nEnd(mNativePtr.get());
final ArrayList<AnimatorListener> listeners = getListeners();
final int numListeners = listeners == null ? 0 : listeners.size();
@@ -202,7 +208,9 @@
@Override
public void end() {
- throw new UnsupportedOperationException();
+ if (!mFinished) {
+ nEnd(mNativePtr.get());
+ }
}
@Override
@@ -281,6 +289,11 @@
}
@Override
+ public boolean isStarted() {
+ return mStarted;
+ }
+
+ @Override
public void setInterpolator(TimeInterpolator interpolator) {
checkMutable();
mInterpolator = interpolator;
@@ -319,6 +332,8 @@
long canvasProperty, float finalValue);
private static native long nCreateCanvasPropertyPaintAnimator(WeakReference<RenderNodeAnimator> weakThis,
long canvasProperty, int paintField, float finalValue);
+ private static native long nCreateRevealAnimator(WeakReference<RenderNodeAnimator> weakThis,
+ int x, int y, boolean inverseClip, float startRadius, float endRadius);
private static native void nSetStartValue(long nativePtr, float startValue);
private static native void nSetDuration(long nativePtr, long duration);
@@ -328,5 +343,5 @@
private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
private static native void nStart(long animPtr);
- private static native void nCancel(long animPtr);
+ private static native void nEnd(long animPtr);
}
diff --git a/core/java/android/view/RenderNodeAnimatorCompat.java b/core/java/android/view/RenderNodeAnimatorCompat.java
index 151277a..8103f47 100644
--- a/core/java/android/view/RenderNodeAnimatorCompat.java
+++ b/core/java/android/view/RenderNodeAnimatorCompat.java
@@ -31,6 +31,7 @@
private long mStartDelay = 0;
private long mStartTime;
private boolean mCanceled;
+ private boolean mStarted;
public RenderNodeAnimatorCompat(int property, float finalValue) {
super(property, finalValue);
@@ -49,6 +50,7 @@
@Override
public void start() {
+ mStarted = true;
if (mStartDelay <= 0) {
doStart();
} else {
@@ -56,6 +58,11 @@
}
}
+ @Override
+ public boolean isStarted() {
+ return mStarted;
+ }
+
private void doStart() {
if (!mCanceled) {
super.start();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7bc8bc5..85fa1e6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10724,8 +10724,7 @@
*/
public final ValueAnimator createClearCircleAnimator(int centerX, int centerY,
float startRadius, float endRadius) {
- return RevealAnimator.ofRevealCircle(this, centerX, centerY,
- startRadius, endRadius, true);
+ return new RevealAnimator(this, centerX, centerY, startRadius, endRadius, true);
}
/**
@@ -10867,8 +10866,7 @@
public void setRevealClip(boolean shouldClip, boolean inverseClip,
float x, float y, float radius) {
mRenderNode.setRevealClip(shouldClip, inverseClip, x, y, radius);
- // TODO: Handle this invalidate in a better way, or purely in native.
- invalidate();
+ invalidateViewProperty(false, false);
}
/**
diff --git a/core/java/android/view/ViewAnimationUtils.java b/core/java/android/view/ViewAnimationUtils.java
index 29e865f..bee35ae 100644
--- a/core/java/android/view/ViewAnimationUtils.java
+++ b/core/java/android/view/ViewAnimationUtils.java
@@ -38,7 +38,6 @@
*/
public static final ValueAnimator createCircularReveal(View view,
int centerX, int centerY, float startRadius, float endRadius) {
- return RevealAnimator.ofRevealCircle(view, centerX, centerY,
- startRadius, endRadius, false);
+ return new RevealAnimator(view, centerX, centerY, startRadius, endRadius, false);
}
}
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp
index ed57979..7c0c6ef 100644
--- a/core/jni/android_view_RenderNodeAnimator.cpp
+++ b/core/jni/android_view_RenderNodeAnimator.cpp
@@ -116,6 +116,14 @@
return reinterpret_cast<jlong>( animator );
}
+static jlong createRevealAnimator(JNIEnv* env, jobject clazz, jobject weakThis,
+ jint centerX, jint centerY, jboolean inverseClip, jfloat startRadius, jfloat endRadius) {
+ BaseRenderNodeAnimator* animator = new RevealAnimator(centerX, centerY, inverseClip,
+ startRadius, endRadius);
+ animator->setListener(new AnimationListenerBridge(env, weakThis));
+ return reinterpret_cast<jlong>( animator );
+}
+
static void setStartValue(JNIEnv* env, jobject clazz, jlong animatorPtr, jfloat startValue) {
BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
animator->setStartValue(startValue);
@@ -154,9 +162,9 @@
animator->start();
}
-static void cancel(JNIEnv* env, jobject clazz, jlong animatorPtr) {
+static void end(JNIEnv* env, jobject clazz, jlong animatorPtr) {
BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
- animator->cancel();
+ animator->end();
}
#endif
@@ -172,6 +180,7 @@
{ "nCreateAnimator", "(Ljava/lang/ref/WeakReference;IF)J", (void*) createAnimator },
{ "nCreateCanvasPropertyFloatAnimator", "(Ljava/lang/ref/WeakReference;JF)J", (void*) createCanvasPropertyFloatAnimator },
{ "nCreateCanvasPropertyPaintAnimator", "(Ljava/lang/ref/WeakReference;JIF)J", (void*) createCanvasPropertyPaintAnimator },
+ { "nCreateRevealAnimator", "(Ljava/lang/ref/WeakReference;IIZFF)J", (void*) createRevealAnimator },
{ "nSetStartValue", "(JF)V", (void*) setStartValue },
{ "nSetDuration", "(JJ)V", (void*) setDuration },
{ "nGetDuration", "(J)J", (void*) getDuration },
@@ -179,7 +188,7 @@
{ "nGetStartDelay", "(J)J", (void*) getStartDelay },
{ "nSetInterpolator", "(JJ)V", (void*) setInterpolator },
{ "nStart", "(J)V", (void*) start },
- { "nCancel", "(J)V", (void*) cancel },
+ { "nEnd", "(J)V", (void*) end },
#endif
};
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index d35dce9..24ed6cd 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -288,5 +288,23 @@
LOG_ALWAYS_FATAL("Unknown field %d", (int) mField);
}
+RevealAnimator::RevealAnimator(int centerX, int centerY, bool inverseClip,
+ float startValue, float finalValue)
+ : BaseRenderNodeAnimator(finalValue)
+ , mCenterX(centerX)
+ , mCenterY(centerY)
+ , mInverseClip(inverseClip) {
+ setStartValue(startValue);
+}
+
+float RevealAnimator::getValue(RenderNode* target) const {
+ return target->properties().getRevealClip().radius();
+}
+
+void RevealAnimator::setValue(RenderNode* target, float value) {
+ target->animatorProperties().mutableRevealClip().set(true, mInverseClip,
+ mCenterX, mCenterY, value);
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index 0dda23f..9a080a7 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -51,7 +51,7 @@
mListener = listener;
}
ANDROID_API void start() { mStagingPlayState = RUNNING; onStagingPlayStateChanged(); }
- ANDROID_API void cancel() { mStagingPlayState = FINISHED; onStagingPlayStateChanged(); }
+ ANDROID_API void end() { mStagingPlayState = FINISHED; onStagingPlayStateChanged(); }
void attach(RenderNode* target);
virtual void onAttached() {}
@@ -169,6 +169,19 @@
PaintField mField;
};
+class RevealAnimator : public BaseRenderNodeAnimator {
+public:
+ ANDROID_API RevealAnimator(int centerX, int centerY, bool inverseClip,
+ float startValue, float finalValue);
+protected:
+ virtual float getValue(RenderNode* target) const;
+ virtual void setValue(RenderNode* target, float value);
+
+private:
+ int mCenterX, mCenterY;
+ bool mInverseClip;
+};
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/RevealClip.h b/libs/hwui/RevealClip.h
index ece8498..07404cb 100644
--- a/libs/hwui/RevealClip.h
+++ b/libs/hwui/RevealClip.h
@@ -57,6 +57,10 @@
return mShouldClip;
}
+ float radius() const {
+ return mRadius;
+ }
+
const SkPath* getPath() const {
if (!mShouldClip) return NULL;
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 544553e..bc2f1fd 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -306,15 +306,6 @@
</activity>
<activity
- android:name="CirclePropActivity"
- android:label="Draw/Circle Props">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="com.android.test.hwui.TEST" />
- </intent-filter>
- </activity>
-
- <activity
android:name="ClearActivity"
android:label="Window/Clear">
<intent-filter>
@@ -920,5 +911,24 @@
<category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
+
+ <activity
+ android:name="CirclePropActivity"
+ android:label="Animation/Circle Props">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.hwui.TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name="RevealActivity"
+ android:label="Animation/Reveal Animation">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.hwui.TEST" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/RevealActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/RevealActivity.java
new file mode 100644
index 0000000..d27be69
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/RevealActivity.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewAnimationUtils;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.ProgressBar;
+
+public class RevealActivity extends Activity implements OnClickListener {
+
+ private static final int DURATION = 800;
+
+ private boolean mShouldBlock;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ ProgressBar spinner = new ProgressBar(this, null, android.R.attr.progressBarStyleLarge);
+ layout.addView(spinner, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ View revealView = new MyView(this);
+ layout.addView(revealView,
+ new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setContentView(layout);
+
+ revealView.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View view) {
+ ValueAnimator animator = ViewAnimationUtils.createCircularReveal(view,
+ view.getWidth() / 2, view.getHeight() / 2,
+ 0, Math.max(view.getWidth(), view.getHeight()));
+ animator.setDuration(DURATION);
+ animator.setAllowRunningAsynchronously(true);
+ animator.start();
+
+ mShouldBlock = !mShouldBlock;
+ if (mShouldBlock) {
+ view.post(sBlockThread);
+ }
+ }
+
+ private final static Runnable sBlockThread = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(DURATION);
+ } catch (InterruptedException e) {
+ }
+ }
+ };
+
+ static class MyView extends View {
+
+ public MyView(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawColor(Color.RED);
+ }
+ }
+}