Have RT drive window positioning

Bug: 22802885

Change-Id: I6beed5474d3a943b16e9097f7bd61ce3cbd37505
This commit is contained in:
John Reck
2016-02-02 15:18:23 -08:00
parent 15d21b3aca
commit f648108f83
15 changed files with 364 additions and 84 deletions

View File

@@ -135,6 +135,9 @@ public class RenderNode {
private RenderNode(String name, View owningView) {
mNativeRenderNode = nCreate(name);
mOwningView = owningView;
if (mOwningView instanceof SurfaceView) {
nRequestPositionUpdates(mNativeRenderNode, (SurfaceView) mOwningView);
}
}
/**
@@ -854,6 +857,8 @@ public class RenderNode {
private static native void nOutput(long renderNode);
private static native int nGetDebugSize(long renderNode);
private static native void nRequestPositionUpdates(long renderNode, SurfaceView callback);
///////////////////////////////////////////////////////////////////////////
// Animations
///////////////////////////////////////////////////////////////////////////

View File

@@ -135,7 +135,7 @@ public class SurfaceView extends View {
}
};
final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
= new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
@@ -143,6 +143,17 @@ public class SurfaceView extends View {
}
};
private final ViewTreeObserver.OnPreDrawListener mDrawListener =
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
// reposition ourselves where the surface is
mHaveFrame = getWidth() > 0 && getHeight() > 0;
updateWindow(false, false);
return true;
}
};
boolean mRequestedVisible = false;
boolean mWindowVisibility = false;
boolean mViewVisibility = false;
@@ -168,17 +179,9 @@ public class SurfaceView extends View {
boolean mUpdateWindowNeeded;
boolean mReportDrawNeeded;
private Translator mTranslator;
private int mWindowInsetLeft;
private int mWindowInsetTop;
private final ViewTreeObserver.OnPreDrawListener mDrawListener =
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
// reposition ourselves where the surface is
mHaveFrame = getWidth() > 0 && getHeight() > 0;
updateWindow(false, false);
return true;
}
};
private boolean mGlobalListenersAdded;
public SurfaceView(Context context) {
@@ -443,17 +446,17 @@ public class SurfaceView extends View {
int myHeight = mRequestedHeight;
if (myHeight <= 0) myHeight = getHeight();
getLocationInWindow(mLocation);
final boolean creating = mWindow == null;
final boolean formatChanged = mFormat != mRequestedFormat;
final boolean sizeChanged = mWindowSpaceWidth != myWidth || mWindowSpaceHeight != myHeight;
final boolean visibleChanged = mVisible != mRequestedVisible;
final boolean layoutSizeChanged = getWidth() != mLayout.width
|| getHeight() != mLayout.height;
final boolean positionChanged = mWindowSpaceLeft != mLocation[0] || mWindowSpaceTop != mLocation[1];
if (force || creating || formatChanged || sizeChanged || visibleChanged
|| mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
getLocationInWindow(mLocation);
if (DEBUG) Log.i(TAG, "Changes: creating=" + creating
+ " format=" + formatChanged + " size=" + sizeChanged
+ " visible=" + visibleChanged
@@ -643,27 +646,69 @@ public class SurfaceView extends View {
TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
" w=" + mLayout.width + " h=" + mLayout.height +
", frame=" + mSurfaceFrame);
} else if (positionChanged || layoutSizeChanged) { // Only the position has changed
mWindowSpaceLeft = mLocation[0];
mWindowSpaceTop = mLocation[1];
// For our size changed check, we keep mLayout.width and mLayout.height
// in view local space.
mLocation[0] = mLayout.width = getWidth();
mLocation[1] = mLayout.height = getHeight();
} else if (!isHardwareAccelerated()) {
getLocationInWindow(mLocation);
final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
|| mWindowSpaceTop != mLocation[1];
if (positionChanged || layoutSizeChanged) { // Only the position has changed
mWindowSpaceLeft = mLocation[0];
mWindowSpaceTop = mLocation[1];
// For our size changed check, we keep mLayout.width and mLayout.height
// in view local space.
mLocation[0] = mLayout.width = getWidth();
mLocation[1] = mLayout.height = getHeight();
transformFromViewToWindowSpace(mLocation);
transformFromViewToWindowSpace(mLocation);
try {
mSession.repositionChild(mWindow, mWindowSpaceLeft, mWindowSpaceTop,
mLocation[0], mLocation[1],
viewRoot != null ? viewRoot.getNextFrameNumber() : -1,
mWinFrame);
} catch (RemoteException ex) {
Log.e(TAG, "Exception from relayout", ex);
try {
Log.d(TAG, String.format("updateWindowPosition UI, " +
"postion = [%d, %d, %d, %d]", mWindowSpaceLeft, mWindowSpaceTop,
mLocation[0], mLocation[1]));
mSession.repositionChild(mWindow, mWindowSpaceLeft, mWindowSpaceTop,
mLocation[0], mLocation[1], -1, mWinFrame);
} catch (RemoteException ex) {
Log.e(TAG, "Exception from relayout", ex);
}
}
}
}
private Rect mRTLastReportedPosition = new Rect();
/**
* Called by native on RenderThread to update the window position
* @hide
*/
public final void updateWindowPositionRT(long frameNumber,
int left, int top, int right, int bottom) {
IWindowSession session = mSession;
MyWindow window = mWindow;
if (session == null || window == null) {
// Guess we got detached, that sucks
return;
}
if (mRTLastReportedPosition.left == left
&& mRTLastReportedPosition.top == top
&& mRTLastReportedPosition.right == right
&& mRTLastReportedPosition.bottom == bottom) {
return;
}
try {
if (DEBUG) {
Log.d(TAG, String.format("updateWindowPosition RT, frameNr = %d, " +
"postion = [%d, %d, %d, %d]", frameNumber, left, top,
right, bottom));
}
// Just using mRTLastReportedPosition as a dummy rect here
session.repositionChild(window, left, top, right, bottom, frameNumber,
mRTLastReportedPosition);
// Now overwrite mRTLastReportedPosition with our values
mRTLastReportedPosition.set(left, top, right, bottom);
} catch (RemoteException ex) {
Log.e(TAG, "Exception from repositionChild", ex);
}
}
private SurfaceHolder.Callback[] getSurfaceCallbacks() {
SurfaceHolder.Callback callbacks[];
synchronized (mCallbacks) {

View File

@@ -6815,20 +6815,6 @@ public final class ViewRootImpl implements ViewParent,
}
}
long getNextFrameNumber() {
long frameNumber = -1;
if (mSurfaceHolder != null) {
mSurfaceHolder.mSurfaceLock.lock();
}
if (mSurface.isValid()) {
frameNumber = mSurface.getNextFrameNumber();
}
if (mSurfaceHolder != null) {
mSurfaceHolder.mSurfaceLock.unlock();
}
return frameNumber;
}
class TakenSurfaceHolder extends BaseSurfaceHolder {
@Override
public boolean onAllowLockCanvas() {

View File

@@ -15,6 +15,7 @@
*/
#define LOG_TAG "OpenGLRenderer"
#define ATRACE_TAG ATRACE_TAG_VIEW
#include <EGL/egl.h>
@@ -24,7 +25,10 @@
#include <android_runtime/AndroidRuntime.h>
#include <Animator.h>
#include <DamageAccumulator.h>
#include <Matrix.h>
#include <RenderNode.h>
#include <TreeInfo.h>
#include <Paint.h>
#include "core_jni_helpers.h"
@@ -461,6 +465,69 @@ static void android_view_RenderNode_endAllAnimators(JNIEnv* env, jobject clazz,
renderNode->animators().endAllStagingAnimators();
}
// ----------------------------------------------------------------------------
// SurfaceView position callback
// ----------------------------------------------------------------------------
jmethodID gSurfaceViewPositionUpdateMethod;
static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
jlong renderNodePtr, jobject surfaceview) {
class SurfaceViewPositionUpdater : public RenderNode::PositionListener {
public:
SurfaceViewPositionUpdater(JNIEnv* env, jobject surfaceview) {
env->GetJavaVM(&mVm);
mWeakRef = env->NewWeakGlobalRef(surfaceview);
}
virtual ~SurfaceViewPositionUpdater() {
jnienv()->DeleteWeakGlobalRef(mWeakRef);
mWeakRef = nullptr;
}
virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) override {
if (CC_UNLIKELY(!mWeakRef || !info.updateWindowPositions)) return;
ATRACE_NAME("Update SurfaceView position");
JNIEnv* env = jnienv();
jobject localref = env->NewLocalRef(mWeakRef);
if (CC_UNLIKELY(!localref)) {
jnienv()->DeleteWeakGlobalRef(mWeakRef);
mWeakRef = nullptr;
return;
}
Matrix4 transform;
info.damageAccumulator->computeCurrentTransform(&transform);
const RenderProperties& props = node.properties();
uirenderer::Rect bounds(props.getWidth(), props.getHeight());
transform.mapRect(bounds);
bounds.left -= info.windowInsetLeft;
bounds.right -= info.windowInsetLeft;
bounds.top -= info.windowInsetTop;
bounds.bottom -= info.windowInsetTop;
env->CallVoidMethod(localref, gSurfaceViewPositionUpdateMethod,
(jlong) info.frameNumber, (jint) bounds.left, (jint) bounds.top,
(jint) bounds.right, (jint) bounds.bottom);
env->DeleteLocalRef(localref);
}
private:
JNIEnv* jnienv() {
JNIEnv* env;
if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", mVm);
}
return env;
}
JavaVM* mVm;
jobject mWeakRef;
};
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
renderNode->setPositionListener(new SurfaceViewPositionUpdater(env, surfaceview));
}
// ----------------------------------------------------------------------------
// JNI Glue
// ----------------------------------------------------------------------------
@@ -539,9 +606,14 @@ static const JNINativeMethod gMethods[] = {
{ "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator },
{ "nEndAllAnimators", "(J)V", (void*) android_view_RenderNode_endAllAnimators },
{ "nRequestPositionUpdates", "(JLandroid/view/SurfaceView;)V", (void*) android_view_RenderNode_requestPositionUpdates },
};
int register_android_view_RenderNode(JNIEnv* env) {
jclass clazz = FindClassOrDie(env, "android/view/SurfaceView");
gSurfaceViewPositionUpdateMethod = GetMethodIDOrDie(env, clazz,
"updateWindowPositionRT", "(JIIII)V");
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}

View File

@@ -133,7 +133,14 @@ public:
virtual void prepareTree(TreeInfo& info) {
info.errorHandler = this;
// TODO: This is hacky
info.windowInsetLeft = -stagingProperties().getLeft();
info.windowInsetTop = -stagingProperties().getTop();
info.updateWindowPositions = true;
RenderNode::prepareTree(info);
info.updateWindowPositions = false;
info.windowInsetLeft = 0;
info.windowInsetTop = 0;
info.errorHandler = NULL;
}
@@ -368,28 +375,28 @@ static void android_view_ThreadedRenderer_setName(JNIEnv* env, jobject clazz,
static void android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
sp<ANativeWindow> window = android_view_Surface_getNativeWindow(env, jsurface);
proxy->initialize(window);
sp<Surface> surface = android_view_Surface_getSurface(env, jsurface);
proxy->initialize(surface);
}
static void android_view_ThreadedRenderer_updateSurface(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
sp<ANativeWindow> window;
sp<Surface> surface;
if (jsurface) {
window = android_view_Surface_getNativeWindow(env, jsurface);
surface = android_view_Surface_getSurface(env, jsurface);
}
proxy->updateSurface(window);
proxy->updateSurface(surface);
}
static jboolean android_view_ThreadedRenderer_pauseSurface(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
sp<ANativeWindow> window;
sp<Surface> surface;
if (jsurface) {
window = android_view_Surface_getNativeWindow(env, jsurface);
surface = android_view_Surface_getSurface(env, jsurface);
}
return proxy->pauseSurface(window);
return proxy->pauseSurface(surface);
}
static void android_view_ThreadedRenderer_setup(JNIEnv* env, jobject clazz, jlong proxyPtr,

View File

@@ -57,7 +57,7 @@ public:
// Returns the current dirty area, *NOT* transformed by pushed transforms
void peekAtDirty(SkRect* dest) const;
void computeCurrentTransform(Matrix4* outMatrix) const;
ANDROID_API void computeCurrentTransform(Matrix4* outMatrix) const;
void finish(SkRect* totalDirty);

View File

@@ -381,6 +381,10 @@ void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) {
bool childFunctorsNeedLayer = mProperties.prepareForFunctorPresence(
willHaveFunctor, functorsNeedLayer);
if (CC_UNLIKELY(mPositionListener.get())) {
mPositionListener->onPositionUpdated(*this, info);
}
prepareLayer(info, animatorDirtyMask);
if (info.mode == TreeInfo::MODE_FULL) {
pushStagingDisplayListChanges(info);

View File

@@ -209,6 +209,19 @@ public:
OffscreenBuffer** getLayerHandle() { return &mLayer; } // ugh...
#endif
class ANDROID_API PositionListener {
public:
virtual ~PositionListener() {}
virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) = 0;
};
// Note this is not thread safe, this needs to be called
// before the RenderNode is used for drawing.
// RenderNode takes ownership of the pointer
ANDROID_API void setPositionListener(PositionListener* listener) {
mPositionListener.reset(listener);
}
private:
typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
@@ -317,6 +330,8 @@ private:
// This is *NOT* thread-safe, and should therefore only be tracking
// mDisplayList, not mStagingDisplayList.
uint32_t mParentCount;
std::unique_ptr<PositionListener> mPositionListener;
}; // class RenderNode
} /* namespace uirenderer */

View File

@@ -86,6 +86,12 @@ public:
#endif
ErrorHandler* errorHandler = nullptr;
// Frame number for use with synchronized surfaceview position updating
int64_t frameNumber = -1;
int32_t windowInsetLeft = 0;
int32_t windowInsetTop = 0;
bool updateWindowPositions = false;
struct Out {
bool hasFunctors = false;
// This is only updated if evaluateAnimations is true

View File

@@ -96,18 +96,18 @@ void CanvasContext::destroy() {
}
}
void CanvasContext::setSurface(ANativeWindow* window) {
void CanvasContext::setSurface(Surface* surface) {
ATRACE_CALL();
mNativeWindow = window;
mNativeSurface = surface;
if (mEglSurface != EGL_NO_SURFACE) {
mEglManager.destroySurface(mEglSurface);
mEglSurface = EGL_NO_SURFACE;
}
if (window) {
mEglSurface = mEglManager.createSurface(window);
if (surface) {
mEglSurface = mEglManager.createSurface(surface);
}
if (mEglSurface != EGL_NO_SURFACE) {
@@ -131,8 +131,8 @@ void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) {
mSwapBehavior = swapBehavior;
}
void CanvasContext::initialize(ANativeWindow* window) {
setSurface(window);
void CanvasContext::initialize(Surface* surface) {
setSurface(surface);
#if !HWUI_NEW_OPS
if (mCanvas) return;
mCanvas = new OpenGLRenderer(mRenderThread.renderState());
@@ -140,11 +140,11 @@ void CanvasContext::initialize(ANativeWindow* window) {
#endif
}
void CanvasContext::updateSurface(ANativeWindow* window) {
setSurface(window);
void CanvasContext::updateSurface(Surface* surface) {
setSurface(surface);
}
bool CanvasContext::pauseSurface(ANativeWindow* window) {
bool CanvasContext::pauseSurface(Surface* surface) {
return mRenderThread.removeFrameCallback(this);
}
@@ -208,6 +208,10 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
info.renderer = mCanvas;
#endif
if (CC_LIKELY(mNativeSurface.get())) {
info.frameNumber = static_cast<int64_t>(mNativeSurface->getNextFrameNumber());
}
mAnimationContext->startFrame(info.mode);
for (const sp<RenderNode>& node : mRenderNodes) {
// Only the primary target node will be drawn full - all other nodes would get drawn in
@@ -223,7 +227,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
freePrefetechedLayers();
GL_CHECKPOINT(MODERATE);
if (CC_UNLIKELY(!mNativeWindow.get())) {
if (CC_UNLIKELY(!mNativeSurface.get())) {
mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
info.out.canDrawThisFrame = false;
return;
@@ -246,8 +250,9 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
} else {
// We're maybe behind? Find out for sure
int runningBehind = 0;
mNativeWindow->query(mNativeWindow.get(),
NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
// TODO: Have this method be on Surface, too, not just ANativeWindow...
ANativeWindow* window = mNativeSurface.get();
window->query(window, NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
info.out.canDrawThisFrame = !runningBehind;
}
} else {

View File

@@ -38,6 +38,7 @@
#include <SkBitmap.h>
#include <SkRect.h>
#include <utils/Functor.h>
#include <gui/Surface.h>
#include <set>
#include <string>
@@ -74,10 +75,10 @@ public:
// Won't take effect until next EGLSurface creation
void setSwapBehavior(SwapBehavior swapBehavior);
void initialize(ANativeWindow* window);
void updateSurface(ANativeWindow* window);
bool pauseSurface(ANativeWindow* window);
bool hasSurface() { return mNativeWindow.get(); }
void initialize(Surface* surface);
void updateSurface(Surface* surface);
bool pauseSurface(Surface* surface);
bool hasSurface() { return mNativeSurface.get(); }
void setup(int width, int height, float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
@@ -171,7 +172,7 @@ private:
// lifecycle tracking
friend class android::uirenderer::RenderState;
void setSurface(ANativeWindow* window);
void setSurface(Surface* window);
void requireSurface();
void freePrefetechedLayers();
@@ -181,7 +182,7 @@ private:
RenderThread& mRenderThread;
EglManager& mEglManager;
sp<ANativeWindow> mNativeWindow;
sp<Surface> mNativeSurface;
EGLSurface mEglSurface = EGL_NO_SURFACE;
bool mBufferPreserved = false;
SwapBehavior mSwapBehavior = kSwap_default;

View File

@@ -139,38 +139,38 @@ void RenderProxy::setName(const char* name) {
postAndWait(task); // block since name/value pointers owned by caller
}
CREATE_BRIDGE2(initialize, CanvasContext* context, ANativeWindow* window) {
args->context->initialize(args->window);
CREATE_BRIDGE2(initialize, CanvasContext* context, Surface* surface) {
args->context->initialize(args->surface);
return nullptr;
}
void RenderProxy::initialize(const sp<ANativeWindow>& window) {
void RenderProxy::initialize(const sp<Surface>& surface) {
SETUP_TASK(initialize);
args->context = mContext;
args->window = window.get();
args->surface = surface.get();
post(task);
}
CREATE_BRIDGE2(updateSurface, CanvasContext* context, ANativeWindow* window) {
args->context->updateSurface(args->window);
CREATE_BRIDGE2(updateSurface, CanvasContext* context, Surface* surface) {
args->context->updateSurface(args->surface);
return nullptr;
}
void RenderProxy::updateSurface(const sp<ANativeWindow>& window) {
void RenderProxy::updateSurface(const sp<Surface>& surface) {
SETUP_TASK(updateSurface);
args->context = mContext;
args->window = window.get();
args->surface = surface.get();
postAndWait(task);
}
CREATE_BRIDGE2(pauseSurface, CanvasContext* context, ANativeWindow* window) {
return (void*) args->context->pauseSurface(args->window);
CREATE_BRIDGE2(pauseSurface, CanvasContext* context, Surface* surface) {
return (void*) args->context->pauseSurface(args->surface);
}
bool RenderProxy::pauseSurface(const sp<ANativeWindow>& window) {
bool RenderProxy::pauseSurface(const sp<Surface>& surface) {
SETUP_TASK(pauseSurface);
args->context = mContext;
args->window = window.get();
args->surface = surface.get();
return (bool) postAndWait(task);
}

View File

@@ -67,9 +67,9 @@ public:
ANDROID_API bool loadSystemProperties();
ANDROID_API void setName(const char* name);
ANDROID_API void initialize(const sp<ANativeWindow>& window);
ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
ANDROID_API bool pauseSurface(const sp<ANativeWindow>& window);
ANDROID_API void initialize(const sp<Surface>& surface);
ANDROID_API void updateSurface(const sp<Surface>& surface);
ANDROID_API bool pauseSurface(const sp<Surface>& surface);
ANDROID_API void setup(int width, int height, float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
ANDROID_API void setLightCenter(const Vector3& lightCenter);

View File

@@ -355,6 +355,15 @@
</intent-filter>
</activity>
<activity
android:name="MovingSurfaceViewActivity"
android:label="SurfaceView/Animated Movement">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="GLTextureViewActivity"
android:label="TextureView/OpenGL">

View File

@@ -0,0 +1,125 @@
/*
* Copyright (C) 2016 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.ObjectAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
public class MovingSurfaceViewActivity extends Activity implements Callback {
static final String TAG = "MovingSurfaceView";
SurfaceView mSurfaceView;
ObjectAnimator mAnimator;
class MySurfaceView extends SurfaceView {
boolean mSlowToggled;
public MySurfaceView(Context context) {
super(context);
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mSlowToggled = !mSlowToggled;
Log.d(TAG, "SLOW MODE: " + mSlowToggled);
invalidate();
}
});
setWillNotDraw(false);
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mSlowToggled) {
try {
Thread.sleep(16);
} catch (InterruptedException e) {}
}
}
public void setMyTranslationY(float ty) {
setTranslationY(ty);
if (mSlowToggled) {
invalidate();
}
}
public float getMyTranslationY() {
return getTranslationY();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FrameLayout content = new FrameLayout(this);
mSurfaceView = new MySurfaceView(this);
mSurfaceView.getHolder().addCallback(this);
final float density = getResources().getDisplayMetrics().density;
int size = (int) (200 * density);
content.addView(mSurfaceView, new FrameLayout.LayoutParams(
size, size, Gravity.CENTER));
mAnimator = ObjectAnimator.ofFloat(mSurfaceView, "myTranslationY",
0, size);
mAnimator.setRepeatMode(ObjectAnimator.REVERSE);
mAnimator.setRepeatCount(ObjectAnimator.INFINITE);
mAnimator.setDuration(200);
mAnimator.setInterpolator(new LinearInterpolator());
setContentView(content);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Canvas canvas = holder.lockCanvas();
canvas.drawARGB(0xFF, 0x00, 0xFF, 0x00);
holder.unlockCanvasAndPost(canvas);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
protected void onResume() {
super.onResume();
mAnimator.start();
}
@Override
protected void onPause() {
mAnimator.pause();
super.onPause();
}
}