Bug: 78866720 Test: Manual + systrace; existing CTS Previously, we set hasAnimations to true when the AnimatedImageDrawable, so that we would get a call to redraw. But if the image does not need to show its next frame yet, the redraw was unnecessary. Instead, add a new field to TreeInfo::Out, representing the delay time until the image will need to be redrawn - i.e. when the duration of the current frame has passed. Each call to prepareTree will post at most one message to redraw, in time for the earliest animated image to be redrawn. Post the message for one rendered frame ahead of time, so that when it is time to show the next frame, the image has already gotten the message to update. On a screen with a single animated image, this drops the number of calls to dispatchFrameCallbacks to as infrequent as possible. It is called only when we need to draw a new frame of the image. On a screen with multiple animated images, the calls may be redundant, but they will not be more frequent than they would be without this change. Switch to nsecs_t and systemTime internally, matching the rest of HWUI. Remove mDidDraw and related. Its purpose was to prevent advancing the animation while the image is not being drawn. But it isn't really necessary. If it's not drawn, onDraw is not called, which is where we trigger decoding. And onDraw already has a defense against getting too far ahead - if its timer indicates that it should skip a frame or show it very briefly, it will back up its timer. More importantly, mDidDraw caused a bug, when combined with less frequent redraws. If the display list containing the drawable doesn't need to be redrawn for other reasons, the drawable's timer never advanced, so its animation stopped. Fix software drawing. Compute the milliseconds in the future to draw the next frame, and add that to SystemClock.uptimeMillis() to compute the time to pass to scheduleSelf. Change-Id: I13aab49922fa300f73b327be25561d7120c09ec4
605 lines
21 KiB
Java
605 lines
21 KiB
Java
/*
|
|
* Copyright (C) 2018 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 android.graphics.drawable;
|
|
|
|
import android.annotation.IntRange;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.content.res.AssetFileDescriptor;
|
|
import android.content.res.Resources;
|
|
import android.content.res.Resources.Theme;
|
|
import android.content.res.TypedArray;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.ColorFilter;
|
|
import android.graphics.ImageDecoder;
|
|
import android.graphics.PixelFormat;
|
|
import android.graphics.Rect;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.os.SystemClock;
|
|
import android.util.AttributeSet;
|
|
import android.util.DisplayMetrics;
|
|
import android.util.TypedValue;
|
|
import android.view.View;
|
|
|
|
import com.android.internal.R;
|
|
|
|
import dalvik.annotation.optimization.FastNative;
|
|
|
|
import libcore.util.NativeAllocationRegistry;
|
|
|
|
import org.xmlpull.v1.XmlPullParser;
|
|
import org.xmlpull.v1.XmlPullParserException;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.util.ArrayList;
|
|
|
|
/**
|
|
* {@link Drawable} for drawing animated images (like GIF).
|
|
*
|
|
* <p>The framework handles decoding subsequent frames in another thread and
|
|
* updating when necessary. The drawable will only animate while it is being
|
|
* displayed.</p>
|
|
*
|
|
* <p>Created by {@link ImageDecoder#decodeDrawable}. A user needs to call
|
|
* {@link #start} to start the animation.</p>
|
|
*
|
|
* <p>It can also be defined in XML using the <code><animated-image></code>
|
|
* element.</p>
|
|
*
|
|
* @attr ref android.R.styleable#AnimatedImageDrawable_src
|
|
* @attr ref android.R.styleable#AnimatedImageDrawable_autoStart
|
|
* @attr ref android.R.styleable#AnimatedImageDrawable_repeatCount
|
|
* @attr ref android.R.styleable#AnimatedImageDrawable_autoMirrored
|
|
*/
|
|
public class AnimatedImageDrawable extends Drawable implements Animatable2 {
|
|
private int mIntrinsicWidth;
|
|
private int mIntrinsicHeight;
|
|
|
|
private boolean mStarting;
|
|
|
|
private Handler mHandler;
|
|
|
|
private class State {
|
|
State(long nativePtr, InputStream is, AssetFileDescriptor afd) {
|
|
mNativePtr = nativePtr;
|
|
mInputStream = is;
|
|
mAssetFd = afd;
|
|
}
|
|
|
|
final long mNativePtr;
|
|
|
|
// These just keep references so the native code can continue using them.
|
|
private final InputStream mInputStream;
|
|
private final AssetFileDescriptor mAssetFd;
|
|
|
|
int[] mThemeAttrs = null;
|
|
boolean mAutoMirrored = false;
|
|
int mRepeatCount = REPEAT_UNDEFINED;
|
|
}
|
|
|
|
private State mState;
|
|
|
|
private Runnable mRunnable;
|
|
|
|
private ColorFilter mColorFilter;
|
|
|
|
/**
|
|
* Pass this to {@link #setRepeatCount} to repeat infinitely.
|
|
*
|
|
* <p>{@link Animatable2.AnimationCallback#onAnimationEnd} will never be
|
|
* called unless there is an error.</p>
|
|
*/
|
|
public static final int REPEAT_INFINITE = -1;
|
|
|
|
/** @removed
|
|
* @deprecated Replaced with REPEAT_INFINITE to match other APIs.
|
|
*/
|
|
@java.lang.Deprecated
|
|
public static final int LOOP_INFINITE = REPEAT_INFINITE;
|
|
|
|
private static final int REPEAT_UNDEFINED = -2;
|
|
|
|
/**
|
|
* Specify the number of times to repeat the animation.
|
|
*
|
|
* <p>By default, the repeat count in the encoded data is respected. If set
|
|
* to {@link #REPEAT_INFINITE}, the animation will repeat as long as it is
|
|
* displayed. If the value is {@code 0}, the animation will play once.</p>
|
|
*
|
|
* <p>This call replaces the current repeat count. If the encoded data
|
|
* specified a repeat count of {@code 2} (meaning that
|
|
* {@link #getRepeatCount()} returns {@code 2}, the animation will play
|
|
* three times. Calling {@code setRepeatCount(1)} will result in playing only
|
|
* twice and {@link #getRepeatCount()} returning {@code 1}.</p>
|
|
*
|
|
* <p>If the animation is already playing, the iterations that have already
|
|
* occurred count towards the new count. If the animation has already
|
|
* repeated the appropriate number of times (or more), it will finish its
|
|
* current iteration and then stop.</p>
|
|
*/
|
|
public void setRepeatCount(@IntRange(from = REPEAT_INFINITE) int repeatCount) {
|
|
if (repeatCount < REPEAT_INFINITE) {
|
|
throw new IllegalArgumentException("invalid value passed to setRepeatCount"
|
|
+ repeatCount);
|
|
}
|
|
if (mState.mRepeatCount != repeatCount) {
|
|
mState.mRepeatCount = repeatCount;
|
|
if (mState.mNativePtr != 0) {
|
|
nSetRepeatCount(mState.mNativePtr, repeatCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @removed
|
|
* @deprecated Replaced with setRepeatCount to match other APIs.
|
|
*/
|
|
@java.lang.Deprecated
|
|
public void setLoopCount(int loopCount) {
|
|
setRepeatCount(loopCount);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the number of times the animation will repeat.
|
|
*
|
|
* <p>By default, the repeat count in the encoded data is respected. If the
|
|
* value is {@link #REPEAT_INFINITE}, the animation will repeat as long as
|
|
* it is displayed. If the value is {@code 0}, it will play once.</p>
|
|
*
|
|
* <p>Calling {@link #setRepeatCount} will make future calls to this method
|
|
* return the value passed to {@link #setRepeatCount}.</p>
|
|
*/
|
|
public int getRepeatCount() {
|
|
if (mState.mNativePtr == 0) {
|
|
throw new IllegalStateException("called getRepeatCount on empty AnimatedImageDrawable");
|
|
}
|
|
if (mState.mRepeatCount == REPEAT_UNDEFINED) {
|
|
mState.mRepeatCount = nGetRepeatCount(mState.mNativePtr);
|
|
|
|
}
|
|
return mState.mRepeatCount;
|
|
}
|
|
|
|
/** @removed
|
|
* @deprecated Replaced with getRepeatCount to match other APIs.
|
|
*/
|
|
@java.lang.Deprecated
|
|
public int getLoopCount(int loopCount) {
|
|
return getRepeatCount();
|
|
}
|
|
|
|
/**
|
|
* Create an empty AnimatedImageDrawable.
|
|
*/
|
|
public AnimatedImageDrawable() {
|
|
mState = new State(0, null, null);
|
|
}
|
|
|
|
@Override
|
|
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
|
|
throws XmlPullParserException, IOException {
|
|
super.inflate(r, parser, attrs, theme);
|
|
|
|
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedImageDrawable);
|
|
updateStateFromTypedArray(a, mSrcDensityOverride);
|
|
}
|
|
|
|
private void updateStateFromTypedArray(TypedArray a, int srcDensityOverride)
|
|
throws XmlPullParserException {
|
|
State oldState = mState;
|
|
final Resources r = a.getResources();
|
|
final int srcResId = a.getResourceId(R.styleable.AnimatedImageDrawable_src, 0);
|
|
if (srcResId != 0) {
|
|
// Follow the density handling in BitmapDrawable.
|
|
final TypedValue value = new TypedValue();
|
|
r.getValueForDensity(srcResId, srcDensityOverride, value, true);
|
|
if (srcDensityOverride > 0 && value.density > 0
|
|
&& value.density != TypedValue.DENSITY_NONE) {
|
|
if (value.density == srcDensityOverride) {
|
|
value.density = r.getDisplayMetrics().densityDpi;
|
|
} else {
|
|
value.density =
|
|
(value.density * r.getDisplayMetrics().densityDpi) / srcDensityOverride;
|
|
}
|
|
}
|
|
|
|
int density = Bitmap.DENSITY_NONE;
|
|
if (value.density == TypedValue.DENSITY_DEFAULT) {
|
|
density = DisplayMetrics.DENSITY_DEFAULT;
|
|
} else if (value.density != TypedValue.DENSITY_NONE) {
|
|
density = value.density;
|
|
}
|
|
|
|
Drawable drawable = null;
|
|
try {
|
|
InputStream is = r.openRawResource(srcResId, value);
|
|
ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
|
|
drawable = ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
|
|
if (!info.isAnimated()) {
|
|
throw new IllegalArgumentException("image is not animated");
|
|
}
|
|
});
|
|
} catch (IOException e) {
|
|
throw new XmlPullParserException(a.getPositionDescription() +
|
|
": <animated-image> requires a valid 'src' attribute", null, e);
|
|
}
|
|
|
|
if (!(drawable instanceof AnimatedImageDrawable)) {
|
|
throw new XmlPullParserException(a.getPositionDescription() +
|
|
": <animated-image> did not decode animated");
|
|
}
|
|
|
|
// This may have previously been set without a src if we were waiting for a
|
|
// theme.
|
|
final int repeatCount = mState.mRepeatCount;
|
|
// Transfer the state of other to this one. other will be discarded.
|
|
AnimatedImageDrawable other = (AnimatedImageDrawable) drawable;
|
|
mState = other.mState;
|
|
other.mState = null;
|
|
mIntrinsicWidth = other.mIntrinsicWidth;
|
|
mIntrinsicHeight = other.mIntrinsicHeight;
|
|
if (repeatCount != REPEAT_UNDEFINED) {
|
|
this.setRepeatCount(repeatCount);
|
|
}
|
|
}
|
|
|
|
mState.mThemeAttrs = a.extractThemeAttrs();
|
|
if (mState.mNativePtr == 0 && (mState.mThemeAttrs == null
|
|
|| mState.mThemeAttrs[R.styleable.AnimatedImageDrawable_src] == 0)) {
|
|
throw new XmlPullParserException(a.getPositionDescription() +
|
|
": <animated-image> requires a valid 'src' attribute");
|
|
}
|
|
|
|
mState.mAutoMirrored = a.getBoolean(
|
|
R.styleable.AnimatedImageDrawable_autoMirrored, oldState.mAutoMirrored);
|
|
|
|
int repeatCount = a.getInt(
|
|
R.styleable.AnimatedImageDrawable_repeatCount, REPEAT_UNDEFINED);
|
|
if (repeatCount != REPEAT_UNDEFINED) {
|
|
this.setRepeatCount(repeatCount);
|
|
}
|
|
|
|
boolean autoStart = a.getBoolean(
|
|
R.styleable.AnimatedImageDrawable_autoStart, false);
|
|
if (autoStart && mState.mNativePtr != 0) {
|
|
this.start();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
* This should only be called by ImageDecoder.
|
|
*
|
|
* decoder is only non-null if it has a PostProcess
|
|
*/
|
|
public AnimatedImageDrawable(long nativeImageDecoder,
|
|
@Nullable ImageDecoder decoder, int width, int height,
|
|
int srcDensity, int dstDensity, Rect cropRect,
|
|
InputStream inputStream, AssetFileDescriptor afd)
|
|
throws IOException {
|
|
width = Bitmap.scaleFromDensity(width, srcDensity, dstDensity);
|
|
height = Bitmap.scaleFromDensity(height, srcDensity, dstDensity);
|
|
|
|
if (cropRect == null) {
|
|
mIntrinsicWidth = width;
|
|
mIntrinsicHeight = height;
|
|
} else {
|
|
cropRect.set(Bitmap.scaleFromDensity(cropRect.left, srcDensity, dstDensity),
|
|
Bitmap.scaleFromDensity(cropRect.top, srcDensity, dstDensity),
|
|
Bitmap.scaleFromDensity(cropRect.right, srcDensity, dstDensity),
|
|
Bitmap.scaleFromDensity(cropRect.bottom, srcDensity, dstDensity));
|
|
mIntrinsicWidth = cropRect.width();
|
|
mIntrinsicHeight = cropRect.height();
|
|
}
|
|
|
|
mState = new State(nCreate(nativeImageDecoder, decoder, width, height, cropRect),
|
|
inputStream, afd);
|
|
|
|
final long nativeSize = nNativeByteSize(mState.mNativePtr);
|
|
NativeAllocationRegistry registry = new NativeAllocationRegistry(
|
|
AnimatedImageDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
|
|
registry.registerNativeAllocation(mState, mState.mNativePtr);
|
|
}
|
|
|
|
@Override
|
|
public int getIntrinsicWidth() {
|
|
return mIntrinsicWidth;
|
|
}
|
|
|
|
@Override
|
|
public int getIntrinsicHeight() {
|
|
return mIntrinsicHeight;
|
|
}
|
|
|
|
// nDraw returns -1 if the animation has finished.
|
|
private static final int FINISHED = -1;
|
|
|
|
@Override
|
|
public void draw(@NonNull Canvas canvas) {
|
|
if (mState.mNativePtr == 0) {
|
|
throw new IllegalStateException("called draw on empty AnimatedImageDrawable");
|
|
}
|
|
|
|
if (mStarting) {
|
|
mStarting = false;
|
|
|
|
postOnAnimationStart();
|
|
}
|
|
|
|
long nextUpdate = nDraw(mState.mNativePtr, canvas.getNativeCanvasWrapper());
|
|
// a value <= 0 indicates that the drawable is stopped or that renderThread
|
|
// will manage the animation
|
|
if (nextUpdate > 0) {
|
|
if (mRunnable == null) {
|
|
mRunnable = this::invalidateSelf;
|
|
}
|
|
scheduleSelf(mRunnable, nextUpdate + SystemClock.uptimeMillis());
|
|
} else if (nextUpdate == FINISHED) {
|
|
// This means the animation was drawn in software mode and ended.
|
|
postOnAnimationEnd();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
|
|
if (alpha < 0 || alpha > 255) {
|
|
throw new IllegalArgumentException("Alpha must be between 0 and"
|
|
+ " 255! provided " + alpha);
|
|
}
|
|
|
|
if (mState.mNativePtr == 0) {
|
|
throw new IllegalStateException("called setAlpha on empty AnimatedImageDrawable");
|
|
}
|
|
|
|
nSetAlpha(mState.mNativePtr, alpha);
|
|
invalidateSelf();
|
|
}
|
|
|
|
@Override
|
|
public int getAlpha() {
|
|
if (mState.mNativePtr == 0) {
|
|
throw new IllegalStateException("called getAlpha on empty AnimatedImageDrawable");
|
|
}
|
|
return nGetAlpha(mState.mNativePtr);
|
|
}
|
|
|
|
@Override
|
|
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
|
if (mState.mNativePtr == 0) {
|
|
throw new IllegalStateException("called setColorFilter on empty AnimatedImageDrawable");
|
|
}
|
|
|
|
if (colorFilter != mColorFilter) {
|
|
mColorFilter = colorFilter;
|
|
long nativeFilter = colorFilter == null ? 0 : colorFilter.getNativeInstance();
|
|
nSetColorFilter(mState.mNativePtr, nativeFilter);
|
|
invalidateSelf();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
@Nullable
|
|
public ColorFilter getColorFilter() {
|
|
return mColorFilter;
|
|
}
|
|
|
|
@Override
|
|
public @PixelFormat.Opacity int getOpacity() {
|
|
return PixelFormat.TRANSLUCENT;
|
|
}
|
|
|
|
@Override
|
|
public void setAutoMirrored(boolean mirrored) {
|
|
if (mState.mAutoMirrored != mirrored) {
|
|
mState.mAutoMirrored = mirrored;
|
|
if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL && mState.mNativePtr != 0) {
|
|
nSetMirrored(mState.mNativePtr, mirrored);
|
|
invalidateSelf();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onLayoutDirectionChanged(int layoutDirection) {
|
|
if (!mState.mAutoMirrored || mState.mNativePtr == 0) {
|
|
return false;
|
|
}
|
|
|
|
final boolean mirror = layoutDirection == View.LAYOUT_DIRECTION_RTL;
|
|
nSetMirrored(mState.mNativePtr, mirror);
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public final boolean isAutoMirrored() {
|
|
return mState.mAutoMirrored;
|
|
}
|
|
|
|
// Animatable overrides
|
|
/**
|
|
* Return whether the animation is currently running.
|
|
*
|
|
* <p>When this drawable is created, this will return {@code false}. A client
|
|
* needs to call {@link #start} to start the animation.</p>
|
|
*/
|
|
@Override
|
|
public boolean isRunning() {
|
|
if (mState.mNativePtr == 0) {
|
|
throw new IllegalStateException("called isRunning on empty AnimatedImageDrawable");
|
|
}
|
|
return nIsRunning(mState.mNativePtr);
|
|
}
|
|
|
|
/**
|
|
* Start the animation.
|
|
*
|
|
* <p>Does nothing if the animation is already running. If the animation is stopped,
|
|
* this will reset it.</p>
|
|
*
|
|
* <p>When the drawable is drawn, starting the animation,
|
|
* {@link Animatable2.AnimationCallback#onAnimationStart} will be called.</p>
|
|
*/
|
|
@Override
|
|
public void start() {
|
|
if (mState.mNativePtr == 0) {
|
|
throw new IllegalStateException("called start on empty AnimatedImageDrawable");
|
|
}
|
|
|
|
if (nStart(mState.mNativePtr)) {
|
|
mStarting = true;
|
|
invalidateSelf();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop the animation.
|
|
*
|
|
* <p>If the animation is stopped, it will continue to display the frame
|
|
* it was displaying when stopped.</p>
|
|
*/
|
|
@Override
|
|
public void stop() {
|
|
if (mState.mNativePtr == 0) {
|
|
throw new IllegalStateException("called stop on empty AnimatedImageDrawable");
|
|
}
|
|
if (nStop(mState.mNativePtr)) {
|
|
postOnAnimationEnd();
|
|
}
|
|
}
|
|
|
|
// Animatable2 overrides
|
|
private ArrayList<Animatable2.AnimationCallback> mAnimationCallbacks = null;
|
|
|
|
@Override
|
|
public void registerAnimationCallback(@NonNull AnimationCallback callback) {
|
|
if (callback == null) {
|
|
return;
|
|
}
|
|
|
|
if (mAnimationCallbacks == null) {
|
|
mAnimationCallbacks = new ArrayList<Animatable2.AnimationCallback>();
|
|
nSetOnAnimationEndListener(mState.mNativePtr, this);
|
|
}
|
|
|
|
if (!mAnimationCallbacks.contains(callback)) {
|
|
mAnimationCallbacks.add(callback);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) {
|
|
if (callback == null || mAnimationCallbacks == null
|
|
|| !mAnimationCallbacks.remove(callback)) {
|
|
return false;
|
|
}
|
|
|
|
if (mAnimationCallbacks.isEmpty()) {
|
|
clearAnimationCallbacks();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void clearAnimationCallbacks() {
|
|
if (mAnimationCallbacks != null) {
|
|
mAnimationCallbacks = null;
|
|
nSetOnAnimationEndListener(mState.mNativePtr, null);
|
|
}
|
|
}
|
|
|
|
private void postOnAnimationStart() {
|
|
if (mAnimationCallbacks == null) {
|
|
return;
|
|
}
|
|
|
|
getHandler().post(() -> {
|
|
for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
|
|
callback.onAnimationStart(this);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void postOnAnimationEnd() {
|
|
if (mAnimationCallbacks == null) {
|
|
return;
|
|
}
|
|
|
|
getHandler().post(() -> {
|
|
for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
|
|
callback.onAnimationEnd(this);
|
|
}
|
|
});
|
|
}
|
|
|
|
private Handler getHandler() {
|
|
if (mHandler == null) {
|
|
mHandler = new Handler(Looper.getMainLooper());
|
|
}
|
|
return mHandler;
|
|
}
|
|
|
|
/**
|
|
* Called by JNI.
|
|
*
|
|
* The JNI code has already posted this to the thread that created the
|
|
* callback, so no need to post.
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
private void onAnimationEnd() {
|
|
if (mAnimationCallbacks != null) {
|
|
for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
|
|
callback.onAnimationEnd(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private static native long nCreate(long nativeImageDecoder,
|
|
@Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
|
|
throws IOException;
|
|
@FastNative
|
|
private static native long nGetNativeFinalizer();
|
|
private static native long nDraw(long nativePtr, long canvasNativePtr);
|
|
@FastNative
|
|
private static native void nSetAlpha(long nativePtr, int alpha);
|
|
@FastNative
|
|
private static native int nGetAlpha(long nativePtr);
|
|
@FastNative
|
|
private static native void nSetColorFilter(long nativePtr, long nativeFilter);
|
|
@FastNative
|
|
private static native boolean nIsRunning(long nativePtr);
|
|
// Return whether the animation started.
|
|
@FastNative
|
|
private static native boolean nStart(long nativePtr);
|
|
@FastNative
|
|
private static native boolean nStop(long nativePtr);
|
|
@FastNative
|
|
private static native int nGetRepeatCount(long nativePtr);
|
|
@FastNative
|
|
private static native void nSetRepeatCount(long nativePtr, int repeatCount);
|
|
// Pass the drawable down to native so it can call onAnimationEnd.
|
|
private static native void nSetOnAnimationEndListener(long nativePtr,
|
|
@Nullable AnimatedImageDrawable drawable);
|
|
@FastNative
|
|
private static native long nNativeByteSize(long nativePtr);
|
|
@FastNative
|
|
private static native void nSetMirrored(long nativePtr, boolean mirror);
|
|
}
|