mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add an example to integrate android.animation
PiperOrigin-RevId: 687274900
This commit is contained in:
parent
2a49ffcb23
commit
f52c7a1d5c
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2024 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 androidx.media3.demo.transformer;
|
||||
|
||||
import android.animation.FloatEvaluator;
|
||||
import android.animation.Keyframe;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.animation.TypeEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.util.Pair;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import androidx.media3.common.VideoFrameProcessingException;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.effect.DrawableOverlay;
|
||||
import androidx.media3.effect.OverlaySettings;
|
||||
import androidx.media3.effect.TextureOverlay;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
/**
|
||||
* An animated {@link TextureOverlay} using {@link android.animation}.
|
||||
*
|
||||
* <p>The rotation is controlled by a simple {@link ValueAnimator}, while the position is controlled
|
||||
* by key frames.
|
||||
*/
|
||||
public class AnimatedLogoOverlay extends DrawableOverlay {
|
||||
|
||||
private static final long ROTATION_PERIOD_MS = 2_000;
|
||||
private static final long POSITION_PERIOD_MS = 5_000;
|
||||
private static final float POSITION_X_BOUND = 0.8f;
|
||||
private static final float POSITION_Y_BOUND = 0.7f;
|
||||
|
||||
private final Drawable logo;
|
||||
private final AnimatedOverlaySettings overlaySettings;
|
||||
|
||||
public AnimatedLogoOverlay(Context context) {
|
||||
try {
|
||||
logo = context.getPackageManager().getApplicationIcon(context.getPackageName());
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
logo.setBounds(
|
||||
/* left= */ 0, /* top= */ 0, logo.getIntrinsicWidth(), logo.getIntrinsicHeight());
|
||||
|
||||
ValueAnimator rotationAnimator = ValueAnimator.ofFloat(0, 360);
|
||||
rotationAnimator.setRepeatMode(ValueAnimator.RESTART);
|
||||
rotationAnimator.setRepeatCount(ValueAnimator.INFINITE);
|
||||
rotationAnimator.setDuration(ROTATION_PERIOD_MS);
|
||||
// Rotate the logo with a constant angular velocity.
|
||||
rotationAnimator.setInterpolator(new LinearInterpolator());
|
||||
|
||||
Keyframe[] keyFrames = new Keyframe[5];
|
||||
keyFrames[0] =
|
||||
Keyframe.ofObject(/* fraction= */ 0f, Pair.create(-POSITION_X_BOUND, -POSITION_Y_BOUND));
|
||||
keyFrames[2] =
|
||||
Keyframe.ofObject(/* fraction= */ 0.5f, Pair.create(POSITION_X_BOUND, POSITION_Y_BOUND));
|
||||
keyFrames[1] =
|
||||
Keyframe.ofObject(/* fraction= */ 0.25f, Pair.create(-POSITION_X_BOUND, POSITION_Y_BOUND));
|
||||
keyFrames[3] =
|
||||
Keyframe.ofObject(/* fraction= */ 0.75f, Pair.create(POSITION_X_BOUND, -POSITION_Y_BOUND));
|
||||
keyFrames[4] =
|
||||
Keyframe.ofObject(/* fraction= */ 1f, Pair.create(-POSITION_X_BOUND, -POSITION_Y_BOUND));
|
||||
PropertyValuesHolder positionValuesHolder =
|
||||
PropertyValuesHolder.ofKeyframe("position", keyFrames);
|
||||
|
||||
ValueAnimator positionAnimator = ValueAnimator.ofPropertyValuesHolder(positionValuesHolder);
|
||||
// The position can also be animated using separate animators for x and y, the purpose of
|
||||
// PairEvaluator is to use one animator for both x and y.
|
||||
positionAnimator.setEvaluator(new AnimatedOverlaySettings.PairEvaluator());
|
||||
positionAnimator.setRepeatMode(ValueAnimator.RESTART);
|
||||
positionAnimator.setRepeatCount(ValueAnimator.INFINITE);
|
||||
positionAnimator.setDuration(POSITION_PERIOD_MS);
|
||||
|
||||
overlaySettings = new AnimatedOverlaySettings(rotationAnimator, positionAnimator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getDrawable(long presentationTimeUs) {
|
||||
return logo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OverlaySettings getOverlaySettings(long presentationTimeUs) {
|
||||
overlaySettings.setCurrentPresentationTimeUs(presentationTimeUs);
|
||||
return overlaySettings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() throws VideoFrameProcessingException {
|
||||
super.release();
|
||||
overlaySettings.stopAnimation();
|
||||
}
|
||||
|
||||
private static class AnimatedOverlaySettings implements OverlaySettings {
|
||||
private final ValueAnimator rotationAnimator;
|
||||
private final ValueAnimator positionAnimator;
|
||||
private final Handler mainThreadHandler;
|
||||
|
||||
private boolean started;
|
||||
|
||||
public AnimatedOverlaySettings(ValueAnimator rotationAnimator, ValueAnimator positionAnimator) {
|
||||
this.rotationAnimator = rotationAnimator;
|
||||
this.positionAnimator = positionAnimator;
|
||||
mainThreadHandler = new Handler(Util.getCurrentOrMainLooper());
|
||||
}
|
||||
|
||||
public void setCurrentPresentationTimeUs(long presentationTimeUs) {
|
||||
// Sets the animation time to the video presentation time, so the animation is presentation
|
||||
// time based.
|
||||
rotationAnimator.setCurrentPlayTime(presentationTimeUs / 1000);
|
||||
positionAnimator.setCurrentPlayTime(presentationTimeUs / 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getRotationDegrees() {
|
||||
maybeStartAnimator();
|
||||
return (float) rotationAnimator.getAnimatedValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Float, Float> getBackgroundFrameAnchor() {
|
||||
maybeStartAnimator();
|
||||
return (Pair<Float, Float>) positionAnimator.getAnimatedValue();
|
||||
}
|
||||
|
||||
public void stopAnimation() {
|
||||
mainThreadHandler.post(
|
||||
() -> {
|
||||
rotationAnimator.cancel();
|
||||
positionAnimator.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
private void maybeStartAnimator() {
|
||||
if (!started) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
mainThreadHandler.post(
|
||||
() -> {
|
||||
rotationAnimator.start();
|
||||
positionAnimator.start();
|
||||
latch.countDown();
|
||||
});
|
||||
try {
|
||||
// Block until the animators are actually started, or they'll return null values.
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
started = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** An {@link TypeEvaluator} to animate position in the form of {@link Pair} of floats. */
|
||||
private static class PairEvaluator implements TypeEvaluator<Pair<Float, Float>> {
|
||||
private final FloatEvaluator floatEvaluator;
|
||||
|
||||
private PairEvaluator() {
|
||||
floatEvaluator = new FloatEvaluator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Float, Float> evaluate(
|
||||
float fraction, Pair<Float, Float> startValue, Pair<Float, Float> endValue) {
|
||||
return Pair.create(
|
||||
floatEvaluator.evaluate(fraction, startValue.first, endValue.first),
|
||||
floatEvaluator.evaluate(fraction, startValue.second, endValue.second));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -116,6 +116,7 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
||||
public static final int TEXT_OVERLAY_INDEX = 12;
|
||||
public static final int CLOCK_OVERLAY_INDEX = 13;
|
||||
public static final int CONFETTI_OVERLAY_INDEX = 14;
|
||||
public static final int ANIMATING_LOGO_OVERLAY = 15;
|
||||
|
||||
// Audio effect selections.
|
||||
public static final int HIGH_PITCHED_INDEX = 0;
|
||||
|
@ -654,6 +654,9 @@ public final class TransformerActivity extends AppCompatActivity {
|
||||
if (selectedEffects[ConfigurationActivity.CONFETTI_OVERLAY_INDEX]) {
|
||||
overlaysBuilder.add(new ConfettiOverlay());
|
||||
}
|
||||
if (selectedEffects[ConfigurationActivity.ANIMATING_LOGO_OVERLAY]) {
|
||||
overlaysBuilder.add(new AnimatedLogoOverlay(this.getApplicationContext()));
|
||||
}
|
||||
|
||||
ImmutableList<TextureOverlay> overlays = overlaysBuilder.build();
|
||||
return overlays.isEmpty() ? null : new OverlayEffect(overlays);
|
||||
|
@ -30,6 +30,7 @@
|
||||
<item>Custom Text Overlay</item>
|
||||
<item>Clock Overlay</item>
|
||||
<item>Confetti Overlay</item>
|
||||
<item>Animated logo</item>
|
||||
</string-array>
|
||||
<string-array name="audio_effects_names">
|
||||
<item>High pitched</item>
|
||||
|
Loading…
x
Reference in New Issue
Block a user