Animator stuff
Bug: 17228458
Change-Id: Id884a429a512f9cd2be0ed16dbd0f10e92b4440d
This commit is contained in:
@@ -485,4 +485,6 @@ public abstract class HardwareRenderer {
|
||||
* Called by {@link ViewRootImpl} when a new performTraverals is scheduled.
|
||||
*/
|
||||
abstract void notifyFramePending();
|
||||
|
||||
abstract void registerAnimatingRenderNode(RenderNode animator);
|
||||
}
|
||||
|
||||
@@ -167,10 +167,13 @@ public class RenderNode {
|
||||
public static final int STATUS_DREW = 0x4;
|
||||
|
||||
private boolean mValid;
|
||||
private final long mNativeRenderNode;
|
||||
// Do not access directly unless you are ThreadedRenderer
|
||||
final long mNativeRenderNode;
|
||||
private final View mOwningView;
|
||||
|
||||
private RenderNode(String name) {
|
||||
private RenderNode(String name, View owningView) {
|
||||
mNativeRenderNode = nCreate(name);
|
||||
mOwningView = owningView;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,6 +181,7 @@ public class RenderNode {
|
||||
*/
|
||||
private RenderNode(long nativePtr) {
|
||||
mNativeRenderNode = nativePtr;
|
||||
mOwningView = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,8 +192,8 @@ public class RenderNode {
|
||||
*
|
||||
* @return A new RenderNode.
|
||||
*/
|
||||
public static RenderNode create(String name) {
|
||||
return new RenderNode(name);
|
||||
public static RenderNode create(String name, @Nullable View owningView) {
|
||||
return new RenderNode(name, owningView);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -805,7 +809,15 @@ public class RenderNode {
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void addAnimator(RenderNodeAnimator animator) {
|
||||
if (mOwningView == null || mOwningView.mAttachInfo == null) {
|
||||
throw new IllegalStateException("Cannot start this animator on a detached view!");
|
||||
}
|
||||
nAddAnimator(mNativeRenderNode, animator.getNativeAnimator());
|
||||
mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this);
|
||||
}
|
||||
|
||||
public void endAllAnimators() {
|
||||
nEndAllAnimators(mNativeRenderNode);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@@ -891,6 +903,7 @@ public class RenderNode {
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static native void nAddAnimator(long renderNode, long animatorPtr);
|
||||
private static native void nEndAllAnimators(long renderNode);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Finalization
|
||||
|
||||
@@ -19,7 +19,6 @@ package android.view;
|
||||
import android.animation.Animator;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.animation.Animator.AnimatorListener;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.CanvasProperty;
|
||||
import android.graphics.Paint;
|
||||
@@ -30,7 +29,6 @@ import com.android.internal.view.animation.FallbackLUTInterpolator;
|
||||
import com.android.internal.view.animation.HasNativeInterpolator;
|
||||
import com.android.internal.view.animation.NativeInterpolatorFactory;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
@@ -111,13 +109,11 @@ public class RenderNodeAnimator extends Animator {
|
||||
mRenderProperty = property;
|
||||
mFinalValue = finalValue;
|
||||
mUiThreadHandlesDelay = true;
|
||||
init(nCreateAnimator(new WeakReference<RenderNodeAnimator>(this),
|
||||
property, finalValue));
|
||||
init(nCreateAnimator(property, finalValue));
|
||||
}
|
||||
|
||||
public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) {
|
||||
init(nCreateCanvasPropertyFloatAnimator(
|
||||
new WeakReference<RenderNodeAnimator>(this),
|
||||
property.getNativeContainer(), finalValue));
|
||||
mUiThreadHandlesDelay = false;
|
||||
}
|
||||
@@ -132,14 +128,12 @@ public class RenderNodeAnimator extends Animator {
|
||||
*/
|
||||
public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) {
|
||||
init(nCreateCanvasPropertyPaintAnimator(
|
||||
new WeakReference<RenderNodeAnimator>(this),
|
||||
property.getNativeContainer(), paintField, finalValue));
|
||||
mUiThreadHandlesDelay = false;
|
||||
}
|
||||
|
||||
public RenderNodeAnimator(int x, int y, float startRadius, float endRadius) {
|
||||
init(nCreateRevealAnimator(new WeakReference<RenderNodeAnimator>(this),
|
||||
x, y, startRadius, endRadius));
|
||||
init(nCreateRevealAnimator(x, y, startRadius, endRadius));
|
||||
mUiThreadHandlesDelay = true;
|
||||
}
|
||||
|
||||
@@ -192,7 +186,7 @@ public class RenderNodeAnimator extends Animator {
|
||||
}
|
||||
|
||||
private void doStart() {
|
||||
nStart(mNativePtr.get());
|
||||
nStart(mNativePtr.get(), this);
|
||||
|
||||
// Alpha is a special snowflake that has the canonical value stored
|
||||
// in mTransformationInfo instead of in RenderNode, so we need to update
|
||||
@@ -248,24 +242,21 @@ public class RenderNodeAnimator extends Animator {
|
||||
|
||||
public void setTarget(View view) {
|
||||
mViewTarget = view;
|
||||
mTarget = view.mRenderNode;
|
||||
mTarget.addAnimator(this);
|
||||
setTarget(mViewTarget.mRenderNode);
|
||||
}
|
||||
|
||||
public void setTarget(Canvas canvas) {
|
||||
if (!(canvas instanceof GLES20RecordingCanvas)) {
|
||||
throw new IllegalArgumentException("Not a GLES20RecordingCanvas");
|
||||
}
|
||||
|
||||
final GLES20RecordingCanvas recordingCanvas = (GLES20RecordingCanvas) canvas;
|
||||
setTarget(recordingCanvas.mNode);
|
||||
}
|
||||
|
||||
public void setTarget(RenderNode node) {
|
||||
private void setTarget(RenderNode node) {
|
||||
if (mTarget != null) {
|
||||
throw new IllegalStateException("Target already set!");
|
||||
}
|
||||
mViewTarget = null;
|
||||
mTarget = node;
|
||||
mTarget.addAnimator(this);
|
||||
}
|
||||
@@ -335,6 +326,12 @@ public class RenderNodeAnimator extends Animator {
|
||||
for (int i = 0; i < numListeners; i++) {
|
||||
listeners.get(i).onAnimationEnd(this);
|
||||
}
|
||||
|
||||
// Release the native object, as it has a global reference to us. This
|
||||
// breaks the cyclic reference chain, and allows this object to be
|
||||
// GC'd
|
||||
mNativePtr.release();
|
||||
mNativePtr = null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -427,11 +424,8 @@ public class RenderNodeAnimator extends Animator {
|
||||
}
|
||||
|
||||
// Called by native
|
||||
private static void callOnFinished(WeakReference<RenderNodeAnimator> weakThis) {
|
||||
RenderNodeAnimator animator = weakThis.get();
|
||||
if (animator != null) {
|
||||
animator.onFinished();
|
||||
}
|
||||
private static void callOnFinished(RenderNodeAnimator animator) {
|
||||
animator.onFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -439,22 +433,20 @@ public class RenderNodeAnimator extends Animator {
|
||||
throw new IllegalStateException("Cannot clone this animator");
|
||||
}
|
||||
|
||||
private static native long nCreateAnimator(WeakReference<RenderNodeAnimator> weakThis,
|
||||
int property, float finalValue);
|
||||
private static native long nCreateCanvasPropertyFloatAnimator(WeakReference<RenderNodeAnimator> weakThis,
|
||||
private static native long nCreateAnimator(int property, float finalValue);
|
||||
private static native long nCreateCanvasPropertyFloatAnimator(
|
||||
long canvasProperty, float finalValue);
|
||||
private static native long nCreateCanvasPropertyPaintAnimator(WeakReference<RenderNodeAnimator> weakThis,
|
||||
private static native long nCreateCanvasPropertyPaintAnimator(
|
||||
long canvasProperty, int paintField, float finalValue);
|
||||
private static native long nCreateRevealAnimator(WeakReference<RenderNodeAnimator> weakThis,
|
||||
private static native long nCreateRevealAnimator(
|
||||
int x, int y, float startRadius, float endRadius);
|
||||
|
||||
private static native void nSetStartValue(long nativePtr, float startValue);
|
||||
private static native void nSetDuration(long nativePtr, long duration);
|
||||
private static native long nGetDuration(long nativePtr);
|
||||
private static native void nSetStartDelay(long nativePtr, long startDelay);
|
||||
private static native long nGetStartDelay(long nativePtr);
|
||||
private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
|
||||
|
||||
private static native void nStart(long animPtr);
|
||||
private static native void nStart(long animPtr, RenderNodeAnimator finishListener);
|
||||
private static native void nEnd(long animPtr);
|
||||
}
|
||||
|
||||
@@ -314,6 +314,20 @@ public class ThreadedRenderer extends HardwareRenderer {
|
||||
|
||||
attachInfo.mIgnoreDirtyState = false;
|
||||
|
||||
// register animating rendernodes which started animating prior to renderer
|
||||
// creation, which is typical for animators started prior to first draw
|
||||
if (attachInfo.mPendingAnimatingRenderNodes != null) {
|
||||
final int count = attachInfo.mPendingAnimatingRenderNodes.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
registerAnimatingRenderNode(
|
||||
attachInfo.mPendingAnimatingRenderNodes.get(i));
|
||||
}
|
||||
attachInfo.mPendingAnimatingRenderNodes.clear();
|
||||
// We don't need this anymore as subsequent calls to
|
||||
// ViewRootImpl#attachRenderNodeAnimator will go directly to us.
|
||||
attachInfo.mPendingAnimatingRenderNodes = null;
|
||||
}
|
||||
|
||||
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,
|
||||
recordDuration, view.getResources().getDisplayMetrics().density);
|
||||
if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) {
|
||||
@@ -371,6 +385,11 @@ public class ThreadedRenderer extends HardwareRenderer {
|
||||
nNotifyFramePending(mNativeProxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerAnimatingRenderNode(RenderNode animator) {
|
||||
nRegisterAnimatingRenderNode(mRootNode.mNativeRenderNode, animator.mNativeRenderNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
@@ -468,6 +487,7 @@ public class ThreadedRenderer extends HardwareRenderer {
|
||||
private static native int nSyncAndDrawFrame(long nativeProxy,
|
||||
long frameTimeNanos, long recordDuration, float density);
|
||||
private static native void nDestroyCanvasAndSurface(long nativeProxy);
|
||||
private static native void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode);
|
||||
|
||||
private static native void nInvokeFunctor(long functor, boolean waitForCompletion);
|
||||
|
||||
|
||||
@@ -3565,7 +3565,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
|
||||
mUserPaddingStart = UNDEFINED_PADDING;
|
||||
mUserPaddingEnd = UNDEFINED_PADDING;
|
||||
mRenderNode = RenderNode.create(getClass().getName());
|
||||
mRenderNode = RenderNode.create(getClass().getName(), this);
|
||||
|
||||
if (!sCompatibilityDone && context != null) {
|
||||
final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
|
||||
@@ -4161,7 +4161,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
*/
|
||||
View() {
|
||||
mResources = null;
|
||||
mRenderNode = RenderNode.create(getClass().getName());
|
||||
mRenderNode = RenderNode.create(getClass().getName(), this);
|
||||
}
|
||||
|
||||
private static SparseArray<String> getAttributeMap() {
|
||||
@@ -15180,9 +15180,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
* @param renderNode Existing RenderNode, or {@code null}
|
||||
* @return A valid display list for the specified drawable
|
||||
*/
|
||||
private static RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) {
|
||||
private RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) {
|
||||
if (renderNode == null) {
|
||||
renderNode = RenderNode.create(drawable.getClass().getName());
|
||||
renderNode = RenderNode.create(drawable.getClass().getName(), this);
|
||||
}
|
||||
|
||||
final Rect bounds = drawable.getBounds();
|
||||
@@ -19908,6 +19908,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
boolean mHardwareAccelerated;
|
||||
boolean mHardwareAccelerationRequested;
|
||||
HardwareRenderer mHardwareRenderer;
|
||||
List<RenderNode> mPendingAnimatingRenderNodes;
|
||||
|
||||
/**
|
||||
* The state of the display to which the window is attached, as reported
|
||||
|
||||
@@ -671,6 +671,17 @@ public final class ViewRootImpl implements ViewParent,
|
||||
ThreadedRenderer.invokeFunctor(functor, waitForCompletion);
|
||||
}
|
||||
|
||||
public void registerAnimatingRenderNode(RenderNode animator) {
|
||||
if (mAttachInfo.mHardwareRenderer != null) {
|
||||
mAttachInfo.mHardwareRenderer.registerAnimatingRenderNode(animator);
|
||||
} else {
|
||||
if (mAttachInfo.mPendingAnimatingRenderNodes == null) {
|
||||
mAttachInfo.mPendingAnimatingRenderNodes = new ArrayList<RenderNode>();
|
||||
}
|
||||
mAttachInfo.mPendingAnimatingRenderNodes.add(animator);
|
||||
}
|
||||
}
|
||||
|
||||
private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
|
||||
mAttachInfo.mHardwareAccelerated = false;
|
||||
mAttachInfo.mHardwareAccelerationRequested = false;
|
||||
@@ -2329,6 +2340,16 @@ public final class ViewRootImpl implements ViewParent,
|
||||
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
|
||||
}
|
||||
|
||||
// For whatever reason we didn't create a HardwareRenderer, end any
|
||||
// hardware animations that are now dangling
|
||||
if (mAttachInfo.mPendingAnimatingRenderNodes != null) {
|
||||
final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();
|
||||
}
|
||||
mAttachInfo.mPendingAnimatingRenderNodes.clear();
|
||||
}
|
||||
|
||||
if (mReportNextDraw) {
|
||||
mReportNextDraw = false;
|
||||
if (mAttachInfo.mHardwareRenderer != null) {
|
||||
|
||||
@@ -147,7 +147,7 @@ public class Editor {
|
||||
boolean isDirty;
|
||||
public TextDisplayList(String name) {
|
||||
isDirty = true;
|
||||
displayList = RenderNode.create(name);
|
||||
displayList = RenderNode.create(name, null);
|
||||
}
|
||||
boolean needsRecord() { return isDirty || !displayList.isValid(); }
|
||||
}
|
||||
|
||||
@@ -455,6 +455,12 @@ static void android_view_RenderNode_addAnimator(JNIEnv* env, jobject clazz,
|
||||
renderNode->addAnimator(animator);
|
||||
}
|
||||
|
||||
static void android_view_RenderNode_endAllAnimators(JNIEnv* env, jobject clazz,
|
||||
jlong renderNodePtr) {
|
||||
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
|
||||
renderNode->animators().endAllAnimators();
|
||||
}
|
||||
|
||||
#endif // USE_OPENGL_RENDERER
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -534,6 +540,7 @@ static JNINativeMethod gMethods[] = {
|
||||
{ "nGetPivotY", "(J)F", (void*) android_view_RenderNode_getPivotY },
|
||||
|
||||
{ "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator },
|
||||
{ "nEndAllAnimators", "(J)V", (void*) android_view_RenderNode_endAllAnimators },
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -51,28 +51,36 @@ public:
|
||||
// cyclic-references-of-doom. If you think "I know, just use NewWeakGlobalRef!"
|
||||
// then you end up with basically a PhantomReference, which is totally not
|
||||
// what we want.
|
||||
AnimationListenerBridge(JNIEnv* env, jobject weakThis) {
|
||||
mWeakThis = env->NewGlobalRef(weakThis);
|
||||
AnimationListenerBridge(JNIEnv* env, jobject finishListener) {
|
||||
mFinishListener = env->NewGlobalRef(finishListener);
|
||||
env->GetJavaVM(&mJvm);
|
||||
}
|
||||
|
||||
virtual ~AnimationListenerBridge() {
|
||||
JNIEnv* env = getEnv(mJvm);
|
||||
env->DeleteGlobalRef(mWeakThis);
|
||||
mWeakThis = NULL;
|
||||
if (mFinishListener) {
|
||||
onAnimationFinished(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void onAnimationFinished(BaseRenderNodeAnimator*) {
|
||||
LOG_ALWAYS_FATAL_IF(!mFinishListener, "Finished listener twice?");
|
||||
JNIEnv* env = getEnv(mJvm);
|
||||
env->CallStaticVoidMethod(
|
||||
gRenderNodeAnimatorClassInfo.clazz,
|
||||
gRenderNodeAnimatorClassInfo.callOnFinished,
|
||||
mWeakThis);
|
||||
mFinishListener);
|
||||
releaseJavaObject();
|
||||
}
|
||||
|
||||
private:
|
||||
void releaseJavaObject() {
|
||||
JNIEnv* env = getEnv(mJvm);
|
||||
env->DeleteGlobalRef(mFinishListener);
|
||||
mFinishListener = NULL;
|
||||
}
|
||||
|
||||
JavaVM* mJvm;
|
||||
jobject mWeakThis;
|
||||
jobject mFinishListener;
|
||||
};
|
||||
|
||||
static inline RenderPropertyAnimator::RenderProperty toRenderProperty(jint property) {
|
||||
@@ -88,38 +96,33 @@ static inline CanvasPropertyPaintAnimator::PaintField toPaintField(jint field) {
|
||||
return static_cast<CanvasPropertyPaintAnimator::PaintField>(field);
|
||||
}
|
||||
|
||||
static jlong createAnimator(JNIEnv* env, jobject clazz, jobject weakThis,
|
||||
static jlong createAnimator(JNIEnv* env, jobject clazz,
|
||||
jint propertyRaw, jfloat finalValue) {
|
||||
RenderPropertyAnimator::RenderProperty property = toRenderProperty(propertyRaw);
|
||||
|
||||
BaseRenderNodeAnimator* animator = new RenderPropertyAnimator(property, finalValue);
|
||||
animator->setListener(new AnimationListenerBridge(env, weakThis));
|
||||
return reinterpret_cast<jlong>( animator );
|
||||
}
|
||||
|
||||
static jlong createCanvasPropertyFloatAnimator(JNIEnv* env, jobject clazz,
|
||||
jobject weakThis, jlong canvasPropertyPtr, jfloat finalValue) {
|
||||
jlong canvasPropertyPtr, jfloat finalValue) {
|
||||
CanvasPropertyPrimitive* canvasProperty = reinterpret_cast<CanvasPropertyPrimitive*>(canvasPropertyPtr);
|
||||
BaseRenderNodeAnimator* animator = new CanvasPropertyPrimitiveAnimator(canvasProperty, finalValue);
|
||||
animator->setListener(new AnimationListenerBridge(env, weakThis));
|
||||
return reinterpret_cast<jlong>( animator );
|
||||
}
|
||||
|
||||
static jlong createCanvasPropertyPaintAnimator(JNIEnv* env, jobject clazz,
|
||||
jobject weakThis, jlong canvasPropertyPtr, jint paintFieldRaw,
|
||||
jlong canvasPropertyPtr, jint paintFieldRaw,
|
||||
jfloat finalValue) {
|
||||
CanvasPropertyPaint* canvasProperty = reinterpret_cast<CanvasPropertyPaint*>(canvasPropertyPtr);
|
||||
CanvasPropertyPaintAnimator::PaintField paintField = toPaintField(paintFieldRaw);
|
||||
BaseRenderNodeAnimator* animator = new CanvasPropertyPaintAnimator(
|
||||
canvasProperty, paintField, finalValue);
|
||||
animator->setListener(new AnimationListenerBridge(env, weakThis));
|
||||
return reinterpret_cast<jlong>( animator );
|
||||
}
|
||||
|
||||
static jlong createRevealAnimator(JNIEnv* env, jobject clazz, jobject weakThis,
|
||||
static jlong createRevealAnimator(JNIEnv* env, jobject clazz,
|
||||
jint centerX, jint centerY, jfloat startRadius, jfloat endRadius) {
|
||||
BaseRenderNodeAnimator* animator = new RevealAnimator(centerX, centerY, startRadius, endRadius);
|
||||
animator->setListener(new AnimationListenerBridge(env, weakThis));
|
||||
return reinterpret_cast<jlong>( animator );
|
||||
}
|
||||
|
||||
@@ -156,8 +159,11 @@ static void setInterpolator(JNIEnv* env, jobject clazz, jlong animatorPtr, jlong
|
||||
animator->setInterpolator(interpolator);
|
||||
}
|
||||
|
||||
static void start(JNIEnv* env, jobject clazz, jlong animatorPtr) {
|
||||
static void start(JNIEnv* env, jobject clazz, jlong animatorPtr, jobject finishListener) {
|
||||
BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
|
||||
if (finishListener) {
|
||||
animator->setListener(new AnimationListenerBridge(env, finishListener));
|
||||
}
|
||||
animator->start();
|
||||
}
|
||||
|
||||
@@ -176,17 +182,16 @@ const char* const kClassPathName = "android/view/RenderNodeAnimator";
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
#ifdef USE_OPENGL_RENDERER
|
||||
{ "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;IIFF)J", (void*) createRevealAnimator },
|
||||
{ "nCreateAnimator", "(IF)J", (void*) createAnimator },
|
||||
{ "nCreateCanvasPropertyFloatAnimator", "(JF)J", (void*) createCanvasPropertyFloatAnimator },
|
||||
{ "nCreateCanvasPropertyPaintAnimator", "(JIF)J", (void*) createCanvasPropertyPaintAnimator },
|
||||
{ "nCreateRevealAnimator", "(IIFF)J", (void*) createRevealAnimator },
|
||||
{ "nSetStartValue", "(JF)V", (void*) setStartValue },
|
||||
{ "nSetDuration", "(JJ)V", (void*) setDuration },
|
||||
{ "nGetDuration", "(J)J", (void*) getDuration },
|
||||
{ "nSetStartDelay", "(JJ)V", (void*) setStartDelay },
|
||||
{ "nGetStartDelay", "(J)J", (void*) getStartDelay },
|
||||
{ "nSetInterpolator", "(JJ)V", (void*) setInterpolator },
|
||||
{ "nStart", "(J)V", (void*) start },
|
||||
{ "nStart", "(JLandroid/view/RenderNodeAnimator;)V", (void*) start },
|
||||
{ "nEnd", "(J)V", (void*) end },
|
||||
#endif
|
||||
};
|
||||
@@ -204,7 +209,7 @@ int register_android_view_RenderNodeAnimator(JNIEnv* env) {
|
||||
gRenderNodeAnimatorClassInfo.clazz = jclass(env->NewGlobalRef(gRenderNodeAnimatorClassInfo.clazz));
|
||||
|
||||
GET_STATIC_METHOD_ID(gRenderNodeAnimatorClassInfo.callOnFinished, gRenderNodeAnimatorClassInfo.clazz,
|
||||
"callOnFinished", "(Ljava/lang/ref/WeakReference;)V");
|
||||
"callOnFinished", "(Landroid/view/RenderNodeAnimator;)V");
|
||||
|
||||
return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#include "android_view_GraphicBuffer.h"
|
||||
|
||||
#include <Animator.h>
|
||||
#include <AnimationContext.h>
|
||||
#include <IContextFactory.h>
|
||||
#include <RenderNode.h>
|
||||
#include <renderthread/CanvasContext.h>
|
||||
#include <renderthread/RenderProxy.h>
|
||||
@@ -103,7 +105,7 @@ private:
|
||||
std::string mMessage;
|
||||
};
|
||||
|
||||
class RootRenderNode : public RenderNode, AnimationHook, ErrorHandler {
|
||||
class RootRenderNode : public RenderNode, ErrorHandler {
|
||||
public:
|
||||
RootRenderNode(JNIEnv* env) : RenderNode() {
|
||||
mLooper = Looper::getForThread();
|
||||
@@ -114,34 +116,84 @@ public:
|
||||
|
||||
virtual ~RootRenderNode() {}
|
||||
|
||||
virtual void callOnFinished(BaseRenderNodeAnimator* animator, AnimationListener* listener) {
|
||||
OnFinishedEvent event(animator, listener);
|
||||
mOnFinishedEvents.push_back(event);
|
||||
}
|
||||
|
||||
virtual void onError(const std::string& message) {
|
||||
mLooper->sendMessage(new RenderingException(mVm, message), 0);
|
||||
}
|
||||
|
||||
virtual void prepareTree(TreeInfo& info) {
|
||||
info.animationHook = this;
|
||||
info.errorHandler = this;
|
||||
RenderNode::prepareTree(info);
|
||||
info.animationHook = NULL;
|
||||
info.errorHandler = NULL;
|
||||
}
|
||||
|
||||
// post all the finished stuff
|
||||
if (mOnFinishedEvents.size()) {
|
||||
sp<InvokeAnimationListeners> message
|
||||
= new InvokeAnimationListeners(mOnFinishedEvents);
|
||||
mLooper->sendMessage(message, 0);
|
||||
void sendMessage(const sp<MessageHandler>& handler) {
|
||||
mLooper->sendMessage(handler, 0);
|
||||
}
|
||||
|
||||
void attachAnimatingNode(RenderNode* animatingNode) {
|
||||
mPendingAnimatingRenderNodes.push_back(animatingNode);
|
||||
}
|
||||
|
||||
void doAttachAnimatingNodes(AnimationContext* context) {
|
||||
for (size_t i = 0; i < mPendingAnimatingRenderNodes.size(); i++) {
|
||||
RenderNode* node = mPendingAnimatingRenderNodes[i].get();
|
||||
context->addAnimatingRenderNode(*node);
|
||||
}
|
||||
mPendingAnimatingRenderNodes.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
sp<Looper> mLooper;
|
||||
std::vector<OnFinishedEvent> mOnFinishedEvents;
|
||||
JavaVM* mVm;
|
||||
std::vector< sp<RenderNode> > mPendingAnimatingRenderNodes;
|
||||
};
|
||||
|
||||
class AnimationContextBridge : public AnimationContext {
|
||||
public:
|
||||
AnimationContextBridge(renderthread::TimeLord& clock, RootRenderNode* rootNode)
|
||||
: AnimationContext(clock), mRootNode(rootNode) {
|
||||
}
|
||||
|
||||
virtual ~AnimationContextBridge() {}
|
||||
|
||||
// Marks the start of a frame, which will update the frame time and move all
|
||||
// next frame animations into the current frame
|
||||
virtual void startFrame() {
|
||||
mRootNode->doAttachAnimatingNodes(this);
|
||||
AnimationContext::startFrame();
|
||||
}
|
||||
|
||||
// Runs any animations still left in mCurrentFrameAnimations
|
||||
virtual void runRemainingAnimations(TreeInfo& info) {
|
||||
AnimationContext::runRemainingAnimations(info);
|
||||
// post all the finished stuff
|
||||
if (mOnFinishedEvents.size()) {
|
||||
sp<InvokeAnimationListeners> message
|
||||
= new InvokeAnimationListeners(mOnFinishedEvents);
|
||||
mRootNode->sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void callOnFinished(BaseRenderNodeAnimator* animator, AnimationListener* listener) {
|
||||
OnFinishedEvent event(animator, listener);
|
||||
mOnFinishedEvents.push_back(event);
|
||||
}
|
||||
|
||||
private:
|
||||
sp<RootRenderNode> mRootNode;
|
||||
std::vector<OnFinishedEvent> mOnFinishedEvents;
|
||||
};
|
||||
|
||||
class ContextFactoryImpl : public IContextFactory {
|
||||
public:
|
||||
ContextFactoryImpl(RootRenderNode* rootNode) : mRootNode(rootNode) {}
|
||||
|
||||
virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) {
|
||||
return new AnimationContextBridge(clock, mRootNode);
|
||||
}
|
||||
|
||||
private:
|
||||
RootRenderNode* mRootNode;
|
||||
};
|
||||
|
||||
static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz,
|
||||
@@ -168,8 +220,9 @@ static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, job
|
||||
|
||||
static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz,
|
||||
jboolean translucent, jlong rootRenderNodePtr) {
|
||||
RenderNode* rootRenderNode = reinterpret_cast<RenderNode*>(rootRenderNodePtr);
|
||||
return (jlong) new RenderProxy(translucent, rootRenderNode);
|
||||
RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr);
|
||||
ContextFactoryImpl factory(rootRenderNode);
|
||||
return (jlong) new RenderProxy(translucent, rootRenderNode, &factory);
|
||||
}
|
||||
|
||||
static void android_view_ThreadedRenderer_deleteProxy(JNIEnv* env, jobject clazz,
|
||||
@@ -244,6 +297,13 @@ static void android_view_ThreadedRenderer_destroyCanvasAndSurface(JNIEnv* env, j
|
||||
proxy->destroyCanvasAndSurface();
|
||||
}
|
||||
|
||||
static void android_view_ThreadedRenderer_registerAnimatingRenderNode(JNIEnv* env, jobject clazz,
|
||||
jlong rootNodePtr, jlong animatingNodePtr) {
|
||||
RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootNodePtr);
|
||||
RenderNode* animatingNode = reinterpret_cast<RenderNode*>(animatingNodePtr);
|
||||
rootRenderNode->attachAnimatingNode(animatingNode);
|
||||
}
|
||||
|
||||
static void android_view_ThreadedRenderer_invokeFunctor(JNIEnv* env, jobject clazz,
|
||||
jlong functorPtr, jboolean waitForCompletion) {
|
||||
Functor* functor = reinterpret_cast<Functor*>(functorPtr);
|
||||
@@ -371,6 +431,7 @@ static JNINativeMethod gMethods[] = {
|
||||
{ "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
|
||||
{ "nSyncAndDrawFrame", "(JJJF)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
|
||||
{ "nDestroyCanvasAndSurface", "(J)V", (void*) android_view_ThreadedRenderer_destroyCanvasAndSurface },
|
||||
{ "nRegisterAnimatingRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_registerAnimatingRenderNode },
|
||||
{ "nInvokeFunctor", "(JZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor },
|
||||
{ "nCreateDisplayListLayer", "(JII)J", (void*) android_view_ThreadedRenderer_createDisplayListLayer },
|
||||
{ "nCreateTextureLayer", "(J)J", (void*) android_view_ThreadedRenderer_createTextureLayer },
|
||||
|
||||
@@ -12,6 +12,7 @@ ifeq ($(USE_OPENGL_RENDERER),true)
|
||||
font/CacheTexture.cpp \
|
||||
font/Font.cpp \
|
||||
AmbientShadow.cpp \
|
||||
AnimationContext.cpp \
|
||||
Animator.cpp \
|
||||
AnimatorManager.cpp \
|
||||
AssetAtlas.cpp \
|
||||
|
||||
126
libs/hwui/AnimationContext.cpp
Normal file
126
libs/hwui/AnimationContext.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#include "AnimationContext.h"
|
||||
|
||||
#include "Animator.h"
|
||||
#include "RenderNode.h"
|
||||
#include "TreeInfo.h"
|
||||
#include "renderthread/TimeLord.h"
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
AnimationContext::AnimationContext(renderthread::TimeLord& clock)
|
||||
: mClock(clock)
|
||||
, mCurrentFrameAnimations(*this)
|
||||
, mNextFrameAnimations(*this)
|
||||
, mFrameTimeMs(0) {
|
||||
}
|
||||
|
||||
AnimationContext::~AnimationContext() {
|
||||
}
|
||||
|
||||
void AnimationContext::addAnimatingRenderNode(RenderNode& node) {
|
||||
if (!node.animators().hasAnimationHandle()) {
|
||||
AnimationHandle* handle = new AnimationHandle(node, *this);
|
||||
addAnimationHandle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationContext::addAnimationHandle(AnimationHandle* handle) {
|
||||
handle->insertAfter(&mNextFrameAnimations);
|
||||
}
|
||||
|
||||
void AnimationContext::startFrame() {
|
||||
LOG_ALWAYS_FATAL_IF(mCurrentFrameAnimations.mNextHandle,
|
||||
"Missed running animations last frame!");
|
||||
AnimationHandle* head = mNextFrameAnimations.mNextHandle;
|
||||
if (head) {
|
||||
mNextFrameAnimations.mNextHandle = NULL;
|
||||
mCurrentFrameAnimations.mNextHandle = head;
|
||||
head->mPreviousHandle = &mCurrentFrameAnimations;
|
||||
}
|
||||
mFrameTimeMs = mClock.computeFrameTimeMs();
|
||||
}
|
||||
|
||||
void AnimationContext::runRemainingAnimations(TreeInfo& info) {
|
||||
while (mCurrentFrameAnimations.mNextHandle) {
|
||||
AnimationHandle* current = mCurrentFrameAnimations.mNextHandle;
|
||||
AnimatorManager& animators = current->mRenderNode->animators();
|
||||
animators.pushStaging();
|
||||
animators.animateNoDamage(info);
|
||||
LOG_ALWAYS_FATAL_IF(mCurrentFrameAnimations.mNextHandle == current,
|
||||
"Animate failed to remove from current frame list!");
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationContext::callOnFinished(BaseRenderNodeAnimator* animator,
|
||||
AnimationListener* listener) {
|
||||
listener->onAnimationFinished(animator);
|
||||
}
|
||||
|
||||
AnimationHandle::AnimationHandle(AnimationContext& context)
|
||||
: mContext(context)
|
||||
, mPreviousHandle(NULL)
|
||||
, mNextHandle(NULL) {
|
||||
}
|
||||
|
||||
AnimationHandle::AnimationHandle(RenderNode& animatingNode, AnimationContext& context)
|
||||
: mRenderNode(&animatingNode)
|
||||
, mContext(context)
|
||||
, mPreviousHandle(NULL)
|
||||
, mNextHandle(NULL) {
|
||||
mRenderNode->animators().setAnimationHandle(this);
|
||||
}
|
||||
|
||||
AnimationHandle::~AnimationHandle() {
|
||||
LOG_ALWAYS_FATAL_IF(mPreviousHandle || mNextHandle,
|
||||
"AnimationHandle destroyed while still animating!");
|
||||
}
|
||||
|
||||
void AnimationHandle::notifyAnimationsRan() {
|
||||
removeFromList();
|
||||
if (mRenderNode->animators().hasAnimators()) {
|
||||
mContext.addAnimationHandle(this);
|
||||
} else {
|
||||
mRenderNode->animators().setAnimationHandle(NULL);
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationHandle::insertAfter(AnimationHandle* prev) {
|
||||
removeFromList();
|
||||
mNextHandle = prev->mNextHandle;
|
||||
if (mNextHandle) {
|
||||
mNextHandle->mPreviousHandle = this;
|
||||
}
|
||||
prev->mNextHandle = this;
|
||||
mPreviousHandle = prev;
|
||||
}
|
||||
|
||||
void AnimationHandle::removeFromList() {
|
||||
if (mPreviousHandle) {
|
||||
mPreviousHandle->mNextHandle = mNextHandle;
|
||||
}
|
||||
if (mNextHandle) {
|
||||
mNextHandle->mPreviousHandle = mPreviousHandle;
|
||||
}
|
||||
mPreviousHandle = NULL;
|
||||
mNextHandle = NULL;
|
||||
}
|
||||
|
||||
} /* namespace uirenderer */
|
||||
} /* namespace android */
|
||||
112
libs/hwui/AnimationContext.h
Normal file
112
libs/hwui/AnimationContext.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef TREEANIMATIONTRACKER_H_
|
||||
#define TREEANIMATIONTRACKER_H_
|
||||
|
||||
#include <cutils/compiler.h>
|
||||
#include <utils/RefBase.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
|
||||
#include "renderthread/TimeLord.h"
|
||||
#include "utils/Macros.h"
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class AnimationContext;
|
||||
class AnimationListener;
|
||||
class BaseRenderNodeAnimator;
|
||||
class RenderNode;
|
||||
class TreeInfo;
|
||||
|
||||
/*
|
||||
* AnimationHandle is several classes merged into one.
|
||||
* 1: It maintains the reference to the AnimationContext required to run animators.
|
||||
* 2: It keeps a strong reference to RenderNodes with animators so that
|
||||
* we don't lose them if they are no longer in the display tree. This is
|
||||
* required so that we can keep animating them, and properly notify listeners
|
||||
* of onAnimationFinished.
|
||||
* 3: It forms a doubly linked list so that we can cheaply move between states.
|
||||
*/
|
||||
class AnimationHandle {
|
||||
PREVENT_COPY_AND_ASSIGN(AnimationHandle);
|
||||
public:
|
||||
AnimationContext& context() { return mContext; }
|
||||
|
||||
void notifyAnimationsRan();
|
||||
|
||||
private:
|
||||
friend class AnimationContext;
|
||||
AnimationHandle(AnimationContext& context);
|
||||
AnimationHandle(RenderNode& animatingNode, AnimationContext& context);
|
||||
~AnimationHandle();
|
||||
|
||||
void insertAfter(AnimationHandle* prev);
|
||||
void removeFromList();
|
||||
|
||||
sp<RenderNode> mRenderNode;
|
||||
|
||||
AnimationContext& mContext;
|
||||
|
||||
AnimationHandle* mPreviousHandle;
|
||||
AnimationHandle* mNextHandle;
|
||||
};
|
||||
|
||||
class AnimationContext {
|
||||
PREVENT_COPY_AND_ASSIGN(AnimationContext);
|
||||
public:
|
||||
ANDROID_API AnimationContext(renderthread::TimeLord& clock);
|
||||
ANDROID_API virtual ~AnimationContext();
|
||||
|
||||
nsecs_t frameTimeMs() { return mFrameTimeMs; }
|
||||
bool hasAnimations() {
|
||||
return mCurrentFrameAnimations.mNextHandle
|
||||
|| mNextFrameAnimations.mNextHandle;
|
||||
}
|
||||
|
||||
// Will always add to the next frame list, which is swapped when
|
||||
// startFrame() is called
|
||||
ANDROID_API void addAnimatingRenderNode(RenderNode& node);
|
||||
|
||||
// Marks the start of a frame, which will update the frame time and move all
|
||||
// next frame animations into the current frame
|
||||
ANDROID_API virtual void startFrame();
|
||||
|
||||
// Runs any animations still left in mCurrentFrameAnimations that were not run
|
||||
// as part of the standard RenderNode:prepareTree pass.
|
||||
ANDROID_API virtual void runRemainingAnimations(TreeInfo& info);
|
||||
|
||||
ANDROID_API virtual void callOnFinished(BaseRenderNodeAnimator* animator, AnimationListener* listener);
|
||||
|
||||
private:
|
||||
friend class AnimationHandle;
|
||||
void addAnimationHandle(AnimationHandle* handle);
|
||||
|
||||
renderthread::TimeLord& mClock;
|
||||
|
||||
// Animations left to run this frame, at the end of the frame this should
|
||||
// be null
|
||||
AnimationHandle mCurrentFrameAnimations;
|
||||
// Animations queued for next frame
|
||||
AnimationHandle mNextFrameAnimations;
|
||||
|
||||
nsecs_t mFrameTimeMs;
|
||||
};
|
||||
|
||||
} /* namespace uirenderer */
|
||||
} /* namespace android */
|
||||
|
||||
#endif /* TREEANIMATIONTRACKER_H_ */
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <set>
|
||||
|
||||
#include "AnimationContext.h"
|
||||
#include "RenderNode.h"
|
||||
#include "RenderProperties.h"
|
||||
|
||||
@@ -85,7 +86,7 @@ void BaseRenderNodeAnimator::attach(RenderNode* target) {
|
||||
onAttached();
|
||||
}
|
||||
|
||||
void BaseRenderNodeAnimator::pushStaging(TreeInfo& info) {
|
||||
void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) {
|
||||
if (!mHasStartValue) {
|
||||
doSetStartValue(getValue(mTarget));
|
||||
}
|
||||
@@ -93,21 +94,22 @@ void BaseRenderNodeAnimator::pushStaging(TreeInfo& info) {
|
||||
mPlayState = mStagingPlayState;
|
||||
// Oh boy, we're starting! Man the battle stations!
|
||||
if (mPlayState == RUNNING) {
|
||||
transitionToRunning(info);
|
||||
transitionToRunning(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BaseRenderNodeAnimator::transitionToRunning(TreeInfo& info) {
|
||||
LOG_ALWAYS_FATAL_IF(info.frameTimeMs <= 0, "%" PRId64 " isn't a real frame time!", info.frameTimeMs);
|
||||
void BaseRenderNodeAnimator::transitionToRunning(AnimationContext& context) {
|
||||
nsecs_t frameTimeMs = context.frameTimeMs();
|
||||
LOG_ALWAYS_FATAL_IF(frameTimeMs <= 0, "%" PRId64 " isn't a real frame time!", frameTimeMs);
|
||||
if (mStartDelay < 0 || mStartDelay > 50000) {
|
||||
ALOGW("Your start delay is strange and confusing: %" PRId64, mStartDelay);
|
||||
}
|
||||
mStartTime = info.frameTimeMs + mStartDelay;
|
||||
mStartTime = frameTimeMs + mStartDelay;
|
||||
if (mStartTime < 0) {
|
||||
ALOGW("Ended up with a really weird start time of %" PRId64
|
||||
" with frame time %" PRId64 " and start delay %" PRId64,
|
||||
mStartTime, info.frameTimeMs, mStartDelay);
|
||||
mStartTime, frameTimeMs, mStartDelay);
|
||||
// Set to 0 so that the animate() basically instantly finishes
|
||||
mStartTime = 0;
|
||||
}
|
||||
@@ -120,7 +122,7 @@ void BaseRenderNodeAnimator::transitionToRunning(TreeInfo& info) {
|
||||
}
|
||||
}
|
||||
|
||||
bool BaseRenderNodeAnimator::animate(TreeInfo& info) {
|
||||
bool BaseRenderNodeAnimator::animate(AnimationContext& context) {
|
||||
if (mPlayState < RUNNING) {
|
||||
return false;
|
||||
}
|
||||
@@ -132,15 +134,14 @@ bool BaseRenderNodeAnimator::animate(TreeInfo& info) {
|
||||
// because the staging properties reflect the final value, we always need
|
||||
// to call setValue even if the animation isn't yet running or is still
|
||||
// being delayed as we need to override the staging value
|
||||
if (mStartTime > info.frameTimeMs) {
|
||||
info.out.hasAnimations |= true;
|
||||
if (mStartTime > context.frameTimeMs()) {
|
||||
setValue(mTarget, mFromValue);
|
||||
return false;
|
||||
}
|
||||
|
||||
float fraction = 1.0f;
|
||||
if (mPlayState == RUNNING && mDuration > 0) {
|
||||
fraction = (float)(info.frameTimeMs - mStartTime) / mDuration;
|
||||
fraction = (float)(context.frameTimeMs() - mStartTime) / mDuration;
|
||||
}
|
||||
if (fraction >= 1.0f) {
|
||||
fraction = 1.0f;
|
||||
@@ -151,21 +152,16 @@ bool BaseRenderNodeAnimator::animate(TreeInfo& info) {
|
||||
setValue(mTarget, mFromValue + (mDeltaValue * fraction));
|
||||
|
||||
if (mPlayState == FINISHED) {
|
||||
callOnFinishedListener(info);
|
||||
callOnFinishedListener(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
info.out.hasAnimations |= true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void BaseRenderNodeAnimator::callOnFinishedListener(TreeInfo& info) {
|
||||
void BaseRenderNodeAnimator::callOnFinishedListener(AnimationContext& context) {
|
||||
if (mListener.get()) {
|
||||
if (!info.animationHook) {
|
||||
mListener->onAnimationFinished(this);
|
||||
} else {
|
||||
info.animationHook->callOnFinished(this, mListener.get());
|
||||
}
|
||||
context.callOnFinished(this, mListener.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class AnimationContext;
|
||||
class BaseRenderNodeAnimator;
|
||||
class RenderNode;
|
||||
class RenderProperties;
|
||||
|
||||
@@ -50,15 +52,17 @@ public:
|
||||
ANDROID_API void setListener(AnimationListener* listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
AnimationListener* listener() { return mListener.get(); }
|
||||
ANDROID_API void start() { mStagingPlayState = RUNNING; onStagingPlayStateChanged(); }
|
||||
ANDROID_API void end() { mStagingPlayState = FINISHED; onStagingPlayStateChanged(); }
|
||||
|
||||
void attach(RenderNode* target);
|
||||
virtual void onAttached() {}
|
||||
void detach() { mTarget = 0; }
|
||||
void pushStaging(TreeInfo& info);
|
||||
bool animate(TreeInfo& info);
|
||||
void pushStaging(AnimationContext& context);
|
||||
bool animate(AnimationContext& context);
|
||||
|
||||
bool isRunning() { return mPlayState == RUNNING; }
|
||||
bool isFinished() { return mPlayState == FINISHED; }
|
||||
float finalValue() { return mFinalValue; }
|
||||
|
||||
@@ -72,7 +76,7 @@ protected:
|
||||
virtual void setValue(RenderNode* target, float value) = 0;
|
||||
RenderNode* target() { return mTarget; }
|
||||
|
||||
void callOnFinishedListener(TreeInfo& info);
|
||||
void callOnFinishedListener(AnimationContext& context);
|
||||
|
||||
virtual void onStagingPlayStateChanged() {}
|
||||
|
||||
@@ -100,7 +104,7 @@ protected:
|
||||
|
||||
private:
|
||||
inline void checkMutable();
|
||||
virtual void transitionToRunning(TreeInfo& info);
|
||||
virtual void transitionToRunning(AnimationContext& context);
|
||||
void doSetStartValue(float value);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "AnimationContext.h"
|
||||
#include "RenderNode.h"
|
||||
|
||||
namespace android {
|
||||
@@ -30,7 +31,8 @@ static void unref(BaseRenderNodeAnimator* animator) {
|
||||
}
|
||||
|
||||
AnimatorManager::AnimatorManager(RenderNode& parent)
|
||||
: mParent(parent) {
|
||||
: mParent(parent)
|
||||
, mAnimationHandle(NULL) {
|
||||
}
|
||||
|
||||
AnimatorManager::~AnimatorManager() {
|
||||
@@ -44,6 +46,11 @@ void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
|
||||
mNewAnimators.push_back(animator.get());
|
||||
}
|
||||
|
||||
void AnimatorManager::setAnimationHandle(AnimationHandle* handle) {
|
||||
LOG_ALWAYS_FATAL_IF(mAnimationHandle && handle, "Already have an AnimationHandle!");
|
||||
mAnimationHandle = handle;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void move_all(T& source, T& dest) {
|
||||
dest.reserve(source.size() + dest.size());
|
||||
@@ -53,26 +60,30 @@ static void move_all(T& source, T& dest) {
|
||||
source.clear();
|
||||
}
|
||||
|
||||
void AnimatorManager::pushStaging(TreeInfo& info) {
|
||||
void AnimatorManager::pushStaging() {
|
||||
if (mNewAnimators.size()) {
|
||||
// Since this is a straight move, we don't need to inc/dec the ref count
|
||||
move_all(mNewAnimators, mAnimators);
|
||||
}
|
||||
for (vector<BaseRenderNodeAnimator*>::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) {
|
||||
(*it)->pushStaging(info);
|
||||
(*it)->pushStaging(mAnimationHandle->context());
|
||||
}
|
||||
}
|
||||
|
||||
class AnimateFunctor {
|
||||
public:
|
||||
AnimateFunctor(RenderNode& target, TreeInfo& info)
|
||||
: dirtyMask(0), mTarget(target), mInfo(info) {}
|
||||
AnimateFunctor(TreeInfo& info, AnimationContext& context)
|
||||
: dirtyMask(0), mInfo(info), mContext(context) {}
|
||||
|
||||
bool operator() (BaseRenderNodeAnimator* animator) {
|
||||
dirtyMask |= animator->dirtyMask();
|
||||
bool remove = animator->animate(mInfo);
|
||||
bool remove = animator->animate(mContext);
|
||||
if (remove) {
|
||||
animator->decStrong(0);
|
||||
} else {
|
||||
if (animator->isRunning()) {
|
||||
mInfo.out.hasAnimations = true;
|
||||
}
|
||||
}
|
||||
return remove;
|
||||
}
|
||||
@@ -80,8 +91,8 @@ public:
|
||||
uint32_t dirtyMask;
|
||||
|
||||
private:
|
||||
RenderNode& mTarget;
|
||||
TreeInfo& mInfo;
|
||||
AnimationContext& mContext;
|
||||
};
|
||||
|
||||
uint32_t AnimatorManager::animate(TreeInfo& info) {
|
||||
@@ -93,17 +104,70 @@ uint32_t AnimatorManager::animate(TreeInfo& info) {
|
||||
mParent.damageSelf(info);
|
||||
info.damageAccumulator->popTransform();
|
||||
|
||||
AnimateFunctor functor(mParent, info);
|
||||
std::vector< BaseRenderNodeAnimator* >::iterator newEnd;
|
||||
newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
|
||||
mAnimators.erase(newEnd, mAnimators.end());
|
||||
uint32_t dirty = animateCommon(info);
|
||||
|
||||
mParent.mProperties.updateMatrix();
|
||||
info.damageAccumulator->pushTransform(&mParent);
|
||||
mParent.damageSelf(info);
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
void AnimatorManager::animateNoDamage(TreeInfo& info) {
|
||||
if (!mAnimators.size()) return;
|
||||
|
||||
animateCommon(info);
|
||||
}
|
||||
|
||||
uint32_t AnimatorManager::animateCommon(TreeInfo& info) {
|
||||
AnimateFunctor functor(info, mAnimationHandle->context());
|
||||
std::vector< BaseRenderNodeAnimator* >::iterator newEnd;
|
||||
newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
|
||||
mAnimators.erase(newEnd, mAnimators.end());
|
||||
mAnimationHandle->notifyAnimationsRan();
|
||||
return functor.dirtyMask;
|
||||
}
|
||||
|
||||
class EndAnimatorsFunctor {
|
||||
public:
|
||||
EndAnimatorsFunctor(AnimationContext& context) : mContext(context) {}
|
||||
|
||||
void operator() (BaseRenderNodeAnimator* animator) {
|
||||
animator->end();
|
||||
animator->pushStaging(mContext);
|
||||
animator->animate(mContext);
|
||||
animator->decStrong(0);
|
||||
}
|
||||
|
||||
private:
|
||||
AnimationContext& mContext;
|
||||
};
|
||||
|
||||
static void endAnimatorsHard(BaseRenderNodeAnimator* animator) {
|
||||
animator->end();
|
||||
if (animator->listener()) {
|
||||
animator->listener()->onAnimationFinished(animator);
|
||||
}
|
||||
animator->decStrong(0);
|
||||
}
|
||||
|
||||
void AnimatorManager::endAllAnimators() {
|
||||
if (mNewAnimators.size()) {
|
||||
// Since this is a straight move, we don't need to inc/dec the ref count
|
||||
move_all(mNewAnimators, mAnimators);
|
||||
}
|
||||
// First try gracefully ending them
|
||||
if (mAnimationHandle) {
|
||||
EndAnimatorsFunctor functor(mAnimationHandle->context());
|
||||
for_each(mAnimators.begin(), mAnimators.end(), functor);
|
||||
} else {
|
||||
// We have no context, so bust out the sledgehammer
|
||||
// This works because this state can only happen on the UI thread,
|
||||
// which means we're already on the right thread to invoke listeners
|
||||
for_each(mAnimators.begin(), mAnimators.end(), endAnimatorsHard);
|
||||
}
|
||||
mAnimators.clear();
|
||||
}
|
||||
|
||||
} /* namespace uirenderer */
|
||||
} /* namespace android */
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class AnimationHandle;
|
||||
class BaseRenderNodeAnimator;
|
||||
class RenderNode;
|
||||
|
||||
@@ -39,12 +40,26 @@ public:
|
||||
|
||||
void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
|
||||
|
||||
void pushStaging(TreeInfo& info);
|
||||
void setAnimationHandle(AnimationHandle* handle);
|
||||
bool hasAnimationHandle() { return mAnimationHandle; }
|
||||
|
||||
void pushStaging();
|
||||
|
||||
// Returns the combined dirty mask of all animators run
|
||||
uint32_t animate(TreeInfo& info);
|
||||
|
||||
void animateNoDamage(TreeInfo& info);
|
||||
|
||||
// Hard-ends all animators. Used for cleanup if the root is being destroyed.
|
||||
ANDROID_API void endAllAnimators();
|
||||
|
||||
bool hasAnimators() { return mAnimators.size(); }
|
||||
|
||||
private:
|
||||
uint32_t animateCommon(TreeInfo& info);
|
||||
|
||||
RenderNode& mParent;
|
||||
AnimationHandle* mAnimationHandle;
|
||||
|
||||
// To improve the efficiency of resizing & removing from the vector
|
||||
// use manual ref counting instead of sp<>.
|
||||
|
||||
39
libs/hwui/IContextFactory.h
Normal file
39
libs/hwui/IContextFactory.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef CONTEXTFACTORY_H_
|
||||
#define CONTEXTFACTORY_H_
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
namespace renderthread {
|
||||
class TimeLord;
|
||||
}
|
||||
|
||||
class AnimationContext;
|
||||
|
||||
class IContextFactory {
|
||||
public:
|
||||
virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~IContextFactory() {}
|
||||
};
|
||||
|
||||
} /* namespace uirenderer */
|
||||
} /* namespace android */
|
||||
|
||||
#endif /* CONTEXTFACTORY_H_ */
|
||||
@@ -235,7 +235,7 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
|
||||
// before properties() is trampled by stagingProperties(), as they are
|
||||
// required by some animators.
|
||||
if (CC_LIKELY(info.runAnimations)) {
|
||||
mAnimatorManager.pushStaging(info);
|
||||
mAnimatorManager.pushStaging();
|
||||
}
|
||||
if (mDirtyPropertyFields) {
|
||||
mDirtyPropertyFields = 0;
|
||||
|
||||
@@ -174,6 +174,8 @@ public:
|
||||
// UI thread only!
|
||||
ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
|
||||
|
||||
AnimatorManager& animators() { return mAnimatorManager; }
|
||||
|
||||
void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -26,18 +26,9 @@
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class BaseRenderNodeAnimator;
|
||||
class AnimationListener;
|
||||
class OpenGLRenderer;
|
||||
class RenderState;
|
||||
|
||||
class AnimationHook {
|
||||
public:
|
||||
virtual void callOnFinished(BaseRenderNodeAnimator* animator, AnimationListener* listener) = 0;
|
||||
protected:
|
||||
~AnimationHook() {}
|
||||
};
|
||||
|
||||
class ErrorHandler {
|
||||
public:
|
||||
virtual void onError(const std::string& message) = 0;
|
||||
@@ -62,8 +53,6 @@ public:
|
||||
|
||||
explicit TreeInfo(TraversalMode mode, RenderState& renderState)
|
||||
: mode(mode)
|
||||
, frameTimeMs(0)
|
||||
, animationHook(NULL)
|
||||
, prepareTextures(mode == MODE_FULL)
|
||||
, runAnimations(true)
|
||||
, damageAccumulator(NULL)
|
||||
@@ -74,8 +63,6 @@ public:
|
||||
|
||||
explicit TreeInfo(TraversalMode mode, const TreeInfo& clone)
|
||||
: mode(mode)
|
||||
, frameTimeMs(clone.frameTimeMs)
|
||||
, animationHook(clone.animationHook)
|
||||
, prepareTextures(mode == MODE_FULL)
|
||||
, runAnimations(clone.runAnimations)
|
||||
, damageAccumulator(clone.damageAccumulator)
|
||||
@@ -85,8 +72,6 @@ public:
|
||||
{}
|
||||
|
||||
const TraversalMode mode;
|
||||
nsecs_t frameTimeMs;
|
||||
AnimationHook* animationHook;
|
||||
// TODO: Remove this? Currently this is used to signal to stop preparing
|
||||
// textures if we run out of cache space.
|
||||
bool prepareTextures;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "EglManager.h"
|
||||
#include "RenderThread.h"
|
||||
#include "../AnimationContext.h"
|
||||
#include "../Caches.h"
|
||||
#include "../DeferredLayerUpdater.h"
|
||||
#include "../RenderState.h"
|
||||
@@ -35,7 +36,8 @@ namespace android {
|
||||
namespace uirenderer {
|
||||
namespace renderthread {
|
||||
|
||||
CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode)
|
||||
CanvasContext::CanvasContext(RenderThread& thread, bool translucent,
|
||||
RenderNode* rootRenderNode, IContextFactory* contextFactory)
|
||||
: mRenderThread(thread)
|
||||
, mEglManager(thread.eglManager())
|
||||
, mEglSurface(EGL_NO_SURFACE)
|
||||
@@ -44,11 +46,13 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode*
|
||||
, mCanvas(NULL)
|
||||
, mHaveNewSurface(false)
|
||||
, mRootRenderNode(rootRenderNode) {
|
||||
mAnimationContext = contextFactory->createAnimationContext(mRenderThread.timeLord());
|
||||
}
|
||||
|
||||
CanvasContext::~CanvasContext() {
|
||||
destroyCanvasAndSurface();
|
||||
mRenderThread.removeFrameCallback(this);
|
||||
delete mAnimationContext;
|
||||
}
|
||||
|
||||
void CanvasContext::destroyCanvasAndSurface() {
|
||||
@@ -136,10 +140,11 @@ void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
|
||||
void CanvasContext::prepareTree(TreeInfo& info) {
|
||||
mRenderThread.removeFrameCallback(this);
|
||||
|
||||
info.frameTimeMs = mRenderThread.timeLord().frameTimeMs();
|
||||
info.damageAccumulator = &mDamageAccumulator;
|
||||
info.renderer = mCanvas;
|
||||
mAnimationContext->startFrame();
|
||||
mRootRenderNode->prepareTree(info);
|
||||
mAnimationContext->runRemainingAnimations(info);
|
||||
|
||||
int runningBehind = 0;
|
||||
// TODO: This query is moderately expensive, investigate adding some sort
|
||||
@@ -254,7 +259,6 @@ void CanvasContext::buildLayer(RenderNode* node) {
|
||||
stopDrawing();
|
||||
|
||||
TreeInfo info(TreeInfo::MODE_FULL, mRenderThread.renderState());
|
||||
info.frameTimeMs = mRenderThread.timeLord().frameTimeMs();
|
||||
info.damageAccumulator = &mDamageAccumulator;
|
||||
info.renderer = mCanvas;
|
||||
info.runAnimations = false;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "../DamageAccumulator.h"
|
||||
#include "../DrawProfiler.h"
|
||||
#include "../IContextFactory.h"
|
||||
#include "../RenderNode.h"
|
||||
#include "RenderTask.h"
|
||||
#include "RenderThread.h"
|
||||
@@ -34,6 +35,7 @@
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class AnimationContext;
|
||||
class DeferredLayerUpdater;
|
||||
class OpenGLRenderer;
|
||||
class Rect;
|
||||
@@ -45,9 +47,11 @@ class EglManager;
|
||||
|
||||
// This per-renderer class manages the bridge between the global EGL context
|
||||
// and the render surface.
|
||||
// TODO: Rename to Renderer or some other per-window, top-level manager
|
||||
class CanvasContext : public IFrameCallback {
|
||||
public:
|
||||
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode);
|
||||
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
|
||||
IContextFactory* contextFactory);
|
||||
virtual ~CanvasContext();
|
||||
|
||||
bool initialize(ANativeWindow* window);
|
||||
@@ -105,6 +109,7 @@ private:
|
||||
OpenGLRenderer* mCanvas;
|
||||
bool mHaveNewSurface;
|
||||
DamageAccumulator mDamageAccumulator;
|
||||
AnimationContext* mAnimationContext;
|
||||
|
||||
const sp<RenderNode> mRootRenderNode;
|
||||
|
||||
|
||||
@@ -52,17 +52,20 @@ namespace renderthread {
|
||||
MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \
|
||||
ARGS(method) *args = (ARGS(method) *) task->payload()
|
||||
|
||||
CREATE_BRIDGE3(createContext, RenderThread* thread, bool translucent, RenderNode* rootRenderNode) {
|
||||
return new CanvasContext(*args->thread, args->translucent, args->rootRenderNode);
|
||||
CREATE_BRIDGE4(createContext, RenderThread* thread, bool translucent,
|
||||
RenderNode* rootRenderNode, IContextFactory* contextFactory) {
|
||||
return new CanvasContext(*args->thread, args->translucent,
|
||||
args->rootRenderNode, args->contextFactory);
|
||||
}
|
||||
|
||||
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode)
|
||||
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory)
|
||||
: mRenderThread(RenderThread::getInstance())
|
||||
, mContext(0) {
|
||||
SETUP_TASK(createContext);
|
||||
args->translucent = translucent;
|
||||
args->rootRenderNode = rootRenderNode;
|
||||
args->thread = &mRenderThread;
|
||||
args->contextFactory = contextFactory;
|
||||
mContext = (CanvasContext*) postAndWait(task);
|
||||
mDrawFrameTask.setContext(&mRenderThread, mContext);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <utils/Vector.h>
|
||||
|
||||
#include "../Caches.h"
|
||||
#include "../IContextFactory.h"
|
||||
#include "DrawFrameTask.h"
|
||||
|
||||
namespace android {
|
||||
@@ -58,7 +59,7 @@ class RenderProxyBridge;
|
||||
*/
|
||||
class ANDROID_API RenderProxy {
|
||||
public:
|
||||
ANDROID_API RenderProxy(bool translucent, RenderNode* rootNode);
|
||||
ANDROID_API RenderProxy(bool translucent, RenderNode* rootNode, IContextFactory* contextFactory);
|
||||
ANDROID_API virtual ~RenderProxy();
|
||||
|
||||
ANDROID_API void setFrameInterval(nsecs_t frameIntervalNanos);
|
||||
|
||||
@@ -30,7 +30,7 @@ void TimeLord::vsyncReceived(nsecs_t vsync) {
|
||||
}
|
||||
}
|
||||
|
||||
nsecs_t TimeLord::frameTimeMs() {
|
||||
nsecs_t TimeLord::computeFrameTimeMs() {
|
||||
// Logic copied from Choreographer.java
|
||||
nsecs_t now = systemTime(CLOCK_MONOTONIC);
|
||||
nsecs_t jitterNanos = now - mFrameTimeNanos;
|
||||
|
||||
@@ -30,7 +30,7 @@ class TimeLord {
|
||||
public:
|
||||
void setFrameInterval(nsecs_t intervalNanos) { mFrameIntervalNanos = intervalNanos; }
|
||||
void vsyncReceived(nsecs_t vsync);
|
||||
nsecs_t frameTimeMs();
|
||||
nsecs_t computeFrameTimeMs();
|
||||
|
||||
private:
|
||||
friend class RenderThread;
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.test.hwui;
|
||||
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
@@ -28,11 +27,6 @@ import android.os.Trace;
|
||||
import android.view.HardwareCanvas;
|
||||
import android.view.RenderNodeAnimator;
|
||||
import android.view.View;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.animation.OvershootInterpolator;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
Reference in New Issue
Block a user