From c82fafaa928ffdac2f5c996d2f38d4867150b0d4 Mon Sep 17 00:00:00 2001 From: Gabriel Peal Date: Thu, 2 Feb 2017 17:11:16 -0800 Subject: [PATCH 1/3] Remove codeStyleSettings from gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d77f574d80..a388e2a653 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .gradle /local.properties /.idea/* +!.idea/codeStyleSettings.xml .DS_Store /build /captures From b803b247bdab516e5b6aa1ae64c0260c94dd2a59 Mon Sep 17 00:00:00 2001 From: Gabriel Peal Date: Thu, 2 Feb 2017 17:11:33 -0800 Subject: [PATCH 2/3] Add codeStyleSettings to the repo --- .idea/codeStyleSettings.xml | 244 ++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 .idea/codeStyleSettings.xml diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml new file mode 100644 index 0000000000..9183d03784 --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,244 @@ + + + + + + \ No newline at end of file From bf86ecaf40060b51583a04df1f790aa5a372fcb9 Mon Sep 17 00:00:00 2001 From: Gabriel Peal Date: Thu, 2 Feb 2017 17:16:07 -0800 Subject: [PATCH 3/3] Reformatted code --- .../com/airbnb/lottie/samples/LottieTest.java | 40 +- .../com/airbnb/lottie/samples/TestRobot.java | 42 +- .../lottie/samples/LottieApplication.java | 70 +- .../lottie/samples/AnimationFragment.java | 586 +++++++-------- .../lottie/samples/AppIntroActivity.java | 187 ++--- .../com/airbnb/lottie/samples/AssetUtils.java | 18 +- .../samples/ChooseAssetDialogFragment.java | 120 ++-- .../airbnb/lottie/samples/FontActivity.java | 40 +- .../lottie/samples/ILottieApplication.java | 9 +- .../airbnb/lottie/samples/ListFragment.java | 198 ++--- .../lottie/samples/LottieFontViewGroup.java | 406 +++++------ .../airbnb/lottie/samples/MainActivity.java | 18 +- .../lottie/samples/ViewAnimationFragment.java | 30 +- .../lotte/samples/LottieApplication.java | 14 +- lottie/src/main/java/com/airbnb/lottie/L.java | 2 +- .../airbnb/lottie/LottieAnimationView.java | 621 ++++++++-------- .../com/airbnb/lottie/LottieViewAnimator.java | 302 ++++---- .../animatable/AnimatableColorValue.java | 72 +- .../animatable/AnimatableFloatValue.java | 72 +- .../animatable/AnimatableIntegerValue.java | 66 +- .../animatable/AnimatablePathValue.java | 316 ++++---- .../animatable/AnimatablePointValue.java | 48 +- .../animatable/AnimatableScaleValue.java | 56 +- .../animatable/AnimatableShapeValue.java | 228 +++--- .../lottie/animatable/AnimatableValue.java | 5 +- .../animatable/BaseAnimatableValue.java | 300 ++++---- .../animation/ColorKeyframeAnimation.java | 60 +- .../lottie/animation/KeyframeAnimation.java | 184 ++--- .../animation/NumberKeyframeAnimation.java | 78 +- .../animation/PathKeyframeAnimation.java | 92 +-- .../animation/PointKeyframeAnimation.java | 66 +- .../animation/ScaleKeyframeAnimation.java | 66 +- .../animation/ShapeKeyframeAnimation.java | 72 +- .../animation/StaticKeyframeAnimation.java | 32 +- .../airbnb/lottie/layers/AnimatableLayer.java | 412 +++++------ .../lottie/layers/EllipseShapeLayer.java | 192 ++--- .../airbnb/lottie/layers/GroupLayerView.java | 108 +-- .../com/airbnb/lottie/layers/LayerView.java | 456 ++++++------ .../airbnb/lottie/layers/LottieDrawable.java | 428 +++++------ .../com/airbnb/lottie/layers/MaskLayer.java | 20 +- .../com/airbnb/lottie/layers/RectLayer.java | 592 +++++++-------- .../com/airbnb/lottie/layers/ShapeLayer.java | 662 ++++++++--------- .../airbnb/lottie/layers/ShapeLayerView.java | 104 +-- .../com/airbnb/lottie/model/CircleShape.java | 30 +- .../airbnb/lottie/model/CubicCurveData.java | 78 +- .../java/com/airbnb/lottie/model/Layer.java | 680 +++++++++--------- .../lottie/model/LottieComposition.java | 478 ++++++------ .../java/com/airbnb/lottie/model/Mask.java | 88 +-- .../airbnb/lottie/model/RectangleShape.java | 82 +-- .../com/airbnb/lottie/model/ShapeData.java | 104 +-- .../com/airbnb/lottie/model/ShapeFill.java | 84 +-- .../com/airbnb/lottie/model/ShapeGroup.java | 142 ++-- .../com/airbnb/lottie/model/ShapePath.java | 86 +-- .../com/airbnb/lottie/model/ShapeStroke.java | 152 ++-- .../airbnb/lottie/model/ShapeTransform.java | 178 ++--- .../airbnb/lottie/model/ShapeTrimPath.java | 50 +- .../com/airbnb/lottie/model/Transform.java | 12 +- .../com/airbnb/lottie/utils/JsonUtils.java | 84 +-- .../com/airbnb/lottie/utils/MiscUtils.java | 38 +- .../java/com/airbnb/lottie/utils/ScaleXY.java | 40 +- .../airbnb/lottie/utils/SegmentedPath.java | 80 +-- .../airbnb/lottie/KeyframeAnimationTest.java | 54 +- 62 files changed, 5024 insertions(+), 5006 deletions(-) diff --git a/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.java b/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.java index 8407cee223..87ee9cc1e5 100644 --- a/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.java +++ b/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.java @@ -11,25 +11,25 @@ */ public class LottieTest extends ActivityInstrumentationTestCase2 { - public LottieTest() { - super(MainActivity.class); - } + public LottieTest() { + super(MainActivity.class); + } - @Test - public void testAll() { - TestRobot.testAnimation(getActivity(), "9squares-AlBoardman.json"); - TestRobot.testAnimation(getActivity(), "EmptyState.json"); - TestRobot.testAnimation(getActivity(), "HamburgerArrow.json"); - TestRobot.testAnimation(getActivity(), "LottieLogo1.json"); - TestRobot.testAnimation(getActivity(), "LottieLogo2.json"); - TestRobot.testAnimation(getActivity(), "MotionCorpse-Jrcanest.json"); - TestRobot.testAnimation(getActivity(), "PinJump.json"); - TestRobot.testAnimation(getActivity(), "TwitterHeart.json"); - TestRobot.testAnimation(getActivity(), "Tests/Hosts.json"); - TestRobot.testAnimation(getActivity(), "Tests/LightBulb.json", new float[] {0f, 0.05f, 0.10f, 0.2f, 0.3f, 0.4f, 0.5f, 1f}); - TestRobot.testAnimation(getActivity(), "Tests/LoopPlayOnce.json"); - TestRobot.testAnimation(getActivity(), "Tests/Alarm.json"); - TestRobot.testAnimation(getActivity(), "Tests/CheckSwitch.json"); - TestRobot.testAnimation(getActivity(), "Tests/EllipseTrimPath.json"); - } + @Test + public void testAll() { + TestRobot.testAnimation(getActivity(), "9squares-AlBoardman.json"); + TestRobot.testAnimation(getActivity(), "EmptyState.json"); + TestRobot.testAnimation(getActivity(), "HamburgerArrow.json"); + TestRobot.testAnimation(getActivity(), "LottieLogo1.json"); + TestRobot.testAnimation(getActivity(), "LottieLogo2.json"); + TestRobot.testAnimation(getActivity(), "MotionCorpse-Jrcanest.json"); + TestRobot.testAnimation(getActivity(), "PinJump.json"); + TestRobot.testAnimation(getActivity(), "TwitterHeart.json"); + TestRobot.testAnimation(getActivity(), "Tests/Hosts.json"); + TestRobot.testAnimation(getActivity(), "Tests/LightBulb.json", new float[]{0f, 0.05f, 0.10f, 0.2f, 0.3f, 0.4f, 0.5f, 1f}); + TestRobot.testAnimation(getActivity(), "Tests/LoopPlayOnce.json"); + TestRobot.testAnimation(getActivity(), "Tests/Alarm.json"); + TestRobot.testAnimation(getActivity(), "Tests/CheckSwitch.json"); + TestRobot.testAnimation(getActivity(), "Tests/EllipseTrimPath.json"); + } } diff --git a/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/TestRobot.java b/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/TestRobot.java index 3d59ff13a8..d9967d27aa 100644 --- a/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/TestRobot.java +++ b/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/TestRobot.java @@ -8,29 +8,29 @@ import java.util.Locale; class TestRobot { - private static final float[] DEFAULT_ANIMATED_PROGRESS = {0f, 0.05f, 0.10f, 0.2f, 0.5f, 1f}; - private static final float[] DEFAULT_STATIC_PROGRESS = {0f}; + private static final float[] DEFAULT_ANIMATED_PROGRESS = {0f, 0.05f, 0.10f, 0.2f, 0.5f, 1f}; + private static final float[] DEFAULT_STATIC_PROGRESS = {0f}; - static void testAnimation(MainActivity activity, String fileName) { - testAnimation(activity, fileName, DEFAULT_ANIMATED_PROGRESS); - } + static void testAnimation(MainActivity activity, String fileName) { + testAnimation(activity, fileName, DEFAULT_ANIMATED_PROGRESS); + } - static void testAnimation(MainActivity activity, String fileName, float[] progress) { - LottieAnimationView view = new LottieAnimationView(activity); - view.setComposition(LottieComposition.fromFileSync(activity, fileName)); - ViewHelpers.setupView(view) - .layout(); + static void testAnimation(MainActivity activity, String fileName, float[] progress) { + LottieAnimationView view = new LottieAnimationView(activity); + view.setComposition(LottieComposition.fromFileSync(activity, fileName)); + ViewHelpers.setupView(view) + .layout(); - String nameWithoutExtension = fileName - .substring(0, fileName.indexOf('.')) - .replace("/", "_"); - for (float p : progress) { - view.setProgress(p); - Screenshot.snap(view) - .setGroup(fileName) - .setName(String.format(Locale.US, "%s %d", nameWithoutExtension, (int) (p * 100))) - .record(); - } - view.recycleBitmaps(); + String nameWithoutExtension = fileName + .substring(0, fileName.indexOf('.')) + .replace("/", "_"); + for (float p : progress) { + view.setProgress(p); + Screenshot.snap(view) + .setGroup(fileName) + .setName(String.format(Locale.US, "%s %d", nameWithoutExtension, (int) (p * 100))) + .record(); } + view.recycleBitmaps(); + } } diff --git a/LottieSample/src/debug/java/com/airbnb/lottie/samples/LottieApplication.java b/LottieSample/src/debug/java/com/airbnb/lottie/samples/LottieApplication.java index ae3fa416c3..3b743f36c0 100644 --- a/LottieSample/src/debug/java/com/airbnb/lottie/samples/LottieApplication.java +++ b/LottieSample/src/debug/java/com/airbnb/lottie/samples/LottieApplication.java @@ -10,41 +10,41 @@ public class LottieApplication extends Application implements ILottieApplication { - private int droppedFrames; - private long droppedFramesStartingNs; - private long currentFrameNs; - - @Override - public void onCreate() { - super.onCreate(); - if (L.DBG) { - TinyDancer.create() - .startingGravity(Gravity.TOP|Gravity.END) - .startingXPosition(0) - .startingYPosition(0) - .addFrameDataCallback(new FrameDataCallback() { - @Override - public void doFrame(long previousFrameNs, long currentFrameNs, int droppedFrames) { - LottieApplication.this.droppedFrames += droppedFrames; - LottieApplication.this.currentFrameNs = currentFrameNs; - } - }) - .show(this); - } - - } - - @Override - public void startRecordingDroppedFrames() { - droppedFrames = 0; - droppedFramesStartingNs = currentFrameNs; + private int droppedFrames; + private long droppedFramesStartingNs; + private long currentFrameNs; + + @Override + public void onCreate() { + super.onCreate(); + if (L.DBG) { + TinyDancer.create() + .startingGravity(Gravity.TOP | Gravity.END) + .startingXPosition(0) + .startingYPosition(0) + .addFrameDataCallback(new FrameDataCallback() { + @Override + public void doFrame(long previousFrameNs, long currentFrameNs, int droppedFrames) { + LottieApplication.this.droppedFrames += droppedFrames; + LottieApplication.this.currentFrameNs = currentFrameNs; + } + }) + .show(this); } - @Override - public Pair stopRecordingDroppedFrames() { - long duration = currentFrameNs - droppedFramesStartingNs; - Pair ret = new Pair<>(droppedFrames, duration); - droppedFrames = 0; - return ret; - } + } + + @Override + public void startRecordingDroppedFrames() { + droppedFrames = 0; + droppedFramesStartingNs = currentFrameNs; + } + + @Override + public Pair stopRecordingDroppedFrames() { + long duration = currentFrameNs - droppedFramesStartingNs; + Pair ret = new Pair<>(droppedFrames, duration); + droppedFrames = 0; + return ret; + } } diff --git a/LottieSample/src/main/java/com/airbnb/lottie/samples/AnimationFragment.java b/LottieSample/src/main/java/com/airbnb/lottie/samples/AnimationFragment.java index 2f5a7842a1..49209c7147 100644 --- a/LottieSample/src/main/java/com/airbnb/lottie/samples/AnimationFragment.java +++ b/LottieSample/src/main/java/com/airbnb/lottie/samples/AnimationFragment.java @@ -47,318 +47,320 @@ import okhttp3.Response; public class AnimationFragment extends Fragment { - private static final int RC_ASSET = 1337; - private static final int RC_FILE = 1338; - private static final int RC_URL = 1339; - static final String EXTRA_ANIMATION_NAME = "animation_name"; - - static AnimationFragment newInstance() { - return new AnimationFragment(); - } - - private OkHttpClient client; - - @BindView(R.id.toolbar) Toolbar toolbar; - @BindView(R.id.instructions) ViewGroup instructionsContainer; - @BindView(R.id.animation_container) ViewGroup animationContainer; - @BindView(R.id.animation_view) LottieAnimationView animationView; - @BindView(R.id.seek_bar) AppCompatSeekBar seekBar; - @BindView(R.id.invert_colors) ImageButton invertButton; - @BindView(R.id.play_button) ImageButton playButton; - @BindView(R.id.loop) ImageButton loopButton; - // @BindView(R.id.frames_per_second) TextView fpsView; - // @BindView(R.id.dropped_frames) TextView droppedFramesView; - @BindView(R.id.animation_name) TextView animationNameView; - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_animation, container, false); - ButterKnife.bind(this, view); - - ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); - toolbar.setNavigationIcon(R.drawable.ic_back); - toolbar.setNavigationOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - getFragmentManager().popBackStack(); - } - }); - - //noinspection ConstantConditions - // fpsView.setVisibility(L.DBG ? View.VISIBLE : View.GONE); - //noinspection ConstantConditions - // droppedFramesView.setVisibility(L.DBG ? View.VISIBLE : View.GONE); + private static final int RC_ASSET = 1337; + private static final int RC_FILE = 1338; + private static final int RC_URL = 1339; + static final String EXTRA_ANIMATION_NAME = "animation_name"; + + static AnimationFragment newInstance() { + return new AnimationFragment(); + } + + private OkHttpClient client; + + @BindView(R.id.toolbar) Toolbar toolbar; + @BindView(R.id.instructions) ViewGroup instructionsContainer; + @BindView(R.id.animation_container) ViewGroup animationContainer; + @BindView(R.id.animation_view) LottieAnimationView animationView; + @BindView(R.id.seek_bar) AppCompatSeekBar seekBar; + @BindView(R.id.invert_colors) ImageButton invertButton; + @BindView(R.id.play_button) ImageButton playButton; + @BindView(R.id.loop) ImageButton loopButton; + // @BindView(R.id.frames_per_second) TextView fpsView; + // @BindView(R.id.dropped_frames) TextView droppedFramesView; + @BindView(R.id.animation_name) TextView animationNameView; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_animation, container, false); + ButterKnife.bind(this, view); + + ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); + toolbar.setNavigationIcon(R.drawable.ic_back); + toolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + getFragmentManager().popBackStack(); + } + }); + + //noinspection ConstantConditions + // fpsView.setVisibility(L.DBG ? View.VISIBLE : View.GONE); + //noinspection ConstantConditions + // droppedFramesView.setVisibility(L.DBG ? View.VISIBLE : View.GONE); + postUpdatePlayButtonText(); + onLoopChanged(); + animationView.addAnimatorListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + startRecordingDroppedFrames(); + } + + @Override + public void onAnimationEnd(Animator animation) { + recordDroppedFrames(); postUpdatePlayButtonText(); - onLoopChanged(); - animationView.addAnimatorListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - startRecordingDroppedFrames(); - } - - @Override - public void onAnimationEnd(Animator animation) { - recordDroppedFrames(); - postUpdatePlayButtonText(); - } - - @Override - public void onAnimationCancel(Animator animation) { - postUpdatePlayButtonText(); - } - - @Override - public void onAnimationRepeat(Animator animation) { - recordDroppedFrames(); - startRecordingDroppedFrames(); - } - }); - animationView.addAnimatorUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - seekBar.setProgress((int) (animation.getAnimatedFraction() * 100)); - } - }); - - seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - animationView.setProgress(progress / 100f); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} - }); - - return view; - } + } - @Override - public void onStop() { - animationView.cancelAnimation(); - super.onStop(); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK) { - return; - } - - switch (requestCode) { - case RC_ASSET: - final String assetName = data.getStringExtra(EXTRA_ANIMATION_NAME); - LottieComposition.fromAssetFileName(getContext(), assetName, new LottieComposition.OnCompositionLoadedListener() { - @Override - public void onCompositionLoaded(LottieComposition composition) { - setComposition(composition, assetName); - } - }); - break; - case RC_FILE: - onFileLoaded(data.getData()); - break; - case RC_URL: - - break; - } - } - - private void setComposition(LottieComposition composition, String name) { - instructionsContainer.setVisibility(View.GONE); - seekBar.setProgress(0); - animationView.setComposition(composition); - animationNameView.setText(name); + @Override + public void onAnimationCancel(Animator animation) { + postUpdatePlayButtonText(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + recordDroppedFrames(); + startRecordingDroppedFrames(); + } + }); + animationView.addAnimatorUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + seekBar.setProgress((int) (animation.getAnimatedFraction() * 100)); + } + }); + + seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + animationView.setProgress(progress / 100f); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + + return view; + } + + @Override + public void onStop() { + animationView.cancelAnimation(); + super.onStop(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK) { + return; } - @OnClick(R.id.play_button) - void onPlayClicked() { - if (animationView.isAnimating()) { - animationView.pauseAnimation(); - postUpdatePlayButtonText(); - } else { - if (animationView.getProgress() == 1f) { - animationView.setProgress(0f); - } - animationView.playAnimation(); - postUpdatePlayButtonText(); - } - } + switch (requestCode) { + case RC_ASSET: + final String assetName = data.getStringExtra(EXTRA_ANIMATION_NAME); + LottieComposition.fromAssetFileName(getContext(), assetName, new LottieComposition.OnCompositionLoadedListener() { + @Override + public void onCompositionLoaded(LottieComposition composition) { + setComposition(composition, assetName); + } + }); + break; + case RC_FILE: + onFileLoaded(data.getData()); + break; + case RC_URL: - @OnClick(R.id.loop) - void onLoopChanged() { - loopButton.setActivated(!loopButton.isActivated()); - animationView.loop(loopButton.isEnabled()); + break; } - - @OnClick(R.id.restart) - void onRestartClicked() { - boolean restart = animationView.isAnimating(); - animationView.cancelAnimation(); + } + + private void setComposition(LottieComposition composition, String name) { + instructionsContainer.setVisibility(View.GONE); + seekBar.setProgress(0); + animationView.setComposition(composition); + animationNameView.setText(name); + } + + @OnClick(R.id.play_button) + void onPlayClicked() { + if (animationView.isAnimating()) { + animationView.pauseAnimation(); + postUpdatePlayButtonText(); + } else { + if (animationView.getProgress() == 1f) { animationView.setProgress(0f); - if (restart) { - animationView.playAnimation(); - } + } + animationView.playAnimation(); + postUpdatePlayButtonText(); } - - @OnClick(R.id.invert_colors) - void onInvertClicked() { - animationContainer.setActivated(!animationContainer.isActivated()); - invertButton.setActivated(animationContainer.isActivated()); + } + + @OnClick(R.id.loop) + void onLoopChanged() { + loopButton.setActivated(!loopButton.isActivated()); + animationView.loop(loopButton.isEnabled()); + } + + @OnClick(R.id.restart) + void onRestartClicked() { + boolean restart = animationView.isAnimating(); + animationView.cancelAnimation(); + animationView.setProgress(0f); + if (restart) { + animationView.playAnimation(); } - - @OnClick(R.id.load_asset) - void onLoadAssetClicked() { - animationView.cancelAnimation(); - android.support.v4.app.DialogFragment assetFragment = ChooseAssetDialogFragment.newInstance(); - assetFragment.setTargetFragment(this, RC_ASSET); - assetFragment.show(getFragmentManager(), "assets"); + } + + @OnClick(R.id.invert_colors) + void onInvertClicked() { + animationContainer.setActivated(!animationContainer.isActivated()); + invertButton.setActivated(animationContainer.isActivated()); + } + + @OnClick(R.id.load_asset) + void onLoadAssetClicked() { + animationView.cancelAnimation(); + android.support.v4.app.DialogFragment assetFragment = ChooseAssetDialogFragment.newInstance(); + assetFragment.setTargetFragment(this, RC_ASSET); + assetFragment.show(getFragmentManager(), "assets"); + } + + @OnClick(R.id.load_file) + void onLoadFileClicked() { + animationView.cancelAnimation(); + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + + try { + startActivityForResult(Intent.createChooser(intent, "Select a JSON file"), RC_FILE); + } catch (android.content.ActivityNotFoundException ex) { + // Potentially direct the user to the Market with a Dialog + Toast.makeText(getContext(), "Please install a File Manager.", Toast.LENGTH_SHORT).show(); } - - @OnClick(R.id.load_file) - void onLoadFileClicked() { - animationView.cancelAnimation(); - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - - try { - startActivityForResult(Intent.createChooser(intent, "Select a JSON file"), RC_FILE); - } catch (android.content.ActivityNotFoundException ex) { - // Potentially direct the user to the Market with a Dialog - Toast.makeText(getContext(), "Please install a File Manager.", Toast.LENGTH_SHORT).show(); - } + } + + @OnClick(R.id.load_url) + void onLoadUrlClicked() { + animationView.cancelAnimation(); + final EditText urlView = new EditText(getContext()); + new AlertDialog.Builder(getContext()) + .setTitle("Enter a URL") + .setView(urlView) + .setPositiveButton("Load", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + loadUrl(urlView.getText().toString()); + } + }) + .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .show(); + } + + private void postUpdatePlayButtonText() { + new Handler().post(new Runnable() { + @Override + public void run() { + updatePlayButtonText(); + } + }); + } + + private void updatePlayButtonText() { + playButton.setActivated(animationView.isAnimating()); + } + + private void onFileLoaded(final Uri uri) { + InputStream fis; + + try { + switch (uri.getScheme()) { + case "file": + fis = new FileInputStream(uri.getPath()); + break; + case "content": + fis = getContext().getContentResolver().openInputStream(uri); + break; + default: + onLoadError(); + return; + } + } catch (FileNotFoundException e) { + onLoadError(); + return; } - @OnClick(R.id.load_url) - void onLoadUrlClicked() { - animationView.cancelAnimation(); - final EditText urlView = new EditText(getContext()); - new AlertDialog.Builder(getContext()) - .setTitle("Enter a URL") - .setView(urlView) - .setPositiveButton("Load", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - loadUrl(urlView.getText().toString()); - } - }) - .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }) - .show(); + LottieComposition.fromInputStream(getContext(), fis, new LottieComposition.OnCompositionLoadedListener() { + @Override + public void onCompositionLoaded(LottieComposition composition) { + setComposition(composition, uri.getPath()); + } + }); + } + + private void loadUrl(String url) { + Request request; + try { + request = new Request.Builder() + .url(url) + .build(); + } catch (IllegalArgumentException e) { + onLoadError(); + return; } - private void postUpdatePlayButtonText() { - new Handler().post(new Runnable() { - @Override - public void run() { - updatePlayButtonText(); - } - }); - } - private void updatePlayButtonText() { - playButton.setActivated(animationView.isAnimating()); + if (client == null) { + client = new OkHttpClient(); } - - private void onFileLoaded(final Uri uri) { - InputStream fis; - - try { - switch (uri.getScheme()) { - case "file": - fis = new FileInputStream(uri.getPath()); - break; - case "content": - fis = getContext().getContentResolver().openInputStream(uri); - break; - default: - onLoadError(); - return; - } - } catch (FileNotFoundException e) { - onLoadError(); - return; + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + onLoadError(); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (!response.isSuccessful()) { + onLoadError(); } - LottieComposition.fromInputStream(getContext(), fis, new LottieComposition.OnCompositionLoadedListener() { + try { + JSONObject json = new JSONObject(response.body().string()); + LottieComposition.fromJson(getResources(), json, new LottieComposition.OnCompositionLoadedListener() { @Override public void onCompositionLoaded(LottieComposition composition) { - setComposition(composition, uri.getPath()); + setComposition(composition, "Network Animation"); } - }); - } - - private void loadUrl(String url) { - Request request; - try { - request = new Request.Builder() - .url(url) - .build(); - } catch (IllegalArgumentException e) { - onLoadError(); - return; - } - - - if (client == null) { - client = new OkHttpClient(); + }); + } catch (JSONException e) { + onLoadError(); } - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - onLoadError(); - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - if (!response.isSuccessful()) { - onLoadError(); - } - - try { - JSONObject json = new JSONObject(response.body().string()); - LottieComposition.fromJson(getResources(), json, new LottieComposition.OnCompositionLoadedListener() { - @Override - public void onCompositionLoaded(LottieComposition composition) { - setComposition(composition, "Network Animation"); - } - }); - } catch (JSONException e) { - onLoadError(); - } - } - }); - } - - private void onLoadError() { - //noinspection ConstantConditions - Snackbar.make(getView(), "Failed to load animation", Snackbar.LENGTH_LONG).show(); - } - - private void startRecordingDroppedFrames() { - getApplication().startRecordingDroppedFrames(); - } - - @SuppressWarnings("unused") - @SuppressLint({"SetTextI18n", "DefaultLocale"}) - private void recordDroppedFrames() { - Pair droppedFrames = getApplication().stopRecordingDroppedFrames(); - int targetFrames = (int) ((droppedFrames.second / 1000000000f) * 60); - int actualFrames = targetFrames - droppedFrames.first; - // fpsView.setText(String.format("Fps: %.0f", actualFrames / (animationView.getDuration() / 1000f))); - // droppedFramesView.setText("Dropped frames: " + droppedFrames.first); - } - - private ILottieApplication getApplication() { - return (ILottieApplication) getActivity().getApplication(); - } + } + }); + } + + private void onLoadError() { + //noinspection ConstantConditions + Snackbar.make(getView(), "Failed to load animation", Snackbar.LENGTH_LONG).show(); + } + + private void startRecordingDroppedFrames() { + getApplication().startRecordingDroppedFrames(); + } + + @SuppressWarnings("unused") + @SuppressLint({"SetTextI18n", "DefaultLocale"}) + private void recordDroppedFrames() { + Pair droppedFrames = getApplication().stopRecordingDroppedFrames(); + int targetFrames = (int) ((droppedFrames.second / 1000000000f) * 60); + int actualFrames = targetFrames - droppedFrames.first; + // fpsView.setText(String.format("Fps: %.0f", actualFrames / (animationView.getDuration() / 1000f))); + // droppedFramesView.setText("Dropped frames: " + droppedFrames.first); + } + + private ILottieApplication getApplication() { + return (ILottieApplication) getActivity().getApplication(); + } } diff --git a/LottieSample/src/main/java/com/airbnb/lottie/samples/AppIntroActivity.java b/LottieSample/src/main/java/com/airbnb/lottie/samples/AppIntroActivity.java index ec1c56361b..505f629bbf 100644 --- a/LottieSample/src/main/java/com/airbnb/lottie/samples/AppIntroActivity.java +++ b/LottieSample/src/main/java/com/airbnb/lottie/samples/AppIntroActivity.java @@ -20,109 +20,114 @@ import java.util.Collection; public class AppIntroActivity extends IntroActivity { - private static final float[] ANIMATION_TIMES = new float[] { - 0f, - 0.3333f, - 0.6666f, - 1f, - 1f - + private static final float[] ANIMATION_TIMES = new float[]{ + 0f, + 0.3333f, + 0.6666f, + 1f, + 1f + + }; + private LottieAnimationView animationView; + private LockableViewPager viewPager; + + @Override + protected Collection generatePages(Bundle savedInstanceState) { + return new ArrayList() {{ + add(EmptyFragment.newInstance()); + add(EmptyFragment.newInstance()); + add(EmptyFragment.newInstance()); + add(EmptyFragment.newInstance()); + }}; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + animationView = (LottieAnimationView) LayoutInflater.from(this).inflate(R.layout.app_intro_animation_view, getRootView(), false); + viewPager = (LockableViewPager) findViewById(com.matthewtamlin.sliding_intro_screen_library.R.id.intro_activity_viewPager); + getRootView().addView(animationView, 0); + setViewPagerScroller(); + + addPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + setAnimationProgress(position, positionOffset); + } + + @Override + public void onPageSelected(int position) { + } + + @Override + public void onPageScrollStateChanged(int state) { + } + }); + } + + @Override + protected IntroButton.Behaviour generateFinalButtonBehaviour() { + return new IntroButton.Behaviour() { + @Override + public void setActivity(IntroActivity activity) { + finish(); + } + + @Override + public IntroActivity getActivity() { + return null; + } + + @Override + public void run() { + } }; - private LottieAnimationView animationView; - private LockableViewPager viewPager; + } - @Override - protected Collection generatePages(Bundle savedInstanceState) { - return new ArrayList() {{ - add(EmptyFragment.newInstance()); - add(EmptyFragment.newInstance()); - add(EmptyFragment.newInstance()); - add(EmptyFragment.newInstance()); - }}; - } + private void setAnimationProgress(int position, float positionOffset) { + float startProgress = ANIMATION_TIMES[position]; + float endProgress = ANIMATION_TIMES[position + 1]; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - animationView = (LottieAnimationView) LayoutInflater.from(this).inflate(R.layout.app_intro_animation_view, getRootView(), false); - viewPager = (LockableViewPager) findViewById(com.matthewtamlin.sliding_intro_screen_library.R.id.intro_activity_viewPager); - getRootView().addView(animationView, 0); - setViewPagerScroller(); - - addPageChangeListener(new ViewPager.OnPageChangeListener() { - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - setAnimationProgress(position, positionOffset); - } - - @Override - public void onPageSelected(int position) { } - - @Override - public void onPageScrollStateChanged(int state) { } - }); - } + animationView.setProgress(lerp(startProgress, endProgress, positionOffset)); + } - @Override - protected IntroButton.Behaviour generateFinalButtonBehaviour() { - return new IntroButton.Behaviour() { - @Override - public void setActivity(IntroActivity activity) { finish();} - - @Override - public IntroActivity getActivity() { - return null; - } - - @Override - public void run() { } - }; - } + private float lerp(float startValue, float endValue, float f) { + return startValue + f * (endValue - startValue); + } - private void setAnimationProgress(int position, float positionOffset) { - float startProgress = ANIMATION_TIMES[position]; - float endProgress = ANIMATION_TIMES[position + 1]; + public static final class EmptyFragment extends Fragment { - animationView.setProgress(lerp(startProgress, endProgress, positionOffset)); + private static EmptyFragment newInstance() { + return new EmptyFragment(); } - private float lerp(float startValue, float endValue, float f) { - return startValue + f * (endValue - startValue); + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_empty, container, false); } + } - public static final class EmptyFragment extends Fragment { - - private static EmptyFragment newInstance() { - return new EmptyFragment(); - } + private void setViewPagerScroller() { + //noinspection TryWithIdenticalCatches + try { + Field scrollerField = ViewPager.class.getDeclaredField("mScroller"); + scrollerField.setAccessible(true); + Field interpolator = ViewPager.class.getDeclaredField("sInterpolator"); + interpolator.setAccessible(true); - @Nullable + Scroller scroller = new Scroller(this, (Interpolator) interpolator.get(null)) { @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_empty, container, false); - } - } - - private void setViewPagerScroller() { - //noinspection TryWithIdenticalCatches - try { - Field scrollerField = ViewPager.class.getDeclaredField("mScroller"); - scrollerField.setAccessible(true); - Field interpolator = ViewPager.class.getDeclaredField("sInterpolator"); - interpolator.setAccessible(true); - - Scroller scroller = new Scroller(this, (Interpolator) interpolator.get(null)) { - @Override - public void startScroll(int startX, int startY, int dx, int dy, int duration) { - super.startScroll(startX, startY, dx, dy, duration * 7); - } - }; - scrollerField.set(viewPager, scroller); - } catch (NoSuchFieldException e) { - // Do nothing. - } catch (IllegalAccessException e) { - // Do nothing. + public void startScroll(int startX, int startY, int dx, int dy, int duration) { + super.startScroll(startX, startY, dx, dy, duration * 7); } + }; + scrollerField.set(viewPager, scroller); + } catch (NoSuchFieldException e) { + // Do nothing. + } catch (IllegalAccessException e) { + // Do nothing. } + } } diff --git a/LottieSample/src/main/java/com/airbnb/lottie/samples/AssetUtils.java b/LottieSample/src/main/java/com/airbnb/lottie/samples/AssetUtils.java index 3778788cbf..0bc712ce65 100644 --- a/LottieSample/src/main/java/com/airbnb/lottie/samples/AssetUtils.java +++ b/LottieSample/src/main/java/com/airbnb/lottie/samples/AssetUtils.java @@ -8,14 +8,14 @@ class AssetUtils { - static List getJsonAssets(Context context, String path) throws IOException { - String[] assetList = context.getAssets().list(path); - List files = new ArrayList<>(); - for (String asset : assetList) { - if (asset.toLowerCase().endsWith(".json")) { - files.add(asset); - } - } - return files; + static List getJsonAssets(Context context, String path) throws IOException { + String[] assetList = context.getAssets().list(path); + List files = new ArrayList<>(); + for (String asset : assetList) { + if (asset.toLowerCase().endsWith(".json")) { + files.add(asset); + } } + return files; + } } diff --git a/LottieSample/src/main/java/com/airbnb/lottie/samples/ChooseAssetDialogFragment.java b/LottieSample/src/main/java/com/airbnb/lottie/samples/ChooseAssetDialogFragment.java index 0dfb68628e..f3e5592f00 100644 --- a/LottieSample/src/main/java/com/airbnb/lottie/samples/ChooseAssetDialogFragment.java +++ b/LottieSample/src/main/java/com/airbnb/lottie/samples/ChooseAssetDialogFragment.java @@ -20,79 +20,79 @@ public class ChooseAssetDialogFragment extends DialogFragment { - static ChooseAssetDialogFragment newInstance() { - return new ChooseAssetDialogFragment(); + static ChooseAssetDialogFragment newInstance() { + return new ChooseAssetDialogFragment(); + } + + @BindView(R.id.recycler_view) RecyclerView recyclerView; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_choose_asset, container, false); + ButterKnife.bind(this, view); + getDialog().setTitle("Choose an Asset"); + + return view; + } + + @Override + public void onStart() { + super.onStart(); + getDialog().getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + recyclerView.setAdapter(new AssetsAdapter()); + } + + final class AssetsAdapter extends RecyclerView.Adapter { + + private List files = Collections.emptyList(); + + AssetsAdapter() { + try { + files = AssetUtils.getJsonAssets(getContext(), ""); + } catch (IOException e) { + //noinspection ConstantConditions + Snackbar.make(getView(), R.string.invalid_assets, Snackbar.LENGTH_LONG).show(); + } } - @BindView(R.id.recycler_view) RecyclerView recyclerView; - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_choose_asset, container, false); - ButterKnife.bind(this, view); - getDialog().setTitle("Choose an Asset"); - - return view; + public StringViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new StringViewHolder(parent); } @Override - public void onStart() { - super.onStart(); - getDialog().getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - recyclerView.setAdapter(new AssetsAdapter()); + public void onBindViewHolder(StringViewHolder holder, int position) { + String fileName = files.get(position); + holder.bind(fileName); } - final class AssetsAdapter extends RecyclerView.Adapter { - - private List files = Collections.emptyList(); - - AssetsAdapter() { - try { - files = AssetUtils.getJsonAssets(getContext(), ""); - } catch (IOException e) { - //noinspection ConstantConditions - Snackbar.make(getView(), R.string.invalid_assets, Snackbar.LENGTH_LONG).show(); - } - } - - @Override - public StringViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return new StringViewHolder(parent); - } - - @Override - public void onBindViewHolder(StringViewHolder holder, int position) { - String fileName = files.get(position); - holder.bind(fileName); - } - - @Override - public int getItemCount() { - return files.size(); - } + @Override + public int getItemCount() { + return files.size(); } + } - final class StringViewHolder extends RecyclerView.ViewHolder { + final class StringViewHolder extends RecyclerView.ViewHolder { - @BindView(R.id.title) TextView titleView; + @BindView(R.id.title) TextView titleView; - StringViewHolder(ViewGroup parent) { - super(LayoutInflater.from(parent.getContext()).inflate(R.layout.view_holder_file, parent, false)); - ButterKnife.bind(this, itemView); - } + StringViewHolder(ViewGroup parent) { + super(LayoutInflater.from(parent.getContext()).inflate(R.layout.view_holder_file, parent, false)); + ButterKnife.bind(this, itemView); + } - void bind(final String fileName) { - titleView.setText(fileName); - itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - getTargetFragment().onActivityResult( - getTargetRequestCode(), - Activity.RESULT_OK, - new Intent().putExtra(AnimationFragment.EXTRA_ANIMATION_NAME, fileName)); - dismiss(); - } - }); + void bind(final String fileName) { + titleView.setText(fileName); + itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + getTargetFragment().onActivityResult( + getTargetRequestCode(), + Activity.RESULT_OK, + new Intent().putExtra(AnimationFragment.EXTRA_ANIMATION_NAME, fileName)); + dismiss(); } + }); } + } } diff --git a/LottieSample/src/main/java/com/airbnb/lottie/samples/FontActivity.java b/LottieSample/src/main/java/com/airbnb/lottie/samples/FontActivity.java index a776b64f2d..540ce75b3b 100644 --- a/LottieSample/src/main/java/com/airbnb/lottie/samples/FontActivity.java +++ b/LottieSample/src/main/java/com/airbnb/lottie/samples/FontActivity.java @@ -12,29 +12,29 @@ public class FontActivity extends AppCompatActivity { - @BindView(R.id.scroll_view) ScrollView scrollView; - @BindView(R.id.font_view) LottieFontViewGroup fontView; - - private final ViewTreeObserver.OnGlobalLayoutListener layoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - scrollView.fullScroll(View.FOCUS_DOWN); - } - }; + @BindView(R.id.scroll_view) ScrollView scrollView; + @BindView(R.id.font_view) LottieFontViewGroup fontView; + private final ViewTreeObserver.OnGlobalLayoutListener layoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_font); - ButterKnife.bind(this); - - fontView.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener); + public void onGlobalLayout() { + scrollView.fullScroll(View.FOCUS_DOWN); } + }; + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_font); + ButterKnife.bind(this); - @Override - protected void onDestroy() { - fontView.getViewTreeObserver().removeOnGlobalLayoutListener(layoutListener); - super.onDestroy(); - } + fontView.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener); + } + + + @Override + protected void onDestroy() { + fontView.getViewTreeObserver().removeOnGlobalLayoutListener(layoutListener); + super.onDestroy(); + } } diff --git a/LottieSample/src/main/java/com/airbnb/lottie/samples/ILottieApplication.java b/LottieSample/src/main/java/com/airbnb/lottie/samples/ILottieApplication.java index 636ba2fb44..c3e9e24a53 100644 --- a/LottieSample/src/main/java/com/airbnb/lottie/samples/ILottieApplication.java +++ b/LottieSample/src/main/java/com/airbnb/lottie/samples/ILottieApplication.java @@ -3,7 +3,10 @@ import android.support.v4.util.Pair; interface ILottieApplication { - void startRecordingDroppedFrames(); - /** Returns the number of frames dropped since starting **/ - Pair stopRecordingDroppedFrames(); + void startRecordingDroppedFrames(); + + /** + * Returns the number of frames dropped since starting + **/ + Pair stopRecordingDroppedFrames(); } diff --git a/LottieSample/src/main/java/com/airbnb/lottie/samples/ListFragment.java b/LottieSample/src/main/java/com/airbnb/lottie/samples/ListFragment.java index f77e068efc..a7e592c7e7 100644 --- a/LottieSample/src/main/java/com/airbnb/lottie/samples/ListFragment.java +++ b/LottieSample/src/main/java/com/airbnb/lottie/samples/ListFragment.java @@ -17,121 +17,121 @@ public class ListFragment extends Fragment { - static ListFragment newInstance() { - return new ListFragment(); - } - - @BindView(R.id.container) ViewGroup container; - @BindView(R.id.recycler_view) RecyclerView recyclerView; - @BindView(R.id.animation_view) LottieAnimationView animationView; - - private final FileAdapter adapter = new FileAdapter(); + static ListFragment newInstance() { + return new ListFragment(); + } + + @BindView(R.id.container) ViewGroup container; + @BindView(R.id.recycler_view) RecyclerView recyclerView; + @BindView(R.id.animation_view) LottieAnimationView animationView; + + private final FileAdapter adapter = new FileAdapter(); + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_list, container, false); + ButterKnife.bind(this, view); + + recyclerView.setAdapter(adapter); + + return view; + } + + @Override + public void onStart() { + super.onStart(); + animationView.setProgress(0f); + animationView.playAnimation(); + } + + @Override + public void onStop() { + super.onStop(); + animationView.cancelAnimation(); + } + + private void onViewerClicked() { + showFragment(AnimationFragment.newInstance()); + } + + private void onTypographyClicked() { + startActivity(new Intent(getContext(), FontActivity.class)); + } + + private void onAppIntroPagerClicked() { + startActivity(new Intent(getContext(), AppIntroActivity.class)); + } + + private void showFragment(Fragment fragment) { + getFragmentManager().beginTransaction() + .addToBackStack(null) + .setCustomAnimations(R.anim.slide_in_right, R.anim.hold, R.anim.hold, R.anim.slide_out_right) + .remove(this) + .replace(R.id.content_2, fragment) + .commit(); + } + + private void onFontClicked() { + startActivity(new Intent(getContext(), FontActivity.class)); + } + + final class FileAdapter extends RecyclerView.Adapter { + private static final String TAG_VIEWER = "viewer"; + private static final String TAG_TYPOGRAPHY = "typography"; + private static final String TAG_APP_INTRO = "app_intro"; - @Nullable @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_list, container, false); - ButterKnife.bind(this, view); - - recyclerView.setAdapter(adapter); - - return view; + public StringViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new StringViewHolder(parent); } @Override - public void onStart() { - super.onStart(); - animationView.setProgress(0f); - animationView.playAnimation(); + public void onBindViewHolder(StringViewHolder holder, int position) { + switch (position) { + case 0: + holder.bind("Animation Viewer", TAG_VIEWER); + break; + case 1: + holder.bind("Animated Typography", TAG_TYPOGRAPHY); + break; + case 2: + holder.bind("Animated App Tutorial", TAG_APP_INTRO); + break; + } } @Override - public void onStop() { - super.onStop(); - animationView.cancelAnimation(); - } - - private void onViewerClicked() { - showFragment(AnimationFragment.newInstance()); - } - - private void onTypographyClicked() { - startActivity(new Intent(getContext(), FontActivity.class)); + public int getItemCount() { + return 3; } + } - private void onAppIntroPagerClicked() { - startActivity(new Intent(getContext(), AppIntroActivity.class)); - } + final class StringViewHolder extends RecyclerView.ViewHolder { - private void showFragment(Fragment fragment) { - getFragmentManager().beginTransaction() - .addToBackStack(null) - .setCustomAnimations(R.anim.slide_in_right, R.anim.hold, R.anim.hold, R.anim.slide_out_right) - .remove(this) - .replace(R.id.content_2, fragment) - .commit(); - } + @BindView(R.id.title) TextView titleView; - private void onFontClicked() { - startActivity(new Intent(getContext(), FontActivity.class)); + StringViewHolder(ViewGroup parent) { + super(LayoutInflater.from(parent.getContext()).inflate(R.layout.view_holder_file, parent, false)); + ButterKnife.bind(this, itemView); } - final class FileAdapter extends RecyclerView.Adapter { - private static final String TAG_VIEWER = "viewer"; - private static final String TAG_TYPOGRAPHY = "typography"; - private static final String TAG_APP_INTRO = "app_intro"; - - @Override - public StringViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return new StringViewHolder(parent); - } - - @Override - public void onBindViewHolder(StringViewHolder holder, int position) { - switch (position) { - case 0: - holder.bind("Animation Viewer", TAG_VIEWER); - break; - case 1: - holder.bind("Animated Typography", TAG_TYPOGRAPHY); - break; - case 2: - holder.bind("Animated App Tutorial", TAG_APP_INTRO); - break; - } - } + void bind(String title, String tag) { + titleView.setText(title); + itemView.setTag(tag); + itemView.setOnClickListener(new View.OnClickListener() { @Override - public int getItemCount() { - return 3; - } - } - - final class StringViewHolder extends RecyclerView.ViewHolder { - - @BindView(R.id.title) TextView titleView; - - StringViewHolder(ViewGroup parent) { - super(LayoutInflater.from(parent.getContext()).inflate(R.layout.view_holder_file, parent, false)); - ButterKnife.bind(this, itemView); - } - - void bind(String title, String tag) { - titleView.setText(title); - itemView.setTag(tag); - - itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (FileAdapter.TAG_VIEWER.equals(v.getTag())) { - onViewerClicked(); - } else if (FileAdapter.TAG_TYPOGRAPHY.equals(v.getTag())) { - onTypographyClicked(); - } else if (FileAdapter.TAG_APP_INTRO.equals(v.getTag())) { - onAppIntroPagerClicked(); - } - } - }); + public void onClick(View v) { + if (FileAdapter.TAG_VIEWER.equals(v.getTag())) { + onViewerClicked(); + } else if (FileAdapter.TAG_TYPOGRAPHY.equals(v.getTag())) { + onTypographyClicked(); + } else if (FileAdapter.TAG_APP_INTRO.equals(v.getTag())) { + onAppIntroPagerClicked(); + } } + }); } + } } diff --git a/LottieSample/src/main/java/com/airbnb/lottie/samples/LottieFontViewGroup.java b/LottieSample/src/main/java/com/airbnb/lottie/samples/LottieFontViewGroup.java index 7c5da874d5..efa1e6dbcb 100644 --- a/LottieSample/src/main/java/com/airbnb/lottie/samples/LottieFontViewGroup.java +++ b/LottieSample/src/main/java/com/airbnb/lottie/samples/LottieFontViewGroup.java @@ -22,215 +22,215 @@ public class LottieFontViewGroup extends FrameLayout { - private final Map compositionMap = new HashMap<>(); - private final List views = new ArrayList<>(); - - @Nullable private LottieAnimationView cursorView; - - public LottieFontViewGroup(Context context) { - super(context); - init(); - } - - public LottieFontViewGroup(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public LottieFontViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); - } - - private void init() { - setFocusableInTouchMode(true); - LottieComposition.fromAssetFileName(getContext(), "Mobilo/BlinkingCursor.json", new LottieComposition.OnCompositionLoadedListener() { - @Override - public void onCompositionLoaded(LottieComposition composition) { - cursorView = new LottieAnimationView(getContext()); - cursorView.setLayoutParams(new LottieFontViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - cursorView.setComposition(composition); - cursorView.loop(true); - cursorView.playAnimation(); - addView(cursorView); - } - }); - } - - private void addSpace() { - int index = indexOfChild(cursorView); - addView(createSpaceView(), index); - } - - @Override - public void addView(View child, int index) { - super.addView(child, index); - if (index == -1) { - views.add(child); - } else { - views.add(index, child); - } - } - - private void removeLastView() { - if (views.size() > 1) { - int position = views.size() - 2; - removeView(views.get(position)); - views.remove(position); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (views.isEmpty()) { - return; - } - int currentX = getPaddingTop(); - int currentY = getPaddingLeft(); - - for (int i = 0; i < views.size(); i++) { - View view = views.get(i); - if (!fitsOnCurrentLine(currentX, view)) { - if (view.getTag() != null && view.getTag().equals("Space")) { - continue; - } - currentX = getPaddingLeft(); - currentY += view.getMeasuredHeight(); - } - currentX += view.getWidth(); - } - - setMeasuredDimension(getMeasuredWidth(), currentY + views.get(views.size() - 1).getMeasuredHeight() * 2); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (views.isEmpty()) { - return; - } - int currentX = getPaddingTop(); - int currentY = getPaddingLeft(); - - for (int i = 0; i < views.size(); i++) { - View view = views.get(i); - if (!fitsOnCurrentLine(currentX, view)) { - if (view.getTag() != null && view.getTag().equals("Space")) { - continue; - } - currentX = getPaddingLeft(); - currentY += view.getMeasuredHeight(); - } - view.layout(currentX, currentY, currentX + view.getMeasuredWidth(), currentY + view.getMeasuredHeight()); - currentX += view.getWidth(); + private final Map compositionMap = new HashMap<>(); + private final List views = new ArrayList<>(); + + @Nullable private LottieAnimationView cursorView; + + public LottieFontViewGroup(Context context) { + super(context); + init(); + } + + public LottieFontViewGroup(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public LottieFontViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + setFocusableInTouchMode(true); + LottieComposition.fromAssetFileName(getContext(), "Mobilo/BlinkingCursor.json", new LottieComposition.OnCompositionLoadedListener() { + @Override + public void onCompositionLoaded(LottieComposition composition) { + cursorView = new LottieAnimationView(getContext()); + cursorView.setLayoutParams(new LottieFontViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + cursorView.setComposition(composition); + cursorView.loop(true); + cursorView.playAnimation(); + addView(cursorView); + } + }); + } + + private void addSpace() { + int index = indexOfChild(cursorView); + addView(createSpaceView(), index); + } + + @Override + public void addView(View child, int index) { + super.addView(child, index); + if (index == -1) { + views.add(child); + } else { + views.add(index, child); + } + } + + private void removeLastView() { + if (views.size() > 1) { + int position = views.size() - 2; + removeView(views.get(position)); + views.remove(position); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + if (views.isEmpty()) { + return; + } + int currentX = getPaddingTop(); + int currentY = getPaddingLeft(); + + for (int i = 0; i < views.size(); i++) { + View view = views.get(i); + if (!fitsOnCurrentLine(currentX, view)) { + if (view.getTag() != null && view.getTag().equals("Space")) { + continue; } + currentX = getPaddingLeft(); + currentY += view.getMeasuredHeight(); + } + currentX += view.getWidth(); } - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - BaseInputConnection fic = new BaseInputConnection(this, false); - outAttrs.actionLabel = null; - outAttrs.inputType = InputType.TYPE_NULL; - outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT; - return fic; - } - - @Override - public boolean onCheckIsTextEditor() { - return true; - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_SPACE) { - addSpace(); - return true; - } - - if (keyCode == KeyEvent.KEYCODE_DEL) { - removeLastView(); - return true; - } - - if (!isValidKey(keyCode)) { - return super.onKeyUp(keyCode, event); - } - + setMeasuredDimension(getMeasuredWidth(), currentY + views.get(views.size() - 1).getMeasuredHeight() * 2); + } - String letter = "" + Character.toUpperCase((char) event.getUnicodeChar()); - // switch (letter) { - // case ",": - // letter = "Comma"; - // break; - // case "'": - // letter = "Apostrophe"; - // break; - // case ";": - // case ":": - // letter = "Colon"; - // break; - // } - final String fileName = "Mobilo/" + letter + ".json"; - if (compositionMap.containsKey(fileName)) { - addComposition(compositionMap.get(fileName)); - } else { - LottieComposition.fromAssetFileName(getContext(), fileName, new LottieComposition.OnCompositionLoadedListener() { - @Override - public void onCompositionLoaded(LottieComposition composition) { - compositionMap.put(fileName, composition); - addComposition(composition); - } - }); - } - - return true; + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (views.isEmpty()) { + return; } + int currentX = getPaddingTop(); + int currentY = getPaddingLeft(); - private boolean isValidKey(int keyCode) { - if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) { - return true; + for (int i = 0; i < views.size(); i++) { + View view = views.get(i); + if (!fitsOnCurrentLine(currentX, view)) { + if (view.getTag() != null && view.getTag().equals("Space")) { + continue; } - - // switch (keyCode) { - // case KeyEvent.KEYCODE_COMMA: - // case KeyEvent.KEYCODE_APOSTROPHE: - // case KeyEvent.KEYCODE_SEMICOLON: - // return true; - // } - return false; - } - - private void addComposition(LottieComposition composition) { - LottieAnimationView lottieAnimationView = new LottieAnimationView(getContext()); - lottieAnimationView.setLayoutParams(new LottieFontViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - lottieAnimationView.setComposition(composition); - lottieAnimationView.playAnimation(); - if (cursorView == null) { - addView(lottieAnimationView); - } else { - int index = indexOfChild(cursorView); - addView(lottieAnimationView, index); + currentX = getPaddingLeft(); + currentY += view.getMeasuredHeight(); + } + view.layout(currentX, currentY, currentX + view.getMeasuredWidth(), currentY + view.getMeasuredHeight()); + currentX += view.getWidth(); + } + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + BaseInputConnection fic = new BaseInputConnection(this, false); + outAttrs.actionLabel = null; + outAttrs.inputType = InputType.TYPE_NULL; + outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT; + return fic; + } + + @Override + public boolean onCheckIsTextEditor() { + return true; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_SPACE) { + addSpace(); + return true; + } + + if (keyCode == KeyEvent.KEYCODE_DEL) { + removeLastView(); + return true; + } + + if (!isValidKey(keyCode)) { + return super.onKeyUp(keyCode, event); + } + + + String letter = "" + Character.toUpperCase((char) event.getUnicodeChar()); + // switch (letter) { + // case ",": + // letter = "Comma"; + // break; + // case "'": + // letter = "Apostrophe"; + // break; + // case ";": + // case ":": + // letter = "Colon"; + // break; + // } + final String fileName = "Mobilo/" + letter + ".json"; + if (compositionMap.containsKey(fileName)) { + addComposition(compositionMap.get(fileName)); + } else { + LottieComposition.fromAssetFileName(getContext(), fileName, new LottieComposition.OnCompositionLoadedListener() { + @Override + public void onCompositionLoaded(LottieComposition composition) { + compositionMap.put(fileName, composition); + addComposition(composition); } - } - - private boolean fitsOnCurrentLine(int currentX, View view) { - return currentX + view.getMeasuredWidth() < getWidth() - getPaddingRight(); - } - - private View createSpaceView() { - View spaceView = new View(getContext()); - spaceView.setLayoutParams(new LayoutParams( - getResources().getDimensionPixelSize(R.dimen.font_space_width), - ViewGroup.LayoutParams.WRAP_CONTENT - )); - spaceView.setTag("Space"); - return spaceView; - } + }); + } + + return true; + } + + private boolean isValidKey(int keyCode) { + if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) { + return true; + } + + // switch (keyCode) { + // case KeyEvent.KEYCODE_COMMA: + // case KeyEvent.KEYCODE_APOSTROPHE: + // case KeyEvent.KEYCODE_SEMICOLON: + // return true; + // } + return false; + } + + private void addComposition(LottieComposition composition) { + LottieAnimationView lottieAnimationView = new LottieAnimationView(getContext()); + lottieAnimationView.setLayoutParams(new LottieFontViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + lottieAnimationView.setComposition(composition); + lottieAnimationView.playAnimation(); + if (cursorView == null) { + addView(lottieAnimationView); + } else { + int index = indexOfChild(cursorView); + addView(lottieAnimationView, index); + } + } + + private boolean fitsOnCurrentLine(int currentX, View view) { + return currentX + view.getMeasuredWidth() < getWidth() - getPaddingRight(); + } + + private View createSpaceView() { + View spaceView = new View(getContext()); + spaceView.setLayoutParams(new LayoutParams( + getResources().getDimensionPixelSize(R.dimen.font_space_width), + ViewGroup.LayoutParams.WRAP_CONTENT + )); + spaceView.setTag("Space"); + return spaceView; + } } diff --git a/LottieSample/src/main/java/com/airbnb/lottie/samples/MainActivity.java b/LottieSample/src/main/java/com/airbnb/lottie/samples/MainActivity.java index 38c470e771..31722dd1e5 100644 --- a/LottieSample/src/main/java/com/airbnb/lottie/samples/MainActivity.java +++ b/LottieSample/src/main/java/com/airbnb/lottie/samples/MainActivity.java @@ -5,15 +5,15 @@ public class MainActivity extends AppCompatActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); - if (savedInstanceState == null) { - getSupportFragmentManager().beginTransaction() - .replace(R.id.content_1, ListFragment.newInstance()) - .commit(); - } + if (savedInstanceState == null) { + getSupportFragmentManager().beginTransaction() + .replace(R.id.content_1, ListFragment.newInstance()) + .commit(); } + } } diff --git a/LottieSample/src/main/java/com/airbnb/lottie/samples/ViewAnimationFragment.java b/LottieSample/src/main/java/com/airbnb/lottie/samples/ViewAnimationFragment.java index 6c7cf01e38..089af1fd98 100644 --- a/LottieSample/src/main/java/com/airbnb/lottie/samples/ViewAnimationFragment.java +++ b/LottieSample/src/main/java/com/airbnb/lottie/samples/ViewAnimationFragment.java @@ -14,23 +14,23 @@ public class ViewAnimationFragment extends Fragment { - static Fragment newInstance() { - return new ViewAnimationFragment(); - } + static Fragment newInstance() { + return new ViewAnimationFragment(); + } - @BindView(R.id.message_bubble) View messageBubble; + @BindView(R.id.message_bubble) View messageBubble; - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_view_animation, container, false); - ButterKnife.bind(this, view); + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_view_animation, container, false); + ButterKnife.bind(this, view); - messageBubble.setTag(R.id.lottie_layer_name, "Tip"); - LottieViewAnimator.of(getContext(), "Tip.json", messageBubble) - .loop(true) - .start(); + messageBubble.setTag(R.id.lottie_layer_name, "Tip"); + LottieViewAnimator.of(getContext(), "Tip.json", messageBubble) + .loop(true) + .start(); - return view; - } + return view; + } } diff --git a/LottieSample/src/release/java/com/airbnb/lotte/samples/LottieApplication.java b/LottieSample/src/release/java/com/airbnb/lotte/samples/LottieApplication.java index f78dd1cd9a..7f30be601f 100644 --- a/LottieSample/src/release/java/com/airbnb/lotte/samples/LottieApplication.java +++ b/LottieSample/src/release/java/com/airbnb/lotte/samples/LottieApplication.java @@ -4,12 +4,12 @@ import android.support.v4.util.Pair; public class LottieApplication extends Application implements ILottieApplication { - @Override - public void startRecordingDroppedFrames() { - } + @Override + public void startRecordingDroppedFrames() { + } - @Override - public Pair stopRecordingDroppedFrames() { - return new Pair<>(0, 0L); - } + @Override + public Pair stopRecordingDroppedFrames() { + return new Pair<>(0, 0L); + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/L.java b/lottie/src/main/java/com/airbnb/lottie/L.java index 90fcc2266b..b490e24375 100644 --- a/lottie/src/main/java/com/airbnb/lottie/L.java +++ b/lottie/src/main/java/com/airbnb/lottie/L.java @@ -4,5 +4,5 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class L { - public static final boolean DBG = false; + public static final boolean DBG = false; } diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java index 717d4b58c5..02269c707c 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java @@ -30,354 +30,357 @@ /** * This view will load, deserialize, and display an After Effects animation exported with * bodymovin (https://github.com/bodymovin/bodymovin). - * + *

* You may set the animation in one of two ways: * 1) Attrs: {@link R.styleable#LottieAnimationView_lottie_fileName} * 2) Programatically: {@link #setAnimation(String)}, {@link #setComposition(LottieComposition)}, or {@link #setAnimation(JSONObject)}. - * + *

* You may manually set the progress of the animation with {@link #setProgress(float)} */ public class LottieAnimationView extends AppCompatImageView { - private static final String TAG = LottieAnimationView.class.getSimpleName(); - - /** - * Caching strategy for compositions that will be reused frequently. - * Weak or Strong indicates the GC reference strength of the composition in the cache. - */ - public enum CacheStrategy { - None, - Weak, - Strong + private static final String TAG = LottieAnimationView.class.getSimpleName(); + + /** + * Caching strategy for compositions that will be reused frequently. + * Weak or Strong indicates the GC reference strength of the composition in the cache. + */ + public enum CacheStrategy { + None, + Weak, + Strong + } + + @Nullable private static Map strongRefCache; + @Nullable private static Map> weakRefCache; + + private final LottieComposition.OnCompositionLoadedListener loadedListener = new LottieComposition.OnCompositionLoadedListener() { + @Override + public void onCompositionLoaded(LottieComposition composition) { + setComposition(composition); + compositionLoader = null; } - - @Nullable private static Map strongRefCache; - @Nullable private static Map> weakRefCache; - - private final LottieComposition.OnCompositionLoadedListener loadedListener = new LottieComposition.OnCompositionLoadedListener() { - @Override - public void onCompositionLoaded(LottieComposition composition) { - setComposition(composition); - compositionLoader = null; - } - }; - - private final LottieDrawable lottieDrawable = new LottieDrawable(); - @FloatRange(from=0f, to=1f) private float progress; - private String animationName; - private boolean isAnimationLoading; - private boolean setProgressWhenCompositionSet; - private boolean playAnimationWhenCompositionSet; - - @Nullable private LottieComposition.Cancellable compositionLoader; - /** Can be null because it is created async */ - @Nullable private LottieComposition composition; - - public LottieAnimationView(Context context) { - super(context); - init(null); + }; + + private final LottieDrawable lottieDrawable = new LottieDrawable(); + @FloatRange(from = 0f, to = 1f) private float progress; + private String animationName; + private boolean isAnimationLoading; + private boolean setProgressWhenCompositionSet; + private boolean playAnimationWhenCompositionSet; + + @Nullable private LottieComposition.Cancellable compositionLoader; + /** + * Can be null because it is created async + */ + @Nullable private LottieComposition composition; + + public LottieAnimationView(Context context) { + super(context); + init(null); + } + + public LottieAnimationView(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs); + } + + public LottieAnimationView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs); + } + + private void init(@Nullable AttributeSet attrs) { + TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.LottieAnimationView); + String fileName = ta.getString(R.styleable.LottieAnimationView_lottie_fileName); + if (!isInEditMode() && fileName != null) { + setAnimation(fileName); } - - public LottieAnimationView(Context context, AttributeSet attrs) { - super(context, attrs); - init(attrs); + if (ta.getBoolean(R.styleable.LottieAnimationView_lottie_autoPlay, false)) { + lottieDrawable.playAnimation(); } - - public LottieAnimationView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(attrs); + lottieDrawable.loop(ta.getBoolean(R.styleable.LottieAnimationView_lottie_loop, false)); + ta.recycle(); + setLayerType(LAYER_TYPE_SOFTWARE, null); + } + + @Override + public void invalidateDrawable(Drawable dr) { + // We always want to invalidate the root drawable to it redraws the whole drawable. + // Eventually it would be great to be able to invalidate just the changed region. + super.invalidateDrawable(lottieDrawable); + } + + @Override + protected Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + ss.animationName = animationName; + ss.progress = lottieDrawable.getProgress(); + ss.isAnimating = lottieDrawable.isAnimating(); + ss.isLooping = lottieDrawable.isLooping(); + return ss; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; } - private void init(@Nullable AttributeSet attrs) { - TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.LottieAnimationView); - String fileName = ta.getString(R.styleable.LottieAnimationView_lottie_fileName); - if (!isInEditMode() && fileName != null) { - setAnimation(fileName); - } - if (ta.getBoolean(R.styleable.LottieAnimationView_lottie_autoPlay, false)) { - lottieDrawable.playAnimation(); - } - lottieDrawable.loop(ta.getBoolean(R.styleable.LottieAnimationView_lottie_loop, false)); - ta.recycle(); - setLayerType(LAYER_TYPE_SOFTWARE, null); + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + this.animationName = ss.animationName; + if (!TextUtils.isEmpty(animationName)) { + setAnimation(animationName); } - - @Override - public void invalidateDrawable(Drawable dr) { - // We always want to invalidate the root drawable to it redraws the whole drawable. - // Eventually it would be great to be able to invalidate just the changed region. - super.invalidateDrawable(lottieDrawable); + setProgress(ss.progress); + loop(ss.isLooping); + if (ss.isAnimating) { + playAnimation(); } - - @Override - protected Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState ss = new SavedState(superState); - ss.animationName = animationName; - ss.progress = lottieDrawable.getProgress(); - ss.isAnimating = lottieDrawable.isAnimating(); - ss.isLooping = lottieDrawable.isLooping(); - return ss; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @SuppressLint("MissingSuperCall") + @Override + protected boolean verifyDrawable(@NonNull Drawable drawable) { + return true; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + } + + @Override + protected void onDetachedFromWindow() { + recycleBitmaps(); + + super.onDetachedFromWindow(); + } + + @VisibleForTesting + public void recycleBitmaps() { + lottieDrawable.recycleBitmaps(); + } + + /** + * Sets the animation from a file in the assets directory. + * This will load and deserialize the file asynchronously. + *

+ * Will not cache the composition once loaded. + */ + public void setAnimation(String animationName) { + setAnimation(animationName, CacheStrategy.None); + } + + /** + * Sets the animation from a file in the assets directory. + * This will load and deserialize the file asynchronously. + *

+ * You may also specify a cache strategy. Specifying {@link CacheStrategy#Strong} will hold a strong reference to the composition once it is loaded + * and deserialized. {@link CacheStrategy#Weak} will hold a weak reference to said composition. + */ + @SuppressWarnings("WeakerAccess") + public void setAnimation(final String animationName, final CacheStrategy cacheStrategy) { + this.animationName = animationName; + if (weakRefCache != null && weakRefCache.containsKey(animationName)) { + WeakReference compRef = weakRefCache.get(animationName); + if (compRef.get() != null) { + setComposition(compRef.get()); + return; + } + } else if (strongRefCache != null && strongRefCache.containsKey(animationName)) { + setComposition(strongRefCache.get(animationName)); + return; } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - if(!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - - SavedState ss = (SavedState) state; - super.onRestoreInstanceState(ss.getSuperState()); - this.animationName = ss.animationName; - if (!TextUtils.isEmpty(animationName)) { - setAnimation(animationName); + isAnimationLoading = true; + setProgressWhenCompositionSet = false; + playAnimationWhenCompositionSet = false; + + this.animationName = animationName; + cancelLoaderTask(); + compositionLoader = LottieComposition.fromAssetFileName(getContext(), animationName, new LottieComposition.OnCompositionLoadedListener() { + @Override + public void onCompositionLoaded(LottieComposition composition) { + if (cacheStrategy == CacheStrategy.Strong) { + if (strongRefCache == null) { + strongRefCache = new HashMap<>(); + } + strongRefCache.put(animationName, composition); + } else if (cacheStrategy == CacheStrategy.Weak) { + if (weakRefCache == null) { + weakRefCache = new HashMap<>(); + } + weakRefCache.put(animationName, new WeakReference<>(composition)); } - setProgress(ss.progress); - loop(ss.isLooping); - if (ss.isAnimating) { - playAnimation(); - } - } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); + setComposition(composition); + } + }); + } + + /** + * Sets the animation from a JSONObject. + * This will load and deserialize the file asynchronously. + *

+ * This is particularly useful for animations loaded from the network. You can fetch the bodymovin json from the network and pass it directly here. + */ + public void setAnimation(final JSONObject json) { + isAnimationLoading = true; + setProgressWhenCompositionSet = false; + playAnimationWhenCompositionSet = false; + + cancelLoaderTask(); + compositionLoader = LottieComposition.fromJson(getResources(), json, loadedListener); + } + + private void cancelLoaderTask() { + if (compositionLoader != null) { + compositionLoader.cancel(); + compositionLoader = null; } + } - @SuppressLint("MissingSuperCall") - @Override - protected boolean verifyDrawable(@NonNull Drawable drawable) { - return true; + public void setComposition(@NonNull LottieComposition composition) { + if (L.DBG) { + Log.v(TAG, "Set Composition \n" + composition); } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); + lottieDrawable.setCallback(this); + lottieDrawable.setComposition(composition); + // If you set a different composition on the view, the bounds will not update unless + // the drawable is different than the original. + setImageDrawable(null); + setImageDrawable(lottieDrawable); + + isAnimationLoading = false; + + if (setProgressWhenCompositionSet) { + setProgressWhenCompositionSet = false; + setProgress(progress); + } else { + setProgress(0f); } - @Override - protected void onDetachedFromWindow() { - recycleBitmaps(); + this.composition = composition; - super.onDetachedFromWindow(); + if (playAnimationWhenCompositionSet) { + playAnimationWhenCompositionSet = false; + playAnimation(); } - @VisibleForTesting - public void recycleBitmaps() { - lottieDrawable.recycleBitmaps(); - } + requestLayout(); + } - /** - * Sets the animation from a file in the assets directory. - * This will load and deserialize the file asynchronously. - * - * Will not cache the composition once loaded. - */ - public void setAnimation(String animationName) { - setAnimation(animationName, CacheStrategy.None); - } - /** - * Sets the animation from a file in the assets directory. - * This will load and deserialize the file asynchronously. - * - * You may also specify a cache strategy. Specifying {@link CacheStrategy#Strong} will hold a strong reference to the composition once it is loaded - * and deserialized. {@link CacheStrategy#Weak} will hold a weak reference to said composition. - */ - @SuppressWarnings("WeakerAccess") - public void setAnimation(final String animationName, final CacheStrategy cacheStrategy) { - this.animationName = animationName; - if (weakRefCache != null && weakRefCache.containsKey(animationName)) { - WeakReference compRef = weakRefCache.get(animationName); - if (compRef.get() != null) { - setComposition(compRef.get()); - return; - } - } else if (strongRefCache != null && strongRefCache.containsKey(animationName)) { - setComposition(strongRefCache.get(animationName)); - return; - } - isAnimationLoading = true; - setProgressWhenCompositionSet = false; - playAnimationWhenCompositionSet = false; - - this.animationName = animationName; - cancelLoaderTask(); - compositionLoader = LottieComposition.fromAssetFileName(getContext(), animationName, new LottieComposition.OnCompositionLoadedListener() { - @Override - public void onCompositionLoaded(LottieComposition composition) { - if (cacheStrategy == CacheStrategy.Strong) { - if (strongRefCache == null) { - strongRefCache = new HashMap<>(); - } - strongRefCache.put(animationName, composition); - } else if (cacheStrategy == CacheStrategy.Weak) { - if (weakRefCache == null) { - weakRefCache = new HashMap<>(); - } - weakRefCache.put(animationName, new WeakReference<>(composition)); - } - - setComposition(composition); - } - }); - } + public void addAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) { + lottieDrawable.addAnimatorUpdateListener(updateListener); + } - /** - * Sets the animation from a JSONObject. - * This will load and deserialize the file asynchronously. - * - * This is particularly useful for animations loaded from the network. You can fetch the bodymovin json from the network and pass it directly here. - */ - public void setAnimation(final JSONObject json) { - isAnimationLoading = true; - setProgressWhenCompositionSet = false; - playAnimationWhenCompositionSet = false; - - cancelLoaderTask(); - compositionLoader = LottieComposition.fromJson(getResources(), json, loadedListener); - } + @SuppressWarnings("unused") + public void removeUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) { + lottieDrawable.removeAnimatorUpdateListener(updateListener); + } - private void cancelLoaderTask() { - if (compositionLoader != null) { - compositionLoader.cancel(); - compositionLoader = null; - } - } + public void addAnimatorListener(Animator.AnimatorListener listener) { + lottieDrawable.addAnimatorListener(listener); + } - public void setComposition(@NonNull LottieComposition composition) { - if (L.DBG) { - Log.v(TAG, "Set Composition \n" + composition); - } - lottieDrawable.setCallback(this); - lottieDrawable.setComposition(composition); - // If you set a different composition on the view, the bounds will not update unless - // the drawable is different than the original. - setImageDrawable(null); - setImageDrawable(lottieDrawable); - - isAnimationLoading = false; - - if (setProgressWhenCompositionSet) { - setProgressWhenCompositionSet = false; - setProgress(progress); - } else { - setProgress(0f); - } + @SuppressWarnings("unused") + public void removeAnimatorListener(Animator.AnimatorListener listener) { + lottieDrawable.removeAnimatorListener(listener); + } - this.composition = composition; + public void loop(boolean loop) { + lottieDrawable.loop(loop); + } - if (playAnimationWhenCompositionSet) { - playAnimationWhenCompositionSet = false; - playAnimation(); - } - - requestLayout(); - } - - - public void addAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) { - lottieDrawable.addAnimatorUpdateListener(updateListener); - } - - @SuppressWarnings("unused") - public void removeUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) { - lottieDrawable.removeAnimatorUpdateListener(updateListener); - } + public boolean isAnimating() { + return lottieDrawable.isAnimating(); + } - public void addAnimatorListener(Animator.AnimatorListener listener) { - lottieDrawable.addAnimatorListener(listener); + public void playAnimation() { + if (isAnimationLoading) { + playAnimationWhenCompositionSet = true; + return; } - - @SuppressWarnings("unused") - public void removeAnimatorListener(Animator.AnimatorListener listener) { - lottieDrawable.removeAnimatorListener(listener); - } - - public void loop(boolean loop) { - lottieDrawable.loop(loop); - } - - public boolean isAnimating() { - return lottieDrawable.isAnimating(); + lottieDrawable.playAnimation(); + } + + public void cancelAnimation() { + setProgressWhenCompositionSet = false; + playAnimationWhenCompositionSet = false; + lottieDrawable.cancelAnimation(); + } + + public void pauseAnimation() { + setProgressWhenCompositionSet = false; + playAnimationWhenCompositionSet = false; + float progress = getProgress(); + lottieDrawable.cancelAnimation(); + setProgress(progress); + } + + public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { + this.progress = progress; + if (isAnimationLoading) { + setProgressWhenCompositionSet = true; + return; } - - public void playAnimation() { - if (isAnimationLoading) { - playAnimationWhenCompositionSet = true; - return; - } - lottieDrawable.playAnimation(); + lottieDrawable.setProgress(progress); + } + + @FloatRange(from = 0.0f, to = 1.0f) + public float getProgress() { + return progress; + } + + public long getDuration() { + return composition != null ? composition.getDuration() : 0; + } + + private static class SavedState extends BaseSavedState { + String animationName; + float progress; + boolean isAnimating; + boolean isLooping; + + SavedState(Parcelable superState) { + super(superState); } - public void cancelAnimation() { - setProgressWhenCompositionSet = false; - playAnimationWhenCompositionSet = false; - lottieDrawable.cancelAnimation(); + private SavedState(Parcel in) { + super(in); + animationName = in.readString(); + progress = in.readFloat(); + isAnimating = in.readInt() == 1; + isLooping = in.readInt() == 1; } - public void pauseAnimation() { - setProgressWhenCompositionSet = false; - playAnimationWhenCompositionSet = false; - float progress = getProgress(); - lottieDrawable.cancelAnimation(); - setProgress(progress); - } - - public void setProgress(@FloatRange(from=0f, to=1f) float progress) { - this.progress = progress; - if (isAnimationLoading) { - setProgressWhenCompositionSet = true; - return; - } - lottieDrawable.setProgress(progress); - } - - @FloatRange(from=0.0f, to=1.0f) - public float getProgress() { - return progress; - } + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeString(animationName); + out.writeFloat(progress); + out.writeInt(isAnimating ? 1 : 0); + out.writeInt(isLooping ? 1 : 0); - public long getDuration() { - return composition != null ? composition.getDuration() : 0; } - private static class SavedState extends BaseSavedState { - String animationName; - float progress; - boolean isAnimating; - boolean isLooping; - - SavedState(Parcelable superState) { - super(superState); - } - - private SavedState(Parcel in) { - super(in); - animationName = in.readString(); - progress = in.readFloat(); - isAnimating = in.readInt() == 1; - isLooping = in.readInt() == 1; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeString(animationName); - out.writeFloat(progress); - out.writeInt(isAnimating ? 1 : 0); - out.writeInt(isLooping ? 1 : 0); - - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieViewAnimator.java b/lottie/src/main/java/com/airbnb/lottie/LottieViewAnimator.java index 698937204d..fddc104051 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieViewAnimator.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieViewAnimator.java @@ -20,180 +20,180 @@ /** * Animated a view based on a null layer. To use this, set a tag on your view with the key {@link R.id#lottie_layer_name} * and the value as the null layer name from After Effects. - * + *

* This supports position, scale, rotation, and anchor point (pivot) - * + *

* Positions will all be relative to the initial point. This is for the ease of use of the animator. * Without subtracting the initial position, animators would have to work with the animation in the * top left corner of the composition. - * + *

* Anchor points affect the pivot point of the animation and should be set between 0 and 1. * Those values will be multiplied by the laid out width and height of the view. * For example, setting the anchor to (1, 1) would set the pivot to the bottom right. */ public class LottieViewAnimator { - public static LottieViewAnimator of(Context context, String fileName, View... views) { - return new LottieViewAnimator(context, fileName, views); - } - - @SuppressWarnings("FieldCanBeLocal") - private final LottieComposition.OnCompositionLoadedListener loadedListener = new LottieComposition.OnCompositionLoadedListener() { - @Override - public void onCompositionLoaded(LottieComposition composition) { - setComposition(composition); - } - }; - - private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); - private final Map viewsMap; - - private final List> animatableValues = new ArrayList<>(); - - private LottieComposition composition; - private boolean startWhenReady = false; + public static LottieViewAnimator of(Context context, String fileName, View... views) { + return new LottieViewAnimator(context, fileName, views); + } - private LottieViewAnimator(Context context, String fileName, View... views) { - viewsMap = new HashMap<>(views.length); + @SuppressWarnings("FieldCanBeLocal") + private final LottieComposition.OnCompositionLoadedListener loadedListener = new LottieComposition.OnCompositionLoadedListener() { + @Override + public void onCompositionLoaded(LottieComposition composition) { + setComposition(composition); + } + }; - for (View view : views) { - Object tag = view.getTag(R.id.lottie_layer_name); - if (tag != null) { - viewsMap.put((String) tag, view); - } - } + private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); + private final Map viewsMap; - animator.setInterpolator(new LinearInterpolator()); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - for (KeyframeAnimation av : animatableValues) { - av.setProgress(animation.getAnimatedFraction()); - } - } - }); + private final List> animatableValues = new ArrayList<>(); - LottieComposition.fromAssetFileName(context, fileName, loadedListener); - } + private LottieComposition composition; + private boolean startWhenReady = false; - private void setComposition(LottieComposition composition) { - this.composition = composition; - animator.setDuration(composition.getDuration()); - - for (final Layer layer : composition.getLayers()) { - final View view = viewsMap.get(layer.getName()); - if (view == null) { - continue; - } - - if (layer.getPosition().hasAnimation()) { - KeyframeAnimation position = layer.getPosition().createAnimation(); - position.addUpdateListener(new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(PointF progress) { - PointF initialPoint = layer.getPosition().getInitialPoint(); - view.setTranslationX(progress.x - initialPoint.x); - view.setTranslationY(progress.y - initialPoint.y); - } - }); - animatableValues.add(position); - } - - if (layer.getScale().hasAnimation()) { - KeyframeAnimation scale = layer.getScale().createAnimation(); - scale.addUpdateListener(new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(ScaleXY scale) { - view.setScaleX(scale.getScaleX()); - view.setScaleY(scale.getScaleY()); - } - }); - animatableValues.add(scale); - } - ScaleXY initialScale = layer.getScale().getInitialValue(); - view.setScaleX(initialScale.getScaleX()); - view.setScaleY(initialScale.getScaleY()); - - if (layer.getRotation().hasAnimation()) { - KeyframeAnimation rotation = layer.getRotation().createAnimation(); - rotation.addUpdateListener(new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Float rotation) { - view.setRotation(rotation); - } - }); - animatableValues.add(rotation); - } - view.setRotation(layer.getRotation().getInitialValue()); - - if (layer.getOpacity().hasAnimation()) { - KeyframeAnimation opacity = layer.getOpacity().createAnimation(); - opacity.addUpdateListener(new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Integer progress) { - view.setAlpha(progress / 255f); - } - }); - animatableValues.add(opacity); - } - view.setAlpha(layer.getOpacity().getInitialValue() / 255f); - - if (layer.getAnchor().hasAnimation()) { - KeyframeAnimation anchor = layer.getAnchor().createAnimation(); - anchor.addUpdateListener(new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(PointF anchor) { - setViewAnchor(view, anchor); - } - }); - } - if (view.getWidth() > 0) { - setViewAnchor(view, layer.getAnchor().getInitialPoint()); - - } else { - view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - view.getViewTreeObserver().removeOnGlobalLayoutListener(this); - setViewAnchor(view, layer.getAnchor().getInitialPoint()); - } - }); - } - } + private LottieViewAnimator(Context context, String fileName, View... views) { + viewsMap = new HashMap<>(views.length); - if (startWhenReady) { - startWhenReady = false; - start(); - } + for (View view : views) { + Object tag = view.getTag(R.id.lottie_layer_name); + if (tag != null) { + viewsMap.put((String) tag, view); + } } - public LottieViewAnimator start() { - if (animatableValues.isEmpty()) { - startWhenReady = true; - return this; + animator.setInterpolator(new LinearInterpolator()); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + for (KeyframeAnimation av : animatableValues) { + av.setProgress(animation.getAnimatedFraction()); } - - animator.start(); - return this; - } - - public LottieViewAnimator cancel() { - animator.cancel(); - return this; + } + }); + + LottieComposition.fromAssetFileName(context, fileName, loadedListener); + } + + private void setComposition(LottieComposition composition) { + this.composition = composition; + animator.setDuration(composition.getDuration()); + + for (final Layer layer : composition.getLayers()) { + final View view = viewsMap.get(layer.getName()); + if (view == null) { + continue; + } + + if (layer.getPosition().hasAnimation()) { + KeyframeAnimation position = layer.getPosition().createAnimation(); + position.addUpdateListener(new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(PointF progress) { + PointF initialPoint = layer.getPosition().getInitialPoint(); + view.setTranslationX(progress.x - initialPoint.x); + view.setTranslationY(progress.y - initialPoint.y); + } + }); + animatableValues.add(position); + } + + if (layer.getScale().hasAnimation()) { + KeyframeAnimation scale = layer.getScale().createAnimation(); + scale.addUpdateListener(new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(ScaleXY scale) { + view.setScaleX(scale.getScaleX()); + view.setScaleY(scale.getScaleY()); + } + }); + animatableValues.add(scale); + } + ScaleXY initialScale = layer.getScale().getInitialValue(); + view.setScaleX(initialScale.getScaleX()); + view.setScaleY(initialScale.getScaleY()); + + if (layer.getRotation().hasAnimation()) { + KeyframeAnimation rotation = layer.getRotation().createAnimation(); + rotation.addUpdateListener(new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Float rotation) { + view.setRotation(rotation); + } + }); + animatableValues.add(rotation); + } + view.setRotation(layer.getRotation().getInitialValue()); + + if (layer.getOpacity().hasAnimation()) { + KeyframeAnimation opacity = layer.getOpacity().createAnimation(); + opacity.addUpdateListener(new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Integer progress) { + view.setAlpha(progress / 255f); + } + }); + animatableValues.add(opacity); + } + view.setAlpha(layer.getOpacity().getInitialValue() / 255f); + + if (layer.getAnchor().hasAnimation()) { + KeyframeAnimation anchor = layer.getAnchor().createAnimation(); + anchor.addUpdateListener(new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(PointF anchor) { + setViewAnchor(view, anchor); + } + }); + } + if (view.getWidth() > 0) { + setViewAnchor(view, layer.getAnchor().getInitialPoint()); + + } else { + view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + view.getViewTreeObserver().removeOnGlobalLayoutListener(this); + setViewAnchor(view, layer.getAnchor().getInitialPoint()); + } + }); + } } - public LottieViewAnimator loop(boolean loop) { - animator.setRepeatCount(loop ? ValueAnimator.INFINITE : 0); - return this; + if (startWhenReady) { + startWhenReady = false; + start(); } + } - public LottieViewAnimator setProgress(float progress) { - animator.setCurrentPlayTime((long) (progress * animator.getDuration())); - return this; + public LottieViewAnimator start() { + if (animatableValues.isEmpty()) { + startWhenReady = true; + return this; } - private void setViewAnchor(View view, PointF anchor) { - view.setPivotX(anchor.x * view.getWidth() / (100f * composition.getScale())); - view.setPivotY(anchor.y * view.getHeight() / (100f * composition.getScale())); - } + animator.start(); + return this; + } + + public LottieViewAnimator cancel() { + animator.cancel(); + return this; + } + + public LottieViewAnimator loop(boolean loop) { + animator.setRepeatCount(loop ? ValueAnimator.INFINITE : 0); + return this; + } + + public LottieViewAnimator setProgress(float progress) { + animator.setCurrentPlayTime((long) (progress * animator.getDuration())); + return this; + } + + private void setViewAnchor(View view, PointF anchor) { + view.setPivotX(anchor.x * view.getWidth() / (100f * composition.getScale())); + view.setPivotY(anchor.y * view.getHeight() / (100f * composition.getScale())); + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableColorValue.java b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableColorValue.java index 9dd9e783f3..cbc0619194 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableColorValue.java +++ b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableColorValue.java @@ -15,45 +15,45 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class AnimatableColorValue extends BaseAnimatableValue { - public AnimatableColorValue(JSONObject json, int frameRate, LottieComposition composition) { - super(json, frameRate, composition, false); - } - - @Override - protected Integer valueFromObject(Object object, float scale) throws JSONException { - JSONArray colorArray = (JSONArray) object; - if (colorArray.length() == 4) { - boolean shouldUse255 = true; - for (int i = 0; i < colorArray.length(); i++) { - double colorChannel = colorArray.getDouble(i); - if (colorChannel > 1f) { - shouldUse255 = false; - } - } - - float multiplier = shouldUse255 ? 255f : 1f; - return Color.argb( - (int) (colorArray.getDouble(3) * multiplier), - (int) (colorArray.getDouble(0) * multiplier), - (int) (colorArray.getDouble(1) * multiplier), - (int) (colorArray.getDouble(2) * multiplier)); + public AnimatableColorValue(JSONObject json, int frameRate, LottieComposition composition) { + super(json, frameRate, composition, false); + } + + @Override + protected Integer valueFromObject(Object object, float scale) throws JSONException { + JSONArray colorArray = (JSONArray) object; + if (colorArray.length() == 4) { + boolean shouldUse255 = true; + for (int i = 0; i < colorArray.length(); i++) { + double colorChannel = colorArray.getDouble(i); + if (colorChannel > 1f) { + shouldUse255 = false; } - return Color.BLACK; + } + + float multiplier = shouldUse255 ? 255f : 1f; + return Color.argb( + (int) (colorArray.getDouble(3) * multiplier), + (int) (colorArray.getDouble(0) * multiplier), + (int) (colorArray.getDouble(1) * multiplier), + (int) (colorArray.getDouble(2) * multiplier)); } + return Color.BLACK; + } - @Override - public KeyframeAnimation createAnimation() { - if (!hasAnimation()) { - return new StaticKeyframeAnimation<>(initialValue); - } - ColorKeyframeAnimation animation = new ColorKeyframeAnimation(duration, composition, keyTimes, keyValues, interpolators); - animation.setStartDelay(delay); - return animation; - } - - @Override - public String toString() { - return "AnimatableColorValue{" + "initialValue=" + initialValue + '}'; + @Override + public KeyframeAnimation createAnimation() { + if (!hasAnimation()) { + return new StaticKeyframeAnimation<>(initialValue); } + ColorKeyframeAnimation animation = new ColorKeyframeAnimation(duration, composition, keyTimes, keyValues, interpolators); + animation.setStartDelay(delay); + return animation; + } + + @Override + public String toString() { + return "AnimatableColorValue{" + "initialValue=" + initialValue + '}'; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableFloatValue.java b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableFloatValue.java index 8da8c437ac..02ce36f9f1 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableFloatValue.java +++ b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableFloatValue.java @@ -14,46 +14,46 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class AnimatableFloatValue extends BaseAnimatableValue { - public AnimatableFloatValue(LottieComposition composition, Float initialValue) { - super(composition); - this.initialValue = initialValue; + public AnimatableFloatValue(LottieComposition composition, Float initialValue) { + super(composition); + this.initialValue = initialValue; + } + + public AnimatableFloatValue(JSONObject json, int frameRate, LottieComposition composition) { + this(json, frameRate, composition, true); + } + + public AnimatableFloatValue(JSONObject json, int frameRate, LottieComposition composition, boolean isDp) { + super(json, frameRate, composition, isDp); + } + + @Override + protected Float valueFromObject(Object object, float scale) throws JSONException { + if (object instanceof JSONArray) { + object = ((JSONArray) object).get(0); } - - public AnimatableFloatValue(JSONObject json, int frameRate, LottieComposition composition) { - this(json, frameRate, composition, true); - } - - public AnimatableFloatValue(JSONObject json, int frameRate, LottieComposition composition, boolean isDp) { - super(json, frameRate, composition, isDp); + if (object instanceof Float) { + return (Float) object * scale; + } else if (object instanceof Double) { + return (float) ((Double) object * scale); + } else if (object instanceof Integer) { + return (Integer) object * scale; } + return null; + } - @Override - protected Float valueFromObject(Object object, float scale) throws JSONException { - if (object instanceof JSONArray) { - object = ((JSONArray) object).get(0); - } - if (object instanceof Float) { - return (Float) object * scale; - } else if (object instanceof Double) { - return (float) ((Double) object * scale); - } else if (object instanceof Integer) { - return (Integer) object * scale; - } - return null; + @Override + public KeyframeAnimation createAnimation() { + if (!hasAnimation()) { + return new StaticKeyframeAnimation<>(initialValue); } - @Override - public KeyframeAnimation createAnimation() { - if (!hasAnimation()) { - return new StaticKeyframeAnimation<>(initialValue); - } + KeyframeAnimation animation = new NumberKeyframeAnimation<>(duration, composition, keyTimes, Float.class, keyValues, interpolators); + animation.setStartDelay(delay); + return animation; + } - KeyframeAnimation animation = new NumberKeyframeAnimation<>(duration, composition, keyTimes, Float.class, keyValues, interpolators); - animation.setStartDelay(delay); - return animation; - } - - public Float getInitialValue() { - return initialValue; - } + public Float getInitialValue() { + return initialValue; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableIntegerValue.java b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableIntegerValue.java index 33b9e454e1..39b232722f 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableIntegerValue.java +++ b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableIntegerValue.java @@ -14,43 +14,43 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class AnimatableIntegerValue extends BaseAnimatableValue { - public AnimatableIntegerValue(LottieComposition composition, Integer initialValue) { - super(composition); - this.initialValue = initialValue; + public AnimatableIntegerValue(LottieComposition composition, Integer initialValue) { + super(composition); + this.initialValue = initialValue; + } + + public AnimatableIntegerValue(JSONObject json, int frameRate, LottieComposition composition, boolean isDp, boolean remap100To255) { + super(json, frameRate, composition, isDp); + if (remap100To255) { + initialValue = initialValue * 255 / 100; + for (int i = 0; i < keyValues.size(); i++) { + keyValues.set(i, keyValues.get(i) * 255 / 100); + } } - - public AnimatableIntegerValue(JSONObject json, int frameRate, LottieComposition composition, boolean isDp, boolean remap100To255) { - super(json, frameRate, composition, isDp); - if (remap100To255) { - initialValue = initialValue * 255 / 100; - for (int i = 0; i < keyValues.size(); i++) { - keyValues.set(i, keyValues.get(i) * 255 / 100); - } - } + } + + @Override + protected Integer valueFromObject(Object object, float scale) throws JSONException { + if (object instanceof Integer) { + return Math.round((Integer) object * scale); + } else if (object instanceof JSONArray && ((JSONArray) object).get(0) instanceof Integer) { + return Math.round(((JSONArray) object).getInt(0) * scale); } + return null; + } - @Override - protected Integer valueFromObject(Object object, float scale) throws JSONException { - if (object instanceof Integer) { - return Math.round((Integer) object * scale); - } else if (object instanceof JSONArray && ((JSONArray) object).get(0) instanceof Integer) { - return Math.round(((JSONArray) object).getInt(0) * scale); - } - return null; + @Override + public KeyframeAnimation createAnimation() { + if (!hasAnimation()) { + return new StaticKeyframeAnimation<>(initialValue); } - @Override - public KeyframeAnimation createAnimation() { - if (!hasAnimation()) { - return new StaticKeyframeAnimation<>(initialValue); - } - - KeyframeAnimation animation = new NumberKeyframeAnimation<>(duration, composition, keyTimes, Integer.class, keyValues, interpolators); - animation.setStartDelay(delay); - return animation; - } + KeyframeAnimation animation = new NumberKeyframeAnimation<>(duration, composition, keyTimes, Integer.class, keyValues, interpolators); + animation.setStartDelay(delay); + return animation; + } - public Integer getInitialValue() { - return initialValue; - } + public Integer getInitialValue() { + return initialValue; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatablePathValue.java b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatablePathValue.java index becf2249f8..ffb53094a6 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatablePathValue.java +++ b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatablePathValue.java @@ -23,178 +23,178 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class AnimatablePathValue implements AnimatableValue { - private final List keyTimes = new ArrayList<>(); - private final List interpolators = new ArrayList<>(); - private final int frameRate; - private final LottieComposition composition; - - private PointF initialPoint; - private final SegmentedPath animationPath = new SegmentedPath(); - private long delay; - private long duration; - private long startFrame; - private long durationFrames; - - /** - * Create a default static animatable path. - */ - public AnimatablePathValue(LottieComposition composition) { - frameRate = 0; - this.composition = composition; - this.initialPoint = new PointF(0, 0); + private final List keyTimes = new ArrayList<>(); + private final List interpolators = new ArrayList<>(); + private final int frameRate; + private final LottieComposition composition; + + private PointF initialPoint; + private final SegmentedPath animationPath = new SegmentedPath(); + private long delay; + private long duration; + private long startFrame; + private long durationFrames; + + /** + * Create a default static animatable path. + */ + public AnimatablePathValue(LottieComposition composition) { + frameRate = 0; + this.composition = composition; + this.initialPoint = new PointF(0, 0); + } + + public AnimatablePathValue(JSONObject pointValues, int frameRate, LottieComposition composition) { + this.frameRate = frameRate; + this.composition = composition; + + Object value; + try { + value = pointValues.get("k"); + } catch (JSONException e) { + throw new IllegalArgumentException("Point values have no keyframes."); } - public AnimatablePathValue(JSONObject pointValues, int frameRate, LottieComposition composition) { - this.frameRate = frameRate; - this.composition = composition; + if (value instanceof JSONArray) { + Object firstObject; + try { + firstObject = ((JSONArray) value).get(0); + } catch (JSONException e) { + throw new IllegalArgumentException("Unable to parse value."); + } + + if (firstObject instanceof JSONObject && ((JSONObject) firstObject).has("t")) { + // Keyframes + buildAnimationForKeyframes((JSONArray) value); + } else { + // Single Value, no animation + initialPoint = JsonUtils.pointFromJsonArray((JSONArray) value, composition.getScale()); + } + } - Object value; - try { - value = pointValues.get("k"); - } catch (JSONException e) { - throw new IllegalArgumentException("Point values have no keyframes."); - } + } - if (value instanceof JSONArray) { - Object firstObject; - try { - firstObject = ((JSONArray) value).get(0); - } catch (JSONException e) { - throw new IllegalArgumentException("Unable to parse value."); - } - - if (firstObject instanceof JSONObject && ((JSONObject) firstObject).has("t")) { - // Keyframes - buildAnimationForKeyframes((JSONArray) value); - } else { - // Single Value, no animation - initialPoint = JsonUtils.pointFromJsonArray((JSONArray) value, composition.getScale()); - } + @SuppressWarnings("Duplicates") + private void buildAnimationForKeyframes(JSONArray keyframes) { + try { + for (int i = 0; i < keyframes.length(); i++) { + JSONObject kf = keyframes.getJSONObject(i); + if (kf.has("t")) { + startFrame = kf.getLong("t"); + break; + } + } + + for (int i = keyframes.length() - 1; i >= 0; i--) { + JSONObject keyframe = keyframes.getJSONObject(i); + if (keyframe.has("t")) { + long endFrame = keyframe.getLong("t"); + if (endFrame <= startFrame) { + throw new IllegalStateException("Invalid frame compDuration " + startFrame + "->" + endFrame); + } + durationFrames = endFrame - startFrame; + duration = (long) (durationFrames / (float) frameRate * 1000); + delay = (long) (startFrame / (float) frameRate * 1000); + break; + } + } + + boolean addStartValue = true; + boolean addTimePadding = false; + PointF outPoint = null; + + for (int i = 0; i < keyframes.length(); i++) { + JSONObject keyframe = keyframes.getJSONObject(i); + long frame = keyframe.getLong("t"); + float timePercentage = (float) (frame - startFrame) / (float) durationFrames; + + if (outPoint != null) { + PointF vertex = outPoint; + animationPath.lineTo(vertex.x, vertex.y); + interpolators.add(new LinearInterpolator()); + outPoint = null; } - } + float scale = composition.getScale(); + PointF startPoint = keyframe.has("s") ? JsonUtils.pointFromJsonArray(keyframe.getJSONArray("s"), scale) : new PointF(); + if (addStartValue) { + if (i == 0) { + animationPath.moveTo(startPoint.x, startPoint.y); + initialPoint = startPoint; + } else { + animationPath.lineTo(startPoint.x, startPoint.y); + interpolators.add(new LinearInterpolator()); + } + addStartValue = false; + } - @SuppressWarnings("Duplicates") - private void buildAnimationForKeyframes(JSONArray keyframes) { - try { - for (int i = 0; i < keyframes.length(); i++) { - JSONObject kf = keyframes.getJSONObject(i); - if (kf.has("t")) { - startFrame = kf.getLong("t"); - break; - } - } - - for (int i = keyframes.length() - 1; i >= 0; i--) { - JSONObject keyframe = keyframes.getJSONObject(i); - if (keyframe.has("t")) { - long endFrame = keyframe.getLong("t"); - if (endFrame <= startFrame) { - throw new IllegalStateException("Invalid frame compDuration " + startFrame + "->" + endFrame); - } - durationFrames = endFrame - startFrame; - duration = (long) (durationFrames / (float) frameRate * 1000); - delay = (long) (startFrame / (float) frameRate * 1000); - break; - } - } - - boolean addStartValue = true; - boolean addTimePadding = false; - PointF outPoint = null; - - for (int i = 0; i < keyframes.length(); i++) { - JSONObject keyframe = keyframes.getJSONObject(i); - long frame = keyframe.getLong("t"); - float timePercentage = (float) (frame - startFrame) / (float) durationFrames; - - if (outPoint != null) { - PointF vertex = outPoint; - animationPath.lineTo(vertex.x, vertex.y); - interpolators.add(new LinearInterpolator()); - outPoint = null; - } - - float scale = composition.getScale(); - PointF startPoint = keyframe.has("s") ? JsonUtils.pointFromJsonArray(keyframe.getJSONArray("s"), scale) : new PointF(); - if (addStartValue) { - if (i == 0) { - animationPath.moveTo(startPoint.x, startPoint.y); - initialPoint = startPoint; - } else { - animationPath.lineTo(startPoint.x, startPoint.y); - interpolators.add(new LinearInterpolator()); - } - addStartValue = false; - } - - if (addTimePadding) { - float holdPercentage = timePercentage - 0.00001f; - keyTimes.add(holdPercentage); - addTimePadding = false; - } - - PointF cp1; - PointF cp2; - if (keyframe.has("e")) { - cp1 = keyframe.has("to") ? JsonUtils.pointFromJsonArray(keyframe.getJSONArray("to"), scale) : null; - cp2 = keyframe.has("ti") ? JsonUtils.pointFromJsonArray(keyframe.getJSONArray("ti"), scale) : null; - PointF vertex = JsonUtils.pointFromJsonArray(keyframe.getJSONArray("e"), scale); - if (cp1 != null && cp2 != null && cp1.length() != 0 && cp2.length() != 0) { - animationPath.cubicTo( - startPoint.x + cp1.x, startPoint.y + cp1.y, - vertex.x + cp2.x, vertex.y + cp2.y, - vertex.x, vertex.y); - } else { - animationPath.lineTo(vertex.x, vertex.y); - } - - Interpolator interpolator; - if (keyframe.has("o") && keyframe.has("i")) { - cp1 = JsonUtils.pointValueFromJsonObject(keyframe.getJSONObject("o"), scale); - cp2 = JsonUtils.pointValueFromJsonObject(keyframe.getJSONObject("i"), scale); - interpolator = PathInterpolatorCompat.create(cp1.x / scale, cp1.y / scale, cp2.x / scale, cp2.y / scale); - } else { - interpolator = new LinearInterpolator(); - } - interpolators.add(interpolator); - } - - keyTimes.add(timePercentage); - - if (keyframe.has("h") && keyframe.getInt("h") == 1) { - outPoint = startPoint; - addStartValue = true; - addTimePadding = true; - } - } - } catch (JSONException e) { - throw new IllegalArgumentException("Unable to parse keyframes " + keyframes, e); + if (addTimePadding) { + float holdPercentage = timePercentage - 0.00001f; + keyTimes.add(holdPercentage); + addTimePadding = false; } - } - @Override - public KeyframeAnimation createAnimation() { - if (!hasAnimation()) { - return new StaticKeyframeAnimation<>(initialPoint); + PointF cp1; + PointF cp2; + if (keyframe.has("e")) { + cp1 = keyframe.has("to") ? JsonUtils.pointFromJsonArray(keyframe.getJSONArray("to"), scale) : null; + cp2 = keyframe.has("ti") ? JsonUtils.pointFromJsonArray(keyframe.getJSONArray("ti"), scale) : null; + PointF vertex = JsonUtils.pointFromJsonArray(keyframe.getJSONArray("e"), scale); + if (cp1 != null && cp2 != null && cp1.length() != 0 && cp2.length() != 0) { + animationPath.cubicTo( + startPoint.x + cp1.x, startPoint.y + cp1.y, + vertex.x + cp2.x, vertex.y + cp2.y, + vertex.x, vertex.y); + } else { + animationPath.lineTo(vertex.x, vertex.y); + } + + Interpolator interpolator; + if (keyframe.has("o") && keyframe.has("i")) { + cp1 = JsonUtils.pointValueFromJsonObject(keyframe.getJSONObject("o"), scale); + cp2 = JsonUtils.pointValueFromJsonObject(keyframe.getJSONObject("i"), scale); + interpolator = PathInterpolatorCompat.create(cp1.x / scale, cp1.y / scale, cp2.x / scale, cp2.y / scale); + } else { + interpolator = new LinearInterpolator(); + } + interpolators.add(interpolator); } - KeyframeAnimation animation = new PathKeyframeAnimation(duration, composition, keyTimes, animationPath, interpolators); - animation.setStartDelay(delay); - return animation; - } + keyTimes.add(timePercentage); - @Override - public boolean hasAnimation() { - return animationPath.hasSegments(); + if (keyframe.has("h") && keyframe.getInt("h") == 1) { + outPoint = startPoint; + addStartValue = true; + addTimePadding = true; + } + } + } catch (JSONException e) { + throw new IllegalArgumentException("Unable to parse keyframes " + keyframes, e); } + } - public PointF getInitialPoint() { - return initialPoint; + @Override + public KeyframeAnimation createAnimation() { + if (!hasAnimation()) { + return new StaticKeyframeAnimation<>(initialPoint); } - @Override - public String toString() { - return "initialPoint=" + initialPoint; - } + KeyframeAnimation animation = new PathKeyframeAnimation(duration, composition, keyTimes, animationPath, interpolators); + animation.setStartDelay(delay); + return animation; + } + + @Override + public boolean hasAnimation() { + return animationPath.hasSegments(); + } + + public PointF getInitialPoint() { + return initialPoint; + } + + @Override + public String toString() { + return "initialPoint=" + initialPoint; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatablePointValue.java b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatablePointValue.java index cdd9f97c0e..7540f9af1a 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatablePointValue.java +++ b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatablePointValue.java @@ -16,33 +16,33 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class AnimatablePointValue extends BaseAnimatableValue { - public AnimatablePointValue(JSONObject pointValues, int frameRate, LottieComposition composition) { - super(pointValues, frameRate, composition, true); + public AnimatablePointValue(JSONObject pointValues, int frameRate, LottieComposition composition) { + super(pointValues, frameRate, composition, true); + } + + @Override + protected PointF valueFromObject(Object object, float scale) throws JSONException { + if (object instanceof JSONArray) { + return JsonUtils.pointFromJsonArray((JSONArray) object, scale); + } else if (object instanceof JSONObject) { + return JsonUtils.pointValueFromJsonObject((JSONObject) object, scale); } + throw new IllegalArgumentException("Unable to parse point from " + object); + } - @Override - protected PointF valueFromObject(Object object, float scale) throws JSONException { - if (object instanceof JSONArray) { - return JsonUtils.pointFromJsonArray((JSONArray) object, scale); - } else if (object instanceof JSONObject) { - return JsonUtils.pointValueFromJsonObject((JSONObject) object, scale); - } - throw new IllegalArgumentException("Unable to parse point from " + object); + @Override + public KeyframeAnimation createAnimation() { + if (!hasAnimation()) { + return new StaticKeyframeAnimation<>(initialValue); } - @Override - public KeyframeAnimation createAnimation() { - if (!hasAnimation()) { - return new StaticKeyframeAnimation<>(initialValue); - } + KeyframeAnimation animation = new PointKeyframeAnimation(duration, composition, keyTimes, keyValues, interpolators); + animation.setStartDelay(delay); + return animation; + } - KeyframeAnimation animation = new PointKeyframeAnimation(duration, composition, keyTimes, keyValues, interpolators); - animation.setStartDelay(delay); - return animation; - } - - @Override - public boolean hasAnimation() { - return !keyValues.isEmpty(); - } + @Override + public boolean hasAnimation() { + return !keyValues.isEmpty(); + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableScaleValue.java b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableScaleValue.java index 86d1aa387e..74526a62af 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableScaleValue.java +++ b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableScaleValue.java @@ -15,37 +15,37 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class AnimatableScaleValue extends BaseAnimatableValue { - public AnimatableScaleValue(LottieComposition composition) { - super(composition); - initialValue = new ScaleXY(); + public AnimatableScaleValue(LottieComposition composition) { + super(composition); + initialValue = new ScaleXY(); + } + + public AnimatableScaleValue(JSONObject scaleValues, int frameRate, LottieComposition composition, boolean isDp) { + super(scaleValues, frameRate, composition, isDp); + } + + @Override + protected ScaleXY valueFromObject(Object object, float scale) throws JSONException { + JSONArray array = (JSONArray) object; + try { + if (array.length() >= 2) { + return new ScaleXY().scale((float) array.getDouble(0) / 100f * scale, (float) array.getDouble(1) / 100f * scale); + } + } catch (JSONException e) { + // Do nothing. } - public AnimatableScaleValue(JSONObject scaleValues, int frameRate, LottieComposition composition, boolean isDp) { - super(scaleValues, frameRate, composition, isDp); - } + return new ScaleXY(); + } - @Override - protected ScaleXY valueFromObject(Object object, float scale) throws JSONException { - JSONArray array = (JSONArray) object; - try { - if (array.length() >= 2) { - return new ScaleXY().scale((float) array.getDouble(0) / 100f * scale, (float) array.getDouble(1) / 100f * scale); - } - } catch (JSONException e) { - // Do nothing. - } - - return new ScaleXY(); + @Override + public KeyframeAnimation createAnimation() { + if (!hasAnimation()) { + return new StaticKeyframeAnimation<>(initialValue); } - @Override - public KeyframeAnimation createAnimation() { - if (!hasAnimation()) { - return new StaticKeyframeAnimation<>(initialValue); - } - - ScaleKeyframeAnimation animation = new ScaleKeyframeAnimation(duration, composition, keyTimes, keyValues, interpolators); - animation.setStartDelay(delay); - return animation; - } + ScaleKeyframeAnimation animation = new ScaleKeyframeAnimation(duration, composition, keyTimes, keyValues, interpolators); + animation.setStartDelay(delay); + return animation; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableShapeValue.java b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableShapeValue.java index 1f9d72a340..e0b391f5ab 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableShapeValue.java +++ b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableShapeValue.java @@ -20,140 +20,140 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class AnimatableShapeValue extends BaseAnimatableValue { - private final Path convertTypePath = new Path(); - - private boolean closed; + private final Path convertTypePath = new Path(); + + private boolean closed; + + public AnimatableShapeValue(JSONObject json, int frameRate, LottieComposition composition, boolean closed) { + super(null, frameRate, composition, true); + this.closed = closed; + init(json); + } + + @Override + protected ShapeData valueFromObject(Object object, float scale) throws JSONException { + JSONObject pointsData = null; + if (object instanceof JSONArray) { + try { + Object firstObject = ((JSONArray) object).get(0); + if (firstObject instanceof JSONObject && ((JSONObject) firstObject).has("v")) { + pointsData = (JSONObject) firstObject; + } + } catch (JSONException e) { + throw new IllegalStateException("Unable to get shape. " + object); + } + } else if (object instanceof JSONObject && ((JSONObject) object).has("v")) { + pointsData = (JSONObject) object; + } - public AnimatableShapeValue(JSONObject json, int frameRate, LottieComposition composition, boolean closed) { - super(null, frameRate, composition, true); - this.closed = closed; - init(json); + if (pointsData == null) { + return null; } - @Override - protected ShapeData valueFromObject(Object object, float scale) throws JSONException { - JSONObject pointsData = null; - if (object instanceof JSONArray) { - try { - Object firstObject = ((JSONArray) object).get(0); - if (firstObject instanceof JSONObject && ((JSONObject) firstObject).has("v")) { - pointsData = (JSONObject) firstObject; - } - } catch (JSONException e) { - throw new IllegalStateException("Unable to get shape. " + object); - } - } else if (object instanceof JSONObject && ((JSONObject) object).has("v")) { - pointsData = (JSONObject) object; - } + JSONArray pointsArray = null; + JSONArray inTangents = null; + JSONArray outTangents = null; + try { + pointsArray = pointsData.getJSONArray("v"); + inTangents = pointsData.getJSONArray("i"); + outTangents = pointsData.getJSONArray("o"); + + if (pointsData.has("c")) { + // Bodymovin < 4.4 uses "closed" one level up in the json so it is passed in to the constructor. + // Bodymovin 4.4+ has closed here. + closed = pointsData.getBoolean("c"); + } + } catch (JSONException e) { + // Do nothing. + } - if (pointsData == null) { - return null; - } + if (pointsArray == null || inTangents == null || outTangents == null || + pointsArray.length() != inTangents.length() || pointsArray.length() != outTangents.length()) { + throw new IllegalStateException("Unable to process points array or tangents. " + pointsData); + } - JSONArray pointsArray = null; - JSONArray inTangents = null; - JSONArray outTangents = null; - try { - pointsArray = pointsData.getJSONArray("v"); - inTangents = pointsData.getJSONArray("i"); - outTangents = pointsData.getJSONArray("o"); - - if (pointsData.has("c")) { - // Bodymovin < 4.4 uses "closed" one level up in the json so it is passed in to the constructor. - // Bodymovin 4.4+ has closed here. - closed = pointsData.getBoolean("c"); - } - } catch (JSONException e) { - // Do nothing. - } + ShapeData shape = new ShapeData(); - if (pointsArray == null || inTangents == null || outTangents == null || - pointsArray.length() != inTangents.length() || pointsArray.length() != outTangents.length()) { - throw new IllegalStateException("Unable to process points array or tangents. " + pointsData); - } + PointF vertex = vertexAtIndex(0, pointsArray); + vertex.x *= scale; + vertex.y *= scale; + shape.setInitialPoint(vertex); - ShapeData shape = new ShapeData(); + for (int i = 1; i < pointsArray.length(); i++) { + vertex = vertexAtIndex(i, pointsArray); + PointF previousVertex = vertexAtIndex(i - 1, pointsArray); + PointF cp1 = vertexAtIndex(i - 1, outTangents); + PointF cp2 = vertexAtIndex(i, inTangents); - PointF vertex = vertexAtIndex(0, pointsArray); - vertex.x *= scale; - vertex.y *= scale; - shape.setInitialPoint(vertex); + PointF shapeCp1 = addPoints(previousVertex, cp1); + PointF shapeCp2 = addPoints(vertex, cp2); - for (int i = 1; i < pointsArray.length(); i++) { - vertex = vertexAtIndex(i, pointsArray); - PointF previousVertex = vertexAtIndex(i - 1, pointsArray); - PointF cp1 = vertexAtIndex(i - 1, outTangents); - PointF cp2 = vertexAtIndex(i, inTangents); + shapeCp1.x *= scale; + shapeCp1.y *= scale; + shapeCp2.x *= scale; + shapeCp2.y *= scale; + vertex.x *= scale; + vertex.y *= scale; - PointF shapeCp1 = addPoints(previousVertex, cp1); - PointF shapeCp2 = addPoints(vertex, cp2); + shape.addCurve(new CubicCurveData(shapeCp1, shapeCp2, vertex)); + } - shapeCp1.x *= scale; - shapeCp1.y *= scale; - shapeCp2.x *= scale; - shapeCp2.y *= scale; - vertex.x *= scale; - vertex.y *= scale; + if (closed) { + vertex = vertexAtIndex(0, pointsArray); + PointF previousVertex = vertexAtIndex(pointsArray.length() - 1, pointsArray); + PointF cp1 = vertexAtIndex(pointsArray.length() - 1, outTangents); + PointF cp2 = vertexAtIndex(0, inTangents); - shape.addCurve(new CubicCurveData(shapeCp1, shapeCp2, vertex)); - } + PointF shapeCp1 = addPoints(previousVertex, cp1); + PointF shapeCp2 = addPoints(vertex, cp2); - if (closed) { - vertex = vertexAtIndex(0, pointsArray); - PointF previousVertex = vertexAtIndex(pointsArray.length() - 1, pointsArray); - PointF cp1 = vertexAtIndex(pointsArray.length() - 1, outTangents); - PointF cp2 = vertexAtIndex(0, inTangents); - - PointF shapeCp1 = addPoints(previousVertex, cp1); - PointF shapeCp2 = addPoints(vertex, cp2); - - if (scale != 1f) { - shapeCp1.x *= scale; - shapeCp1.y *= scale; - shapeCp2.x *= scale; - shapeCp2.y *= scale; - vertex.x *= scale; - vertex.y *= scale; - } - - shape.addCurve(new CubicCurveData(shapeCp1, shapeCp2, vertex)); - } - return shape; + if (scale != 1f) { + shapeCp1.x *= scale; + shapeCp1.y *= scale; + shapeCp2.x *= scale; + shapeCp2.y *= scale; + vertex.x *= scale; + vertex.y *= scale; + } + shape.addCurve(new CubicCurveData(shapeCp1, shapeCp2, vertex)); } + return shape; - private PointF vertexAtIndex(int idx, JSONArray points) { - if (idx >= points.length()) { - throw new IllegalArgumentException("Invalid index " + idx + ". There are only " + points.length() + " points."); - } + } - try { - JSONArray pointArray = points.getJSONArray(idx); - Object x = pointArray.get(0); - Object y = pointArray.get(1); - return new PointF( - x instanceof Double ? new Float((Double) x) : (int) x, - y instanceof Double ? new Float((Double) y) : (int) y); - } catch (JSONException e) { - throw new IllegalArgumentException("Unable to get point.", e); - } + private PointF vertexAtIndex(int idx, JSONArray points) { + if (idx >= points.length()) { + throw new IllegalArgumentException("Invalid index " + idx + ". There are only " + points.length() + " points."); } - @Override - public KeyframeAnimation createAnimation() { - if (!hasAnimation()) { - return new StaticKeyframeAnimation<>(convertType(initialValue)); - } - - ShapeKeyframeAnimation animation = new ShapeKeyframeAnimation(duration, composition, keyTimes, keyValues, interpolators); - animation.setStartDelay(delay); - return animation; + try { + JSONArray pointArray = points.getJSONArray(idx); + Object x = pointArray.get(0); + Object y = pointArray.get(1); + return new PointF( + x instanceof Double ? new Float((Double) x) : (int) x, + y instanceof Double ? new Float((Double) y) : (int) y); + } catch (JSONException e) { + throw new IllegalArgumentException("Unable to get point.", e); } + } - @Override - Path convertType(ShapeData shapeData) { - convertTypePath.reset(); - MiscUtils.getPathFromData(shapeData, convertTypePath); - return convertTypePath; + @Override + public KeyframeAnimation createAnimation() { + if (!hasAnimation()) { + return new StaticKeyframeAnimation<>(convertType(initialValue)); } + + ShapeKeyframeAnimation animation = new ShapeKeyframeAnimation(duration, composition, keyTimes, keyValues, interpolators); + animation.setStartDelay(delay); + return animation; + } + + @Override + Path convertType(ShapeData shapeData) { + convertTypePath.reset(); + MiscUtils.getPathFromData(shapeData, convertTypePath); + return convertTypePath; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableValue.java b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableValue.java index 19c36e703d..cff2308f82 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableValue.java +++ b/lottie/src/main/java/com/airbnb/lottie/animatable/AnimatableValue.java @@ -4,6 +4,7 @@ interface AnimatableValue { - KeyframeAnimation createAnimation(); - boolean hasAnimation(); + KeyframeAnimation createAnimation(); + + boolean hasAnimation(); } diff --git a/lottie/src/main/java/com/airbnb/lottie/animatable/BaseAnimatableValue.java b/lottie/src/main/java/com/airbnb/lottie/animatable/BaseAnimatableValue.java index 0e526bf182..57e56b9f7f 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animatable/BaseAnimatableValue.java +++ b/lottie/src/main/java/com/airbnb/lottie/animatable/BaseAnimatableValue.java @@ -20,177 +20,177 @@ import java.util.List; abstract class BaseAnimatableValue implements AnimatableValue { - final List keyValues = new ArrayList<>(); - final List keyTimes = new ArrayList<>(); - final List interpolators = new ArrayList<>(); - long delay; - long duration; - final LottieComposition composition; - private final boolean isDp; - - private long startFrame; - private long durationFrames; - private final int frameRate; - - V initialValue; - - /** - * Create a default static animatable path. - */ - BaseAnimatableValue(LottieComposition composition) { - this.composition = composition; - isDp = false; - frameRate = 0; + final List keyValues = new ArrayList<>(); + final List keyTimes = new ArrayList<>(); + final List interpolators = new ArrayList<>(); + long delay; + long duration; + final LottieComposition composition; + private final boolean isDp; + + private long startFrame; + private long durationFrames; + private final int frameRate; + + V initialValue; + + /** + * Create a default static animatable path. + */ + BaseAnimatableValue(LottieComposition composition) { + this.composition = composition; + isDp = false; + frameRate = 0; + } + + BaseAnimatableValue(@Nullable JSONObject json, int frameRate, LottieComposition composition, boolean isDp) { + this.frameRate = frameRate; + this.composition = composition; + this.isDp = isDp; + if (json != null) { + init(json); } - - BaseAnimatableValue(@Nullable JSONObject json, int frameRate, LottieComposition composition, boolean isDp) { - this.frameRate = frameRate; - this.composition = composition; - this.isDp = isDp; - if (json != null) { - init(json); - } + } + + void init(JSONObject json) { + try { + Object value = json.get("k"); + + if (value instanceof JSONArray && + ((JSONArray) value).get(0) instanceof JSONObject && + ((JSONArray) value).getJSONObject(0).has("t")) { + buildAnimationForKeyframes((JSONArray) value); + } else { + initialValue = valueFromObject(value, getScale()); + } + } catch (JSONException e) { + throw new IllegalStateException("Unable to parse json " + json, e); } + } + + @SuppressWarnings("Duplicates") + private void buildAnimationForKeyframes(JSONArray keyframes) { + try { + for (int i = 0; i < keyframes.length(); i++) { + JSONObject kf = keyframes.getJSONObject(i); + if (kf.has("t")) { + startFrame = kf.getLong("t"); + break; + } + } + + for (int i = keyframes.length() - 1; i >= 0; i--) { + JSONObject keyframe = keyframes.getJSONObject(i); + if (keyframe.has("t")) { + long endFrame = keyframe.getLong("t"); + if (endFrame <= startFrame) { + throw new IllegalStateException("Invalid frame compDuration " + startFrame + "->" + endFrame); + } + durationFrames = endFrame - startFrame; + duration = (long) (durationFrames / (float) frameRate * 1000); + delay = (long) (startFrame / (float) frameRate * 1000); + break; + } + } - void init(JSONObject json) { - try { - Object value = json.get("k"); + boolean addStartValue = true; + boolean addTimePadding = false; + V outValue = null; - if (value instanceof JSONArray && - ((JSONArray) value).get(0) instanceof JSONObject && - ((JSONArray) value).getJSONObject(0).has("t")) { - buildAnimationForKeyframes((JSONArray) value); - } else { - initialValue = valueFromObject(value, getScale()); - } - } catch (JSONException e) { - throw new IllegalStateException("Unable to parse json " + json, e); + for (int i = 0; i < keyframes.length(); i++) { + JSONObject keyframe = keyframes.getJSONObject(i); + long frame = keyframe.getLong("t"); + float timePercentage = (float) (frame - startFrame) / (float) durationFrames; + + if (outValue != null) { + keyValues.add(outValue); + interpolators.add(new LinearInterpolator()); + outValue = null; } - } - @SuppressWarnings("Duplicates") - private void buildAnimationForKeyframes(JSONArray keyframes) { - try { - for (int i = 0; i < keyframes.length(); i++) { - JSONObject kf = keyframes.getJSONObject(i); - if (kf.has("t")) { - startFrame = kf.getLong("t"); - break; - } + V startValue = keyframe.has("s") ? valueFromObject(keyframe.getJSONArray("s"), getScale()) : null; + if (addStartValue) { + if (startValue != null) { + if (i == 0) { + //noinspection ResourceAsColor + initialValue = startValue; } - - for (int i = keyframes.length() - 1; i >= 0; i--) { - JSONObject keyframe = keyframes.getJSONObject(i); - if (keyframe.has("t")) { - long endFrame = keyframe.getLong("t"); - if (endFrame <= startFrame) { - throw new IllegalStateException("Invalid frame compDuration " + startFrame + "->" + endFrame); - } - durationFrames = endFrame - startFrame; - duration = (long) (durationFrames / (float) frameRate * 1000); - delay = (long) (startFrame / (float) frameRate * 1000); - break; - } + keyValues.add(startValue); + if (!interpolators.isEmpty()) { + interpolators.add(new LinearInterpolator()); } + } + addStartValue = false; + } + + if (addTimePadding) { + float holdPercentage = timePercentage - 0.00001f; + keyTimes.add(holdPercentage); + addTimePadding = false; + } - boolean addStartValue = true; - boolean addTimePadding = false; - V outValue = null; - - for (int i = 0; i < keyframes.length(); i++) { - JSONObject keyframe = keyframes.getJSONObject(i); - long frame = keyframe.getLong("t"); - float timePercentage = (float) (frame - startFrame) / (float) durationFrames; - - if (outValue != null) { - keyValues.add(outValue); - interpolators.add(new LinearInterpolator()); - outValue = null; - } - - V startValue = keyframe.has("s") ? valueFromObject(keyframe.getJSONArray("s"), getScale()) : null; - if (addStartValue) { - if (startValue != null) { - if (i == 0) { - //noinspection ResourceAsColor - initialValue = startValue; - } - keyValues.add(startValue); - if (!interpolators.isEmpty()) { - interpolators.add(new LinearInterpolator()); - } - } - addStartValue = false; - } - - if (addTimePadding) { - float holdPercentage = timePercentage - 0.00001f; - keyTimes.add(holdPercentage); - addTimePadding = false; - } - - if (keyframe.has("e")) { - V endValue = valueFromObject(keyframe.getJSONArray("e"), getScale()); - keyValues.add(endValue); + if (keyframe.has("e")) { + V endValue = valueFromObject(keyframe.getJSONArray("e"), getScale()); + keyValues.add(endValue); /* Timing function for time interpolation between keyframes. Should be n - 1 where n is the number of keyframes. */ - Interpolator timingFunction; - if (keyframe.has("o") && keyframe.has("i")) { - JSONObject timingControlPoint1 = keyframe.getJSONObject("o"); - JSONObject timingControlPoint2 = keyframe.getJSONObject("i"); - PointF cp1 = JsonUtils.pointValueFromJsonObject(timingControlPoint1, 1); - PointF cp2 = JsonUtils.pointValueFromJsonObject(timingControlPoint2, 1); - - timingFunction = PathInterpolatorCompat.create(cp1.x, cp1.y, cp2.x, cp2.y); - } else { - timingFunction = new LinearInterpolator(); - } - interpolators.add(timingFunction); - } - - keyTimes.add(timePercentage); - - if (keyframe.has("h") && keyframe.getInt("h") == 1) { - outValue = startValue; - addStartValue = true; - addTimePadding = true; - } - } - } catch (JSONException e) { - throw new IllegalArgumentException("Unable to parse values.", e); + Interpolator timingFunction; + if (keyframe.has("o") && keyframe.has("i")) { + JSONObject timingControlPoint1 = keyframe.getJSONObject("o"); + JSONObject timingControlPoint2 = keyframe.getJSONObject("i"); + PointF cp1 = JsonUtils.pointValueFromJsonObject(timingControlPoint1, 1); + PointF cp2 = JsonUtils.pointValueFromJsonObject(timingControlPoint2, 1); + + timingFunction = PathInterpolatorCompat.create(cp1.x, cp1.y, cp2.x, cp2.y); + } else { + timingFunction = new LinearInterpolator(); + } + interpolators.add(timingFunction); } - } - private float getScale() { - return isDp ? composition.getScale() : 1f; - } + keyTimes.add(timePercentage); - O convertType(V value) { - //noinspection unchecked - return (O) value; + if (keyframe.has("h") && keyframe.getInt("h") == 1) { + outValue = startValue; + addStartValue = true; + addTimePadding = true; + } + } + } catch (JSONException e) { + throw new IllegalArgumentException("Unable to parse values.", e); } + } - public boolean hasAnimation() { - return !keyValues.isEmpty(); - } + private float getScale() { + return isDp ? composition.getScale() : 1f; + } - public O getInitialValue() { - return convertType(initialValue); - } + O convertType(V value) { + //noinspection unchecked + return (O) value; + } - protected abstract V valueFromObject(Object object, float scale) throws JSONException; + public boolean hasAnimation() { + return !keyValues.isEmpty(); + } - public abstract KeyframeAnimation createAnimation(); + public O getInitialValue() { + return convertType(initialValue); + } - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(); - sb.append("initialValue=").append(initialValue); - if (!keyValues.isEmpty()) { - sb.append(", values=").append(Arrays.toString(keyTimes.toArray())); - } - return sb.toString(); + protected abstract V valueFromObject(Object object, float scale) throws JSONException; + + public abstract KeyframeAnimation createAnimation(); + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("initialValue=").append(initialValue); + if (!keyValues.isEmpty()) { + sb.append(", values=").append(Arrays.toString(keyTimes.toArray())); } + return sb.toString(); + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/ColorKeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/animation/ColorKeyframeAnimation.java index e0bf3f6ac8..a4d7861db3 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/ColorKeyframeAnimation.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/ColorKeyframeAnimation.java @@ -10,42 +10,42 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ColorKeyframeAnimation extends KeyframeAnimation { - private final ArgbEvaluator argbEvaluator = new ArgbEvaluator(); + private final ArgbEvaluator argbEvaluator = new ArgbEvaluator(); - private final List values; + private final List values; - public ColorKeyframeAnimation(long duration, LottieComposition composition, List keyTimes, List values, List interpolators) { - super(duration, composition, keyTimes, interpolators); - if (keyTimes.size() != values.size()) { - throw new IllegalArgumentException("Key times and values must be the same length " + keyTimes.size() + " vs " + values.size()); - } - this.values = values; + public ColorKeyframeAnimation(long duration, LottieComposition composition, List keyTimes, List values, List interpolators) { + super(duration, composition, keyTimes, interpolators); + if (keyTimes.size() != values.size()) { + throw new IllegalArgumentException("Key times and values must be the same length " + keyTimes.size() + " vs " + values.size()); + } + this.values = values; + } + + @Override + public Integer getValue() { + if (progress <= 0f) { + return values.get(0); + } else if (progress >= 1f) { + return values.get(values.size() - 1); } - @Override - public Integer getValue() { - if (progress <= 0f) { - return values.get(0); - } else if (progress >= 1f) { - return values.get(values.size() - 1); - } - - int keyframeIndex = getKeyframeIndex(); + int keyframeIndex = getKeyframeIndex(); - float startKeytime = keyTimes.get(keyframeIndex); - float endKeytime = keyTimes.get(keyframeIndex + 1); + float startKeytime = keyTimes.get(keyframeIndex); + float endKeytime = keyTimes.get(keyframeIndex + 1); - float percentageIntoFrame = 0; - if (!isDiscrete) { - percentageIntoFrame = (progress - startKeytime) / (endKeytime - startKeytime); - if (interpolators != null) { - percentageIntoFrame = interpolators.get(keyframeIndex).getInterpolation(percentageIntoFrame); - } - } + float percentageIntoFrame = 0; + if (!isDiscrete) { + percentageIntoFrame = (progress - startKeytime) / (endKeytime - startKeytime); + if (interpolators != null) { + percentageIntoFrame = interpolators.get(keyframeIndex).getInterpolation(percentageIntoFrame); + } + } - int startColor = values.get(keyframeIndex); - int endColor = values.get(keyframeIndex + 1); + int startColor = values.get(keyframeIndex); + int endColor = values.get(keyframeIndex + 1); - return (Integer) argbEvaluator.evaluate(percentageIntoFrame, startColor, endColor); - } + return (Integer) argbEvaluator.evaluate(percentageIntoFrame, startColor, endColor); + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/KeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/animation/KeyframeAnimation.java index a29201efa0..9cd9717065 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/KeyframeAnimation.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/KeyframeAnimation.java @@ -12,108 +12,108 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public abstract class KeyframeAnimation { - public interface AnimationListener { - void onValueChanged(T progress); + public interface AnimationListener { + void onValueChanged(T progress); + } + + private final List> listeners = new ArrayList<>(); + private final long duration; + private final LottieComposition composition; + final List keyTimes; + + private long startDelay; + boolean isDiscrete = false; + final List interpolators; + + float progress; + + private int cachedKeyframeIndex = -1; + private float cachedKeyframeIndexStart; + private float cachedKeyframeIndexEnd; + private float cachedDurationEndProgress = Float.MIN_VALUE; + + KeyframeAnimation(long duration, LottieComposition composition, List keyTimes, List interpolators) { + this.duration = duration; + this.composition = composition; + this.keyTimes = keyTimes; + this.interpolators = interpolators; + if (!interpolators.isEmpty() && interpolators.size() != (keyTimes.size() - 1)) { + throw new IllegalArgumentException("There must be 1 fewer interpolator than keytime " + interpolators.size() + " vs " + keyTimes.size()); } - - private final List> listeners = new ArrayList<>(); - private final long duration; - private final LottieComposition composition; - final List keyTimes; - - private long startDelay; - boolean isDiscrete = false; - final List interpolators; - - float progress; - - private int cachedKeyframeIndex = -1; - private float cachedKeyframeIndexStart; - private float cachedKeyframeIndexEnd; - private float cachedDurationEndProgress = Float.MIN_VALUE; - - KeyframeAnimation(long duration, LottieComposition composition, List keyTimes, List interpolators) { - this.duration = duration; - this.composition = composition; - this.keyTimes = keyTimes; - this.interpolators = interpolators; - if (!interpolators.isEmpty() && interpolators.size() != (keyTimes.size() - 1)) { - throw new IllegalArgumentException("There must be 1 fewer interpolator than keytime " + interpolators.size() + " vs " + keyTimes.size()); - } - } - - public void setStartDelay(long startDelay) { - this.startDelay = startDelay; - cachedDurationEndProgress = Float.MIN_VALUE; - } - - public void setIsDiscrete() { - isDiscrete = true; + } + + public void setStartDelay(long startDelay) { + this.startDelay = startDelay; + cachedDurationEndProgress = Float.MIN_VALUE; + } + + public void setIsDiscrete() { + isDiscrete = true; + } + + public void addUpdateListener(AnimationListener listener) { + listeners.add(listener); + } + + public void removeUpdateListener(AnimationListener listener) { + listeners.remove(listener); + } + + public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { + if (progress < getStartDelayProgress()) { + progress = 0f; + } else if (progress > getDurationEndProgress()) { + progress = 1f; + } else { + progress = (progress - getStartDelayProgress()) / getDurationRangeProgress(); } - - public void addUpdateListener(AnimationListener listener) { - listeners.add(listener); + if (progress == this.progress) { + return; } + this.progress = progress; - public void removeUpdateListener(AnimationListener listener) { - listeners.remove(listener); + T value = getValue(); + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).onValueChanged(value); } - - public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { - if (progress < getStartDelayProgress()) { - progress = 0f; - } else if (progress > getDurationEndProgress()) { - progress = 1f; - } else { - progress = (progress - getStartDelayProgress()) / getDurationRangeProgress(); - } - if (progress == this.progress) { - return; - } - this.progress = progress; - - T value = getValue(); - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onValueChanged(value); - } + } + + int getKeyframeIndex() { + int keyframeIndex = 1; + if (cachedKeyframeIndex != -1 && progress >= cachedKeyframeIndexStart && progress <= cachedKeyframeIndexEnd) { + keyframeIndex = cachedKeyframeIndex; + } else { + float keyTime = keyTimes.get(1); + while (keyTime < progress && keyframeIndex < keyTimes.size() - 1) { + keyframeIndex++; + keyTime = keyTimes.get(keyframeIndex); + } + cachedKeyframeIndex = keyframeIndex; + cachedKeyframeIndexStart = keyTimes.get(cachedKeyframeIndex - 1); + cachedKeyframeIndexEnd = keyTimes.get(cachedKeyframeIndex); } - int getKeyframeIndex() { - int keyframeIndex = 1; - if (cachedKeyframeIndex != -1 && progress >= cachedKeyframeIndexStart && progress <= cachedKeyframeIndexEnd) { - keyframeIndex = cachedKeyframeIndex; - } else { - float keyTime = keyTimes.get(1); - while (keyTime < progress && keyframeIndex < keyTimes.size() - 1) { - keyframeIndex++; - keyTime = keyTimes.get(keyframeIndex); - } - cachedKeyframeIndex = keyframeIndex; - cachedKeyframeIndexStart = keyTimes.get(cachedKeyframeIndex - 1); - cachedKeyframeIndexEnd = keyTimes.get(cachedKeyframeIndex); - } - - return keyframeIndex - 1; - } + return keyframeIndex - 1; + } - @FloatRange(from=0f, to=1f) - private float getStartDelayProgress() { - return (float) startDelay / (float) (composition.getDuration()); - } + @FloatRange(from = 0f, to = 1f) + private float getStartDelayProgress() { + return (float) startDelay / (float) (composition.getDuration()); + } - @FloatRange(from=0f, to=1f) - private float getDurationEndProgress() { - if (cachedDurationEndProgress == Float.MIN_VALUE) { - // This was taking a surprisingly long time according to systrace. Cache it! - cachedDurationEndProgress = getStartDelayProgress() + getDurationRangeProgress(); - } - return cachedDurationEndProgress; + @FloatRange(from = 0f, to = 1f) + private float getDurationEndProgress() { + if (cachedDurationEndProgress == Float.MIN_VALUE) { + // This was taking a surprisingly long time according to systrace. Cache it! + cachedDurationEndProgress = getStartDelayProgress() + getDurationRangeProgress(); } + return cachedDurationEndProgress; + } - @FloatRange(from=0f, to=1f) - private float getDurationRangeProgress() { - return (float) duration / (float) composition.getDuration(); - } + @FloatRange(from = 0f, to = 1f) + private float getDurationRangeProgress() { + return (float) duration / (float) composition.getDuration(); + } - public abstract T getValue(); + public abstract T getValue(); } diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/NumberKeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/animation/NumberKeyframeAnimation.java index 4c6e9f341d..fddd9adcc4 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/NumberKeyframeAnimation.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/NumberKeyframeAnimation.java @@ -12,46 +12,46 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class NumberKeyframeAnimation extends KeyframeAnimation { - private final List values; - private final Class klass; - - public NumberKeyframeAnimation(long duration, LottieComposition composition, List keyTimes, Class klass, List values, List interpolators) { - super(duration, composition, keyTimes, interpolators); - this.klass = klass; - if (keyTimes.size() != values.size()) { - throw new IllegalArgumentException("Key times and values must be the same length " + keyTimes + " vs " + values); - } - this.values = values; + private final List values; + private final Class klass; + + public NumberKeyframeAnimation(long duration, LottieComposition composition, List keyTimes, Class klass, List values, List interpolators) { + super(duration, composition, keyTimes, interpolators); + this.klass = klass; + if (keyTimes.size() != values.size()) { + throw new IllegalArgumentException("Key times and values must be the same length " + keyTimes + " vs " + values); } + this.values = values; + } + + @Override + public T getValue() { + if (progress <= 0f) { + return values.get(0); + } else if (progress >= 1f) { + return values.get(values.size() - 1); + } + + int keyframeIndex = getKeyframeIndex(); + + float startKeytime = keyTimes.get(keyframeIndex); + float endKeytime = keyTimes.get(keyframeIndex + 1); + + float percentageIntoFrame = 0; + if (!isDiscrete) { + percentageIntoFrame = (progress - startKeytime) / (endKeytime - startKeytime); + if (interpolators != null) { + percentageIntoFrame = interpolators.get(keyframeIndex).getInterpolation(percentageIntoFrame); + } + } + + Number startValue = values.get(keyframeIndex); + Number endValue = values.get(keyframeIndex + 1); - @Override - public T getValue() { - if (progress <= 0f) { - return values.get(0); - } else if (progress >= 1f) { - return values.get(values.size() - 1); - } - - int keyframeIndex = getKeyframeIndex(); - - float startKeytime = keyTimes.get(keyframeIndex); - float endKeytime = keyTimes.get(keyframeIndex + 1); - - float percentageIntoFrame = 0; - if (!isDiscrete) { - percentageIntoFrame = (progress - startKeytime) / (endKeytime - startKeytime); - if (interpolators != null) { - percentageIntoFrame = interpolators.get(keyframeIndex).getInterpolation(percentageIntoFrame); - } - } - - Number startValue = values.get(keyframeIndex); - Number endValue = values.get(keyframeIndex + 1); - - if (klass.isAssignableFrom(Integer.class)) { - return klass.cast(lerp(startValue.intValue(), endValue.intValue(), percentageIntoFrame)); - } else { - return klass.cast(lerp(startValue.floatValue(), endValue.floatValue(), percentageIntoFrame)); - } + if (klass.isAssignableFrom(Integer.class)) { + return klass.cast(lerp(startValue.intValue(), endValue.intValue(), percentageIntoFrame)); + } else { + return klass.cast(lerp(startValue.floatValue(), endValue.floatValue(), percentageIntoFrame)); } + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/PathKeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/animation/PathKeyframeAnimation.java index 8712ddf7aa..4ec7f148f3 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/PathKeyframeAnimation.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/PathKeyframeAnimation.java @@ -13,56 +13,56 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class PathKeyframeAnimation extends KeyframeAnimation { - private final PointF point = new PointF(); - private final float[] pos = new float[2]; - private final SegmentedPath segmentedPath; - private int pathMeasureKeyframeIndex = -1; - @Nullable private PathMeasure pathMeasure; + private final PointF point = new PointF(); + private final float[] pos = new float[2]; + private final SegmentedPath segmentedPath; + private int pathMeasureKeyframeIndex = -1; + @Nullable private PathMeasure pathMeasure; - public PathKeyframeAnimation(long duration, LottieComposition composition, List keyTimes, SegmentedPath segmentedPath, List interpolators) { - super(duration, composition, keyTimes, interpolators); - this.segmentedPath = segmentedPath; - } - - @Override - public PointF getValue() { - if (progress <= 0f) { - if (pathMeasureKeyframeIndex != 0 || pathMeasure == null) { - pathMeasureKeyframeIndex = 0; - pathMeasure = new PathMeasure(segmentedPath.getSegment(0), false); - } - pathMeasure.getPosTan(0, pos, null); - point.set(pos[0], pos[1]); - return point; - } else if (progress >= 1f) { - if (pathMeasureKeyframeIndex != segmentedPath.getSegmentCount() - 1 || pathMeasure == null) { - pathMeasureKeyframeIndex = segmentedPath.getSegmentCount() - 1; - pathMeasure = new PathMeasure(segmentedPath.getSegment(segmentedPath.getSegmentCount() - 1), false); - } - pathMeasure.getPosTan(pathMeasure.getLength(), pos, null); - point.set(pos[0], pos[1]); - return point; - } + public PathKeyframeAnimation(long duration, LottieComposition composition, List keyTimes, SegmentedPath segmentedPath, List interpolators) { + super(duration, composition, keyTimes, interpolators); + this.segmentedPath = segmentedPath; + } - int keyframeIndex = getKeyframeIndex(); - if (pathMeasureKeyframeIndex != keyframeIndex) { - pathMeasureKeyframeIndex = keyframeIndex; - pathMeasure = new PathMeasure(segmentedPath.getSegment(keyframeIndex), false); - } + @Override + public PointF getValue() { + if (progress <= 0f) { + if (pathMeasureKeyframeIndex != 0 || pathMeasure == null) { + pathMeasureKeyframeIndex = 0; + pathMeasure = new PathMeasure(segmentedPath.getSegment(0), false); + } + pathMeasure.getPosTan(0, pos, null); + point.set(pos[0], pos[1]); + return point; + } else if (progress >= 1f) { + if (pathMeasureKeyframeIndex != segmentedPath.getSegmentCount() - 1 || pathMeasure == null) { + pathMeasureKeyframeIndex = segmentedPath.getSegmentCount() - 1; + pathMeasure = new PathMeasure(segmentedPath.getSegment(segmentedPath.getSegmentCount() - 1), false); + } + pathMeasure.getPosTan(pathMeasure.getLength(), pos, null); + point.set(pos[0], pos[1]); + return point; + } - float startKeytime = keyTimes.get(keyframeIndex); - float endKeytime = keyTimes.get(keyframeIndex + 1); + int keyframeIndex = getKeyframeIndex(); + if (pathMeasureKeyframeIndex != keyframeIndex) { + pathMeasureKeyframeIndex = keyframeIndex; + pathMeasure = new PathMeasure(segmentedPath.getSegment(keyframeIndex), false); + } - float percentageIntoFrame = 0; - if (!isDiscrete) { - percentageIntoFrame = (progress - startKeytime) / (endKeytime - startKeytime); - if (interpolators != null) { - percentageIntoFrame = interpolators.get(keyframeIndex).getInterpolation(percentageIntoFrame); - } - } + float startKeytime = keyTimes.get(keyframeIndex); + float endKeytime = keyTimes.get(keyframeIndex + 1); - pathMeasure.getPosTan(percentageIntoFrame * pathMeasure.getLength(), pos, null); - point.set(pos[0], pos[1]); - return point; + float percentageIntoFrame = 0; + if (!isDiscrete) { + percentageIntoFrame = (progress - startKeytime) / (endKeytime - startKeytime); + if (interpolators != null) { + percentageIntoFrame = interpolators.get(keyframeIndex).getInterpolation(percentageIntoFrame); + } } + + pathMeasure.getPosTan(percentageIntoFrame * pathMeasure.getLength(), pos, null); + point.set(pos[0], pos[1]); + return point; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/PointKeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/animation/PointKeyframeAnimation.java index 8cff934851..0dcafa7b68 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/PointKeyframeAnimation.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/PointKeyframeAnimation.java @@ -10,40 +10,40 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class PointKeyframeAnimation extends KeyframeAnimation { - private final PointF point = new PointF(); - private final List points; - - public PointKeyframeAnimation(long duration, LottieComposition composition, List keyTimes, List points, List interpolators) { - super(duration, composition, keyTimes, interpolators); - this.points = points; + private final PointF point = new PointF(); + private final List points; + + public PointKeyframeAnimation(long duration, LottieComposition composition, List keyTimes, List points, List interpolators) { + super(duration, composition, keyTimes, interpolators); + this.points = points; + } + + @Override + public PointF getValue() { + if (progress <= 0f) { + return points.get(0); + } else if (progress > 1f) { + return points.get(points.size() - 1); } - @Override - public PointF getValue() { - if (progress <= 0f) { - return points.get(0); - } else if (progress > 1f) { - return points.get(points.size() - 1); - } - - int keyframeIndex = getKeyframeIndex(); - - float startKeytime = keyTimes.get(keyframeIndex); - float endKeytime = keyTimes.get(keyframeIndex + 1); - - float percentageIntoFrame = 0; - if (!isDiscrete) { - percentageIntoFrame = (progress - startKeytime) / (endKeytime - startKeytime); - if (interpolators != null) { - percentageIntoFrame = interpolators.get(keyframeIndex).getInterpolation(percentageIntoFrame); - } - } - - PointF startPoint = points.get(keyframeIndex); - PointF endPoint = points.get(keyframeIndex + 1); - - point.set(startPoint.x + percentageIntoFrame * (endPoint.x - startPoint.x), - startPoint.y + percentageIntoFrame * (endPoint.y - startPoint.y)); - return point; + int keyframeIndex = getKeyframeIndex(); + + float startKeytime = keyTimes.get(keyframeIndex); + float endKeytime = keyTimes.get(keyframeIndex + 1); + + float percentageIntoFrame = 0; + if (!isDiscrete) { + percentageIntoFrame = (progress - startKeytime) / (endKeytime - startKeytime); + if (interpolators != null) { + percentageIntoFrame = interpolators.get(keyframeIndex).getInterpolation(percentageIntoFrame); + } } + + PointF startPoint = points.get(keyframeIndex); + PointF endPoint = points.get(keyframeIndex + 1); + + point.set(startPoint.x + percentageIntoFrame * (endPoint.x - startPoint.x), + startPoint.y + percentageIntoFrame * (endPoint.y - startPoint.y)); + return point; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/ScaleKeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/animation/ScaleKeyframeAnimation.java index e57c20cf1c..5c686cac91 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/ScaleKeyframeAnimation.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/ScaleKeyframeAnimation.java @@ -12,42 +12,42 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ScaleKeyframeAnimation extends KeyframeAnimation { - private final ScaleXY outTransform = new ScaleXY(); + private final ScaleXY outTransform = new ScaleXY(); - private final List transforms; + private final List transforms; - public ScaleKeyframeAnimation(long duration, LottieComposition composition, List keyTimes, List transforms, List interpolators) { - super(duration, composition, keyTimes, interpolators); - this.transforms = transforms; + public ScaleKeyframeAnimation(long duration, LottieComposition composition, List keyTimes, List transforms, List interpolators) { + super(duration, composition, keyTimes, interpolators); + this.transforms = transforms; + } + + @Override + public ScaleXY getValue() { + if (progress <= 0f) { + return transforms.get(0); + } else if (progress >= 1f) { + return transforms.get(transforms.size() - 1); } - @Override - public ScaleXY getValue() { - if (progress <= 0f) { - return transforms.get(0); - } else if (progress >= 1f) { - return transforms.get(transforms.size() - 1); - } - - int keyframeIndex = getKeyframeIndex(); - - float startKeytime = keyTimes.get(keyframeIndex); - float endKeytime = keyTimes.get(keyframeIndex + 1); - - float percentageIntoFrame = 0; - if (!isDiscrete) { - percentageIntoFrame = (progress - startKeytime) / (endKeytime - startKeytime); - if (interpolators != null) { - percentageIntoFrame = interpolators.get(keyframeIndex).getInterpolation(percentageIntoFrame); - } - } - - ScaleXY startTransform = transforms.get(keyframeIndex); - ScaleXY endTransform = transforms.get(keyframeIndex + 1); - - outTransform.scale( - lerp(startTransform.getScaleX(), endTransform.getScaleX(), percentageIntoFrame), - lerp(startTransform.getScaleY(), endTransform.getScaleY(), percentageIntoFrame)); - return outTransform; + int keyframeIndex = getKeyframeIndex(); + + float startKeytime = keyTimes.get(keyframeIndex); + float endKeytime = keyTimes.get(keyframeIndex + 1); + + float percentageIntoFrame = 0; + if (!isDiscrete) { + percentageIntoFrame = (progress - startKeytime) / (endKeytime - startKeytime); + if (interpolators != null) { + percentageIntoFrame = interpolators.get(keyframeIndex).getInterpolation(percentageIntoFrame); + } } + + ScaleXY startTransform = transforms.get(keyframeIndex); + ScaleXY endTransform = transforms.get(keyframeIndex + 1); + + outTransform.scale( + lerp(startTransform.getScaleX(), endTransform.getScaleX(), percentageIntoFrame), + lerp(startTransform.getScaleY(), endTransform.getScaleY(), percentageIntoFrame)); + return outTransform; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/ShapeKeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/animation/ShapeKeyframeAnimation.java index 10ead2bd46..9e9b21b894 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/ShapeKeyframeAnimation.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/ShapeKeyframeAnimation.java @@ -12,43 +12,43 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ShapeKeyframeAnimation extends KeyframeAnimation { - private final Path tempPath = new Path(); - private final ShapeData tempShapeData = new ShapeData(); - private final List shapeData; - - public ShapeKeyframeAnimation(long duration, LottieComposition composition, List keyTimes, List shapeData, List interpolators) { - super(duration, composition, keyTimes, interpolators); - this.shapeData = shapeData; + private final Path tempPath = new Path(); + private final ShapeData tempShapeData = new ShapeData(); + private final List shapeData; + + public ShapeKeyframeAnimation(long duration, LottieComposition composition, List keyTimes, List shapeData, List interpolators) { + super(duration, composition, keyTimes, interpolators); + this.shapeData = shapeData; + } + + @Override + public Path getValue() { + if (progress <= 0f) { + MiscUtils.getPathFromData(shapeData.get(0), tempPath); + return tempPath; + } else if (progress >= 1f) { + MiscUtils.getPathFromData(shapeData.get(shapeData.size() - 1), tempPath); + return tempPath; } - @Override - public Path getValue() { - if (progress <= 0f) { - MiscUtils.getPathFromData(shapeData.get(0), tempPath); - return tempPath; - } else if (progress >= 1f) { - MiscUtils.getPathFromData(shapeData.get(shapeData.size() - 1), tempPath); - return tempPath; - } - - int keyframeIndex = getKeyframeIndex(); - - float startKeytime = keyTimes.get(keyframeIndex); - float endKeytime = keyTimes.get(keyframeIndex + 1); - - float percentageIntoFrame = 0; - if (!isDiscrete) { - percentageIntoFrame = (progress - startKeytime) / (endKeytime - startKeytime); - if (interpolators != null) { - percentageIntoFrame = interpolators.get(keyframeIndex).getInterpolation(percentageIntoFrame); - } - } - - ShapeData startShapeData = shapeData.get(keyframeIndex); - ShapeData endShapeData = shapeData.get(keyframeIndex + 1); - - tempShapeData.interpolateBetween(startShapeData, endShapeData, percentageIntoFrame); - MiscUtils.getPathFromData(tempShapeData, tempPath); - return tempPath; + int keyframeIndex = getKeyframeIndex(); + + float startKeytime = keyTimes.get(keyframeIndex); + float endKeytime = keyTimes.get(keyframeIndex + 1); + + float percentageIntoFrame = 0; + if (!isDiscrete) { + percentageIntoFrame = (progress - startKeytime) / (endKeytime - startKeytime); + if (interpolators != null) { + percentageIntoFrame = interpolators.get(keyframeIndex).getInterpolation(percentageIntoFrame); + } } + + ShapeData startShapeData = shapeData.get(keyframeIndex); + ShapeData endShapeData = shapeData.get(keyframeIndex + 1); + + tempShapeData.interpolateBetween(startShapeData, endShapeData, percentageIntoFrame); + MiscUtils.getPathFromData(tempShapeData, tempPath); + return tempPath; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/StaticKeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/animation/StaticKeyframeAnimation.java index bfc4978cdb..da9752bc8f 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/StaticKeyframeAnimation.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/StaticKeyframeAnimation.java @@ -10,20 +10,20 @@ public class StaticKeyframeAnimation extends KeyframeAnimation { - private final T initialValue; - - public StaticKeyframeAnimation(T initialValue) { - super(0, null, Collections.emptyList(), Collections.emptyList()); - this.initialValue = initialValue; - } - - @Override - public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { - // Do nothing - } - - @Override - public T getValue() { - return initialValue; - } + private final T initialValue; + + public StaticKeyframeAnimation(T initialValue) { + super(0, null, Collections.emptyList(), Collections.emptyList()); + this.initialValue = initialValue; + } + + @Override + public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { + // Do nothing + } + + @Override + public T getValue() { + return initialValue; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/layers/AnimatableLayer.java b/lottie/src/main/java/com/airbnb/lottie/layers/AnimatableLayer.java index beb4b5b9d6..634e6a82ff 100644 --- a/lottie/src/main/java/com/airbnb/lottie/layers/AnimatableLayer.java +++ b/lottie/src/main/java/com/airbnb/lottie/layers/AnimatableLayer.java @@ -22,243 +22,245 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class AnimatableLayer extends Drawable { - private final KeyframeAnimation.AnimationListener integerChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Integer progress) { - invalidateSelf(); - } - }; - private final KeyframeAnimation.AnimationListener floatChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Float progress) { - invalidateSelf(); - } - }; - private final KeyframeAnimation.AnimationListener scaleChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(ScaleXY progress) { - invalidateSelf(); - } - }; - private final KeyframeAnimation.AnimationListener pointChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(PointF progress) { - invalidateSelf(); - } - }; - - final List layers = new ArrayList<>(); - @Nullable private AnimatableLayer parentLayer; - - private KeyframeAnimation position; - private KeyframeAnimation anchorPoint; - /** This should mimic CALayer#transform */ - private KeyframeAnimation transform; - private KeyframeAnimation alpha = null; - private KeyframeAnimation rotation; - - private final Paint solidBackgroundPaint = new Paint(); - @ColorInt private int backgroundColor; - private final List> animations = new ArrayList<>(); - @FloatRange(from = 0f, to = 1f) private float progress; - - AnimatableLayer(Drawable.Callback callback) { - setCallback(callback); - - solidBackgroundPaint.setAlpha(0); - solidBackgroundPaint.setStyle(Paint.Style.FILL); + private final KeyframeAnimation.AnimationListener integerChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Integer progress) { + invalidateSelf(); } - - void setBackgroundColor(@ColorInt int color) { - this.backgroundColor = color; - solidBackgroundPaint.setColor(color); - invalidateSelf(); + }; + private final KeyframeAnimation.AnimationListener floatChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Float progress) { + invalidateSelf(); } - - void addAnimation(KeyframeAnimation newAnimation) { - animations.add(newAnimation); + }; + private final KeyframeAnimation.AnimationListener scaleChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(ScaleXY progress) { + invalidateSelf(); } - - void removeAnimation(KeyframeAnimation animation) { - animations.remove(animation); + }; + private final KeyframeAnimation.AnimationListener pointChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(PointF progress) { + invalidateSelf(); } + }; + + final List layers = new ArrayList<>(); + @Nullable private AnimatableLayer parentLayer; + + private KeyframeAnimation position; + private KeyframeAnimation anchorPoint; + /** + * This should mimic CALayer#transform + */ + private KeyframeAnimation transform; + private KeyframeAnimation alpha = null; + private KeyframeAnimation rotation; + + private final Paint solidBackgroundPaint = new Paint(); + @ColorInt private int backgroundColor; + private final List> animations = new ArrayList<>(); + @FloatRange(from = 0f, to = 1f) private float progress; + + AnimatableLayer(Drawable.Callback callback) { + setCallback(callback); + + solidBackgroundPaint.setAlpha(0); + solidBackgroundPaint.setStyle(Paint.Style.FILL); + } + + void setBackgroundColor(@ColorInt int color) { + this.backgroundColor = color; + solidBackgroundPaint.setColor(color); + invalidateSelf(); + } + + void addAnimation(KeyframeAnimation newAnimation) { + animations.add(newAnimation); + } + + void removeAnimation(KeyframeAnimation animation) { + animations.remove(animation); + } + + @Override + public void draw(@NonNull Canvas canvas) { + int saveCount = canvas.save(); + applyTransformForLayer(canvas, this); + + int backgroundAlpha = Color.alpha(backgroundColor); + if (backgroundAlpha != 0) { + int alpha = backgroundAlpha; + if (this.alpha != null) { + alpha = alpha * this.alpha.getValue() / 255; + } + solidBackgroundPaint.setAlpha(alpha); + if (alpha > 0) { + canvas.drawRect(getBounds(), solidBackgroundPaint); + } + } + for (int i = 0; i < layers.size(); i++) { + layers.get(i).draw(canvas); + } + canvas.restoreToCount(saveCount); + } - @Override - public void draw(@NonNull Canvas canvas) { - int saveCount = canvas.save(); - applyTransformForLayer(canvas, this); - - int backgroundAlpha = Color.alpha(backgroundColor); - if (backgroundAlpha != 0) { - int alpha = backgroundAlpha; - if (this.alpha != null) { - alpha = alpha * this.alpha.getValue() / 255; - } - solidBackgroundPaint.setAlpha(alpha); - if (alpha > 0) { - canvas.drawRect(getBounds(), solidBackgroundPaint); - } - } - for (int i = 0; i < layers.size(); i++) { - layers.get(i).draw(canvas); - } - canvas.restoreToCount(saveCount); + @Override + public void invalidateSelf() { + if (parentLayer != null) { + parentLayer.invalidateSelf(); } + } - @Override - public void invalidateSelf() { - if (parentLayer != null) { - parentLayer.invalidateSelf(); - } + int saveCanvas(@Nullable Canvas canvas) { + if (canvas == null) { + return 0; } + return canvas.save(); + } - int saveCanvas(@Nullable Canvas canvas) { - if (canvas == null) { - return 0; - } - return canvas.save(); + void restoreCanvas(@Nullable Canvas canvas, int count) { + if (canvas == null) { + return; } + canvas.restoreToCount(count); + } - void restoreCanvas(@Nullable Canvas canvas, int count) { - if (canvas == null) { - return; - } - canvas.restoreToCount(count); + void applyTransformForLayer(@Nullable Canvas canvas, AnimatableLayer layer) { + if (canvas == null) { + return; + } + // TODO: Determine if these null checks are necessary. + if (layer.position != null) { + PointF position = layer.position.getValue(); + if (position.x != 0 || position.y != 0) { + canvas.translate(position.x, position.y); + } } - void applyTransformForLayer(@Nullable Canvas canvas, AnimatableLayer layer) { - if (canvas == null) { - return; - } - // TODO: Determine if these null checks are necessary. - if (layer.position != null) { - PointF position = layer.position.getValue(); - if (position.x != 0 || position.y != 0) { - canvas.translate(position.x, position.y); - } - } - - if (layer.rotation != null) { - float rotation = layer.rotation.getValue(); - if (rotation != 0f) { - canvas.rotate(rotation); - } - } - - if (layer.transform != null) { - ScaleXY scale = layer.transform.getValue(); - if (scale.getScaleX() != 1f || scale.getScaleY() != 1f) { - canvas.scale(scale.getScaleX(), scale.getScaleY()); - } - } - - if (layer.anchorPoint != null) { - PointF anchorPoint = layer.anchorPoint.getValue(); - if (anchorPoint.x != 0 || anchorPoint.y != 0) { - canvas.translate(-anchorPoint.x, -anchorPoint.y); - } - } + if (layer.rotation != null) { + float rotation = layer.rotation.getValue(); + if (rotation != 0f) { + canvas.rotate(rotation); + } } + if (layer.transform != null) { + ScaleXY scale = layer.transform.getValue(); + if (scale.getScaleX() != 1f || scale.getScaleY() != 1f) { + canvas.scale(scale.getScaleX(), scale.getScaleY()); + } + } - @Override - public void setAlpha(int alpha) { - throw new IllegalArgumentException("This shouldn't be used."); + if (layer.anchorPoint != null) { + PointF anchorPoint = layer.anchorPoint.getValue(); + if (anchorPoint.x != 0 || anchorPoint.y != 0) { + canvas.translate(-anchorPoint.x, -anchorPoint.y); + } } + } - void setAlpha(KeyframeAnimation alpha) { - if (this.alpha != null) { - removeAnimation(this.alpha); - this.alpha.removeUpdateListener(integerChangedListener); - } - this.alpha = alpha; - addAnimation(alpha); - alpha.addUpdateListener(integerChangedListener); - invalidateSelf(); - } + @Override + public void setAlpha(int alpha) { + throw new IllegalArgumentException("This shouldn't be used."); + } - @Override - public int getAlpha() { - float alpha = this.alpha == null ? 1f : (this.alpha.getValue() / 255f); - float parentAlpha = parentLayer == null ? 1f : (parentLayer.getAlpha() / 255f); - return (int) (alpha * parentAlpha * 255); + void setAlpha(KeyframeAnimation alpha) { + if (this.alpha != null) { + removeAnimation(this.alpha); + this.alpha.removeUpdateListener(integerChangedListener); } + this.alpha = alpha; + addAnimation(alpha); + alpha.addUpdateListener(integerChangedListener); - @Override - public void setColorFilter(ColorFilter colorFilter) { + invalidateSelf(); + } - } + @Override + public int getAlpha() { + float alpha = this.alpha == null ? 1f : (this.alpha.getValue() / 255f); + float parentAlpha = parentLayer == null ? 1f : (parentLayer.getAlpha() / 255f); + return (int) (alpha * parentAlpha * 255); + } - void setAnchorPoint(KeyframeAnimation anchorPoint) { - if (this.anchorPoint != null) { - removeAnimation(this.anchorPoint); - this.anchorPoint.removeUpdateListener(pointChangedListener); - } - this.anchorPoint = anchorPoint; - addAnimation(anchorPoint); - anchorPoint.addUpdateListener(pointChangedListener); - } + @Override + public void setColorFilter(ColorFilter colorFilter) { - void setPosition(KeyframeAnimation position) { - if (this.position != null) { - removeAnimation(this.position); - this.position.removeUpdateListener(pointChangedListener); - } - this.position = position; - addAnimation(position); - position.addUpdateListener(pointChangedListener); - } + } - void setTransform(KeyframeAnimation transform) { - if (this.transform != null) { - removeAnimation(this.transform); - this.transform.removeUpdateListener(scaleChangedListener); - } - this.transform = transform; - addAnimation(this.transform); - transform.addUpdateListener(scaleChangedListener); + void setAnchorPoint(KeyframeAnimation anchorPoint) { + if (this.anchorPoint != null) { + removeAnimation(this.anchorPoint); + this.anchorPoint.removeUpdateListener(pointChangedListener); } - - void setRotation(KeyframeAnimation rotation) { - if (this.rotation != null) { - removeAnimation(this.rotation); - this.rotation.removeUpdateListener(floatChangedListener); - } - this.rotation = rotation; - addAnimation(this.rotation); - rotation.addUpdateListener(floatChangedListener); + this.anchorPoint = anchorPoint; + addAnimation(anchorPoint); + anchorPoint.addUpdateListener(pointChangedListener); + } + + void setPosition(KeyframeAnimation position) { + if (this.position != null) { + removeAnimation(this.position); + this.position.removeUpdateListener(pointChangedListener); } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; + this.position = position; + addAnimation(position); + position.addUpdateListener(pointChangedListener); + } + + void setTransform(KeyframeAnimation transform) { + if (this.transform != null) { + removeAnimation(this.transform); + this.transform.removeUpdateListener(scaleChangedListener); } - - void addLayer(AnimatableLayer layer) { - layer.parentLayer = this; - layers.add(layer); - layer.setProgress(progress); - invalidateSelf(); + this.transform = transform; + addAnimation(this.transform); + transform.addUpdateListener(scaleChangedListener); + } + + void setRotation(KeyframeAnimation rotation) { + if (this.rotation != null) { + removeAnimation(this.rotation); + this.rotation.removeUpdateListener(floatChangedListener); } - - void clearLayers() { - layers.clear(); - invalidateSelf(); + this.rotation = rotation; + addAnimation(this.rotation); + rotation.addUpdateListener(floatChangedListener); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + void addLayer(AnimatableLayer layer) { + layer.parentLayer = this; + layers.add(layer); + layer.setProgress(progress); + invalidateSelf(); + } + + void clearLayers() { + layers.clear(); + invalidateSelf(); + } + + public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { + this.progress = progress; + for (int i = 0; i < animations.size(); i++) { + animations.get(i).setProgress(progress); } - public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { - this.progress = progress; - for (int i = 0; i < animations.size(); i++) { - animations.get(i).setProgress(progress); - } - - for (int i = 0; i < layers.size(); i++) { - layers.get(i).setProgress(progress); - } + for (int i = 0; i < layers.size(); i++) { + layers.get(i).setProgress(progress); } + } - public float getProgress() { - return progress; - } + public float getProgress() { + return progress; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/layers/EllipseShapeLayer.java b/lottie/src/main/java/com/airbnb/lottie/layers/EllipseShapeLayer.java index cad1887201..ce2dfa8528 100644 --- a/lottie/src/main/java/com/airbnb/lottie/layers/EllipseShapeLayer.java +++ b/lottie/src/main/java/com/airbnb/lottie/layers/EllipseShapeLayer.java @@ -18,117 +18,117 @@ class EllipseShapeLayer extends AnimatableLayer { - EllipseShapeLayer(CircleShape circleShape, ShapeFill fill, ShapeStroke stroke, - ShapeTrimPath trim, Transform transform, Drawable.Callback callback) { - super(callback); - - setBounds(transform.getBounds()); - setAnchorPoint(transform.getAnchor().createAnimation()); - setAlpha(transform.getOpacity().createAnimation()); - setPosition(transform.getPosition().createAnimation()); - setTransform(transform.getScale().createAnimation()); - setRotation(transform.getRotation().createAnimation()); - - if (fill != null) { - CircleShapeLayer fillLayer = new CircleShapeLayer(getCallback()); - fillLayer.setColor(fill.getColor().createAnimation()); - fillLayer.setAlpha(fill.getOpacity().createAnimation()); - fillLayer.updateCircle( - circleShape.getPosition().createAnimation(), - circleShape.getSize().createAnimation()); - addLayer(fillLayer); - } + EllipseShapeLayer(CircleShape circleShape, ShapeFill fill, ShapeStroke stroke, + ShapeTrimPath trim, Transform transform, Drawable.Callback callback) { + super(callback); + + setBounds(transform.getBounds()); + setAnchorPoint(transform.getAnchor().createAnimation()); + setAlpha(transform.getOpacity().createAnimation()); + setPosition(transform.getPosition().createAnimation()); + setTransform(transform.getScale().createAnimation()); + setRotation(transform.getRotation().createAnimation()); + + if (fill != null) { + CircleShapeLayer fillLayer = new CircleShapeLayer(getCallback()); + fillLayer.setColor(fill.getColor().createAnimation()); + fillLayer.setAlpha(fill.getOpacity().createAnimation()); + fillLayer.updateCircle( + circleShape.getPosition().createAnimation(), + circleShape.getSize().createAnimation()); + addLayer(fillLayer); + } - if (stroke != null) { - CircleShapeLayer strokeLayer = new CircleShapeLayer(getCallback()); - strokeLayer.setIsStroke(); - strokeLayer.setColor(stroke.getColor().createAnimation()); - strokeLayer.setAlpha(stroke.getOpacity().createAnimation()); - strokeLayer.setLineWidth(stroke.getWidth().createAnimation()); - if (!stroke.getLineDashPattern().isEmpty()) { - List> dashPatternAnimations = new ArrayList<>(stroke.getLineDashPattern().size()); - for (AnimatableFloatValue dashPattern : stroke.getLineDashPattern()) { - dashPatternAnimations.add(dashPattern.createAnimation()); - } - strokeLayer.setDashPattern(dashPatternAnimations, stroke.getDashOffset().createAnimation()); - } - strokeLayer.setLineCapType(stroke.getCapType()); - strokeLayer.updateCircle( - circleShape.getPosition().createAnimation(), - circleShape.getSize().createAnimation()); - if (trim != null) { - strokeLayer.setTrimPath(trim.getStart().createAnimation(), trim.getEnd().createAnimation(), trim.getOffset().createAnimation()); - } - - addLayer(strokeLayer); + if (stroke != null) { + CircleShapeLayer strokeLayer = new CircleShapeLayer(getCallback()); + strokeLayer.setIsStroke(); + strokeLayer.setColor(stroke.getColor().createAnimation()); + strokeLayer.setAlpha(stroke.getOpacity().createAnimation()); + strokeLayer.setLineWidth(stroke.getWidth().createAnimation()); + if (!stroke.getLineDashPattern().isEmpty()) { + List> dashPatternAnimations = new ArrayList<>(stroke.getLineDashPattern().size()); + for (AnimatableFloatValue dashPattern : stroke.getLineDashPattern()) { + dashPatternAnimations.add(dashPattern.createAnimation()); } + strokeLayer.setDashPattern(dashPatternAnimations, stroke.getDashOffset().createAnimation()); + } + strokeLayer.setLineCapType(stroke.getCapType()); + strokeLayer.updateCircle( + circleShape.getPosition().createAnimation(), + circleShape.getSize().createAnimation()); + if (trim != null) { + strokeLayer.setTrimPath(trim.getStart().createAnimation(), trim.getEnd().createAnimation(), trim.getOffset().createAnimation()); + } + + addLayer(strokeLayer); } + } - private static final class CircleShapeLayer extends ShapeLayer { - private static final float ELLIPSE_CONTROL_POINT_PERCENTAGE = 0.55228f; + private static final class CircleShapeLayer extends ShapeLayer { + private static final float ELLIPSE_CONTROL_POINT_PERCENTAGE = 0.55228f; - private final KeyframeAnimation.AnimationListener circleSizeChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(PointF progress) { - onCircleSizeChanged(); - } - }; + private final KeyframeAnimation.AnimationListener circleSizeChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(PointF progress) { + onCircleSizeChanged(); + } + }; - private final KeyframeAnimation.AnimationListener circlePositionChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(PointF progress) { - invalidateSelf(); - } - }; + private final KeyframeAnimation.AnimationListener circlePositionChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(PointF progress) { + invalidateSelf(); + } + }; - private final Path path = new Path(); + private final Path path = new Path(); - private KeyframeAnimation circleSize; - private KeyframeAnimation circlePosition; + private KeyframeAnimation circleSize; + private KeyframeAnimation circlePosition; - CircleShapeLayer(Drawable.Callback callback) { - super(callback); - setPath(new StaticKeyframeAnimation<>(path)); - } + CircleShapeLayer(Drawable.Callback callback) { + super(callback); + setPath(new StaticKeyframeAnimation<>(path)); + } - void updateCircle(KeyframeAnimation circlePosition, KeyframeAnimation circleSize) { - if (this.circleSize != null) { - removeAnimation(this.circleSize); - this.circleSize.removeUpdateListener(circleSizeChangedListener); - } - if (this.circlePosition != null) { - removeAnimation(this.circlePosition); - this.circlePosition.removeUpdateListener(circlePositionChangedListener); - } - this.circleSize = circleSize; - this.circlePosition = circlePosition; - addAnimation(circleSize); - circleSize.addUpdateListener(circleSizeChangedListener); - addAnimation(circlePosition); - circlePosition.addUpdateListener(circlePositionChangedListener); - onCircleSizeChanged(); - } + void updateCircle(KeyframeAnimation circlePosition, KeyframeAnimation circleSize) { + if (this.circleSize != null) { + removeAnimation(this.circleSize); + this.circleSize.removeUpdateListener(circleSizeChangedListener); + } + if (this.circlePosition != null) { + removeAnimation(this.circlePosition); + this.circlePosition.removeUpdateListener(circlePositionChangedListener); + } + this.circleSize = circleSize; + this.circlePosition = circlePosition; + addAnimation(circleSize); + circleSize.addUpdateListener(circleSizeChangedListener); + addAnimation(circlePosition); + circlePosition.addUpdateListener(circlePositionChangedListener); + onCircleSizeChanged(); + } - private void onCircleSizeChanged() { - float halfWidth = circleSize.getValue().x / 2f; - float halfHeight = circleSize.getValue().y / 2f; - setBounds(0, 0, (int) halfWidth * 2, (int) halfHeight * 2); + private void onCircleSizeChanged() { + float halfWidth = circleSize.getValue().x / 2f; + float halfHeight = circleSize.getValue().y / 2f; + setBounds(0, 0, (int) halfWidth * 2, (int) halfHeight * 2); - float cpW = halfWidth * ELLIPSE_CONTROL_POINT_PERCENTAGE; - float cpH = halfHeight * ELLIPSE_CONTROL_POINT_PERCENTAGE; + float cpW = halfWidth * ELLIPSE_CONTROL_POINT_PERCENTAGE; + float cpH = halfHeight * ELLIPSE_CONTROL_POINT_PERCENTAGE; - path.reset(); - path.moveTo(0, -halfHeight); - path.cubicTo(0 + cpW, -halfHeight, halfWidth, 0 - cpH, halfWidth, 0); - path.cubicTo(halfWidth, 0 + cpH, 0 + cpW, halfHeight, 0, halfHeight); - path.cubicTo(0 - cpW, halfHeight, -halfWidth, 0 + cpH, -halfWidth, 0); - path.cubicTo(-halfWidth, 0 - cpH, 0 - cpW, -halfHeight, 0, -halfHeight); + path.reset(); + path.moveTo(0, -halfHeight); + path.cubicTo(0 + cpW, -halfHeight, halfWidth, 0 - cpH, halfWidth, 0); + path.cubicTo(halfWidth, 0 + cpH, 0 + cpW, halfHeight, 0, halfHeight); + path.cubicTo(0 - cpW, halfHeight, -halfWidth, 0 + cpH, -halfWidth, 0); + path.cubicTo(-halfWidth, 0 - cpH, 0 - cpW, -halfHeight, 0, -halfHeight); - path.offset(circlePosition.getValue().x, circlePosition.getValue().y); + path.offset(circlePosition.getValue().x, circlePosition.getValue().y); - onPathChanged(); + onPathChanged(); - invalidateSelf(); - } + invalidateSelf(); } + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/layers/GroupLayerView.java b/lottie/src/main/java/com/airbnb/lottie/layers/GroupLayerView.java index 30244c05fd..5bfc247041 100644 --- a/lottie/src/main/java/com/airbnb/lottie/layers/GroupLayerView.java +++ b/lottie/src/main/java/com/airbnb/lottie/layers/GroupLayerView.java @@ -19,65 +19,65 @@ class GroupLayerView extends AnimatableLayer { - private final ShapeGroup shapeGroup; - @Nullable private final Transform shapeTransform; + private final ShapeGroup shapeGroup; + @Nullable private final Transform shapeTransform; - GroupLayerView(ShapeGroup shapeGroup, @Nullable ShapeFill previousFill, - @Nullable ShapeStroke previousStroke, @Nullable ShapeTrimPath previousTrimPath, - @Nullable Transform previousTransform, Drawable.Callback callback) { - super(callback); - this.shapeGroup = shapeGroup; - shapeTransform = previousTransform; - setupShapeGroupWithFill(previousFill, previousStroke, previousTrimPath); - } + GroupLayerView(ShapeGroup shapeGroup, @Nullable ShapeFill previousFill, + @Nullable ShapeStroke previousStroke, @Nullable ShapeTrimPath previousTrimPath, + @Nullable Transform previousTransform, Drawable.Callback callback) { + super(callback); + this.shapeGroup = shapeGroup; + shapeTransform = previousTransform; + setupShapeGroupWithFill(previousFill, previousStroke, previousTrimPath); + } - private void setupShapeGroupWithFill(ShapeFill previousFill, - ShapeStroke previousStroke, ShapeTrimPath previousTrimPath) { - if (shapeTransform != null) { - setBounds(shapeTransform.getBounds()); - setAnchorPoint(shapeTransform.getAnchor().createAnimation()); - setPosition(shapeTransform.getPosition().createAnimation()); - setAlpha(shapeTransform.getOpacity().createAnimation()); - setTransform(shapeTransform.getScale().createAnimation()); - setRotation(shapeTransform.getRotation().createAnimation()); - } + private void setupShapeGroupWithFill(ShapeFill previousFill, + ShapeStroke previousStroke, ShapeTrimPath previousTrimPath) { + if (shapeTransform != null) { + setBounds(shapeTransform.getBounds()); + setAnchorPoint(shapeTransform.getAnchor().createAnimation()); + setPosition(shapeTransform.getPosition().createAnimation()); + setAlpha(shapeTransform.getOpacity().createAnimation()); + setTransform(shapeTransform.getScale().createAnimation()); + setRotation(shapeTransform.getRotation().createAnimation()); + } - List reversedItems = new ArrayList<>(shapeGroup.getItems()); - Collections.reverse(reversedItems); + List reversedItems = new ArrayList<>(shapeGroup.getItems()); + Collections.reverse(reversedItems); - ShapeFill currentFill = previousFill; - ShapeStroke currentStroke = previousStroke; - Transform currentTransform = null; - ShapeTrimPath currentTrim = previousTrimPath; + ShapeFill currentFill = previousFill; + ShapeStroke currentStroke = previousStroke; + Transform currentTransform = null; + ShapeTrimPath currentTrim = previousTrimPath; - for (int i = 0; i < reversedItems.size(); i++) { - Object item = reversedItems.get(i); - if (item instanceof ShapeTransform) { - currentTransform = (ShapeTransform) item; - } else if (item instanceof ShapeStroke) { - currentStroke = (ShapeStroke) item; - } else if (item instanceof ShapeFill) { - currentFill = (ShapeFill) item; - } else if (item instanceof ShapeTrimPath) { - currentTrim = (ShapeTrimPath) item; - } else if (item instanceof ShapePath) { - ShapePath shapePath = (ShapePath) item; - ShapeLayerView shapeLayer = new ShapeLayerView(shapePath, currentFill, currentStroke, currentTrim, currentTransform, getCallback()); - addLayer(shapeLayer); - } else if (item instanceof RectangleShape) { - RectangleShape shapeRect = (RectangleShape) item; - RectLayer shapeLayer = new RectLayer(shapeRect, currentFill, currentStroke, currentTransform, getCallback()); - addLayer(shapeLayer); - } else if (item instanceof CircleShape) { - CircleShape shapeCircle = (CircleShape) item; - EllipseShapeLayer shapeLayer = new EllipseShapeLayer(shapeCircle, currentFill, currentStroke, currentTrim, currentTransform, getCallback()); - addLayer(shapeLayer); - } else if (item instanceof ShapeGroup) { - ShapeGroup shapeGroup = (ShapeGroup) item; - GroupLayerView groupLayer = new GroupLayerView(shapeGroup, currentFill, currentStroke, currentTrim, currentTransform, getCallback()); - addLayer(groupLayer); - } + for (int i = 0; i < reversedItems.size(); i++) { + Object item = reversedItems.get(i); + if (item instanceof ShapeTransform) { + currentTransform = (ShapeTransform) item; + } else if (item instanceof ShapeStroke) { + currentStroke = (ShapeStroke) item; + } else if (item instanceof ShapeFill) { + currentFill = (ShapeFill) item; + } else if (item instanceof ShapeTrimPath) { + currentTrim = (ShapeTrimPath) item; + } else if (item instanceof ShapePath) { + ShapePath shapePath = (ShapePath) item; + ShapeLayerView shapeLayer = new ShapeLayerView(shapePath, currentFill, currentStroke, currentTrim, currentTransform, getCallback()); + addLayer(shapeLayer); + } else if (item instanceof RectangleShape) { + RectangleShape shapeRect = (RectangleShape) item; + RectLayer shapeLayer = new RectLayer(shapeRect, currentFill, currentStroke, currentTransform, getCallback()); + addLayer(shapeLayer); + } else if (item instanceof CircleShape) { + CircleShape shapeCircle = (CircleShape) item; + EllipseShapeLayer shapeLayer = new EllipseShapeLayer(shapeCircle, currentFill, currentStroke, currentTrim, currentTransform, getCallback()); + addLayer(shapeLayer); + } else if (item instanceof ShapeGroup) { + ShapeGroup shapeGroup = (ShapeGroup) item; + GroupLayerView groupLayer = new GroupLayerView(shapeGroup, currentFill, currentStroke, currentTrim, currentTransform, getCallback()); + addLayer(groupLayer); + } - } } + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/layers/LayerView.java b/lottie/src/main/java/com/airbnb/lottie/layers/LayerView.java index d4a440d306..0d50963eed 100644 --- a/lottie/src/main/java/com/airbnb/lottie/layers/LayerView.java +++ b/lottie/src/main/java/com/airbnb/lottie/layers/LayerView.java @@ -36,255 +36,255 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class LayerView extends AnimatableLayer { - private MaskLayer mask; - private LayerView matteLayer; - - private final List transformLayers = new ArrayList<>(); - private final Paint mainCanvasPaint = new Paint(); - @Nullable private final Bitmap contentBitmap; - @Nullable private final Bitmap maskBitmap; - @Nullable private final Bitmap matteBitmap; - @Nullable private Canvas contentCanvas; - @Nullable private Canvas maskCanvas; - @Nullable private Canvas matteCanvas; - private final Paint maskShapePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final Paint maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final Paint mattePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - - private final Layer layerModel; - private final LottieComposition composition; - - @Nullable private LayerView parentLayer; - - - LayerView(Layer layerModel, LottieComposition composition, Callback callback, @Nullable Bitmap mainBitmap, @Nullable Bitmap maskBitmap, @Nullable Bitmap matteBitmap) { - super(callback); - this.layerModel = layerModel; - this.composition = composition; - this.maskBitmap = maskBitmap; - this.matteBitmap = matteBitmap; - this.contentBitmap = mainBitmap; - mattePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - setBounds(composition.getBounds()); - if (contentBitmap != null) { - contentCanvas = new Canvas(contentBitmap); - if (maskBitmap != null) { - maskPaint.setShader(new BitmapShader(contentBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); - } - } - - setupForModel(); + private MaskLayer mask; + private LayerView matteLayer; + + private final List transformLayers = new ArrayList<>(); + private final Paint mainCanvasPaint = new Paint(); + @Nullable private final Bitmap contentBitmap; + @Nullable private final Bitmap maskBitmap; + @Nullable private final Bitmap matteBitmap; + @Nullable private Canvas contentCanvas; + @Nullable private Canvas maskCanvas; + @Nullable private Canvas matteCanvas; + private final Paint maskShapePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint mattePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private final Layer layerModel; + private final LottieComposition composition; + + @Nullable private LayerView parentLayer; + + + LayerView(Layer layerModel, LottieComposition composition, Callback callback, @Nullable Bitmap mainBitmap, @Nullable Bitmap maskBitmap, @Nullable Bitmap matteBitmap) { + super(callback); + this.layerModel = layerModel; + this.composition = composition; + this.maskBitmap = maskBitmap; + this.matteBitmap = matteBitmap; + this.contentBitmap = mainBitmap; + mattePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + setBounds(composition.getBounds()); + if (contentBitmap != null) { + contentCanvas = new Canvas(contentBitmap); + if (maskBitmap != null) { + maskPaint.setShader(new BitmapShader(contentBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); + } } - private void setupForModel() { - setBackgroundColor(layerModel.getSolidColor()); - setBounds(0, 0, layerModel.getSolidWidth(), layerModel.getSolidHeight()); - - setPosition(layerModel.getPosition().createAnimation()); - setAnchorPoint(layerModel.getAnchor().createAnimation()); - setTransform(layerModel.getScale().createAnimation()); - setRotation(layerModel.getRotation().createAnimation()); - setAlpha(layerModel.getOpacity().createAnimation()); - - setVisible(layerModel.hasInAnimation(), false); - - List reversedItems = new ArrayList<>(layerModel.getShapes()); - Collections.reverse(reversedItems); - Transform currentTransform = null; - ShapeTrimPath currentTrimPath = null; - ShapeFill currentFill = null; - ShapeStroke currentStroke = null; - - for (int i = 0; i < reversedItems.size(); i++) { - Object item = reversedItems.get(i); - if (item instanceof ShapeGroup) { - GroupLayerView groupLayer = new GroupLayerView((ShapeGroup) item, currentFill, - currentStroke, currentTrimPath, currentTransform, getCallback()); - addLayer(groupLayer); - } else if (item instanceof ShapeTransform) { - currentTransform = (ShapeTransform) item; - } else if (item instanceof ShapeFill) { - currentFill = (ShapeFill) item; - } else if (item instanceof ShapeTrimPath) { - currentTrimPath = (ShapeTrimPath) item; - } else if (item instanceof ShapeStroke) { - currentStroke = (ShapeStroke) item; - } else if (item instanceof ShapePath) { - ShapePath shapePath = (ShapePath) item; - ShapeLayerView shapeLayer = new ShapeLayerView(shapePath, currentFill, currentStroke, currentTrimPath, new ShapeTransform(composition), getCallback()); - addLayer(shapeLayer); - } else if (item instanceof RectangleShape) { - RectangleShape shapeRect = (RectangleShape) item; - RectLayer shapeLayer = new RectLayer(shapeRect, currentFill, currentStroke, new ShapeTransform(composition), getCallback()); - addLayer(shapeLayer); - } else if (item instanceof CircleShape) { - CircleShape shapeCircle = (CircleShape) item; - EllipseShapeLayer shapeLayer = new EllipseShapeLayer(shapeCircle, currentFill, currentStroke, currentTrimPath, new ShapeTransform(composition), getCallback()); - addLayer(shapeLayer); - } - } + setupForModel(); + } + + private void setupForModel() { + setBackgroundColor(layerModel.getSolidColor()); + setBounds(0, 0, layerModel.getSolidWidth(), layerModel.getSolidHeight()); + + setPosition(layerModel.getPosition().createAnimation()); + setAnchorPoint(layerModel.getAnchor().createAnimation()); + setTransform(layerModel.getScale().createAnimation()); + setRotation(layerModel.getRotation().createAnimation()); + setAlpha(layerModel.getOpacity().createAnimation()); + + setVisible(layerModel.hasInAnimation(), false); + + List reversedItems = new ArrayList<>(layerModel.getShapes()); + Collections.reverse(reversedItems); + Transform currentTransform = null; + ShapeTrimPath currentTrimPath = null; + ShapeFill currentFill = null; + ShapeStroke currentStroke = null; + + for (int i = 0; i < reversedItems.size(); i++) { + Object item = reversedItems.get(i); + if (item instanceof ShapeGroup) { + GroupLayerView groupLayer = new GroupLayerView((ShapeGroup) item, currentFill, + currentStroke, currentTrimPath, currentTransform, getCallback()); + addLayer(groupLayer); + } else if (item instanceof ShapeTransform) { + currentTransform = (ShapeTransform) item; + } else if (item instanceof ShapeFill) { + currentFill = (ShapeFill) item; + } else if (item instanceof ShapeTrimPath) { + currentTrimPath = (ShapeTrimPath) item; + } else if (item instanceof ShapeStroke) { + currentStroke = (ShapeStroke) item; + } else if (item instanceof ShapePath) { + ShapePath shapePath = (ShapePath) item; + ShapeLayerView shapeLayer = new ShapeLayerView(shapePath, currentFill, currentStroke, currentTrimPath, new ShapeTransform(composition), getCallback()); + addLayer(shapeLayer); + } else if (item instanceof RectangleShape) { + RectangleShape shapeRect = (RectangleShape) item; + RectLayer shapeLayer = new RectLayer(shapeRect, currentFill, currentStroke, new ShapeTransform(composition), getCallback()); + addLayer(shapeLayer); + } else if (item instanceof CircleShape) { + CircleShape shapeCircle = (CircleShape) item; + EllipseShapeLayer shapeLayer = new EllipseShapeLayer(shapeCircle, currentFill, currentStroke, currentTrimPath, new ShapeTransform(composition), getCallback()); + addLayer(shapeLayer); + } + } - if (maskBitmap != null && layerModel.getMasks() != null && !layerModel.getMasks().isEmpty()) { - setMask(new MaskLayer(layerModel.getMasks(), getCallback())); - maskCanvas = new Canvas(maskBitmap); + if (maskBitmap != null && layerModel.getMasks() != null && !layerModel.getMasks().isEmpty()) { + setMask(new MaskLayer(layerModel.getMasks(), getCallback())); + maskCanvas = new Canvas(maskBitmap); + } + buildAnimations(); + } + + private void buildAnimations() { + if (layerModel.hasInOutAnimation()) { + NumberKeyframeAnimation inOutAnimation = new NumberKeyframeAnimation<>( + layerModel.getComposition().getDuration(), + layerModel.getComposition(), + layerModel.getInOutKeyTimes(), + Float.class, + layerModel.getInOutKeyFrames(), + Collections.emptyList()); + inOutAnimation.setIsDiscrete(); + inOutAnimation.addUpdateListener(new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Float progress) { + setVisible(progress == 1f, false); } - buildAnimations(); + }); + setVisible(inOutAnimation.getValue() == 1f, false); + addAnimation(inOutAnimation); + } else { + setVisible(true, false); } - - private void buildAnimations() { - if (layerModel.hasInOutAnimation()) { - NumberKeyframeAnimation inOutAnimation = new NumberKeyframeAnimation<>( - layerModel.getComposition().getDuration(), - layerModel.getComposition(), - layerModel.getInOutKeyTimes(), - Float.class, - layerModel.getInOutKeyFrames(), - Collections.emptyList()); - inOutAnimation.setIsDiscrete(); - inOutAnimation.addUpdateListener(new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Float progress) { - setVisible(progress == 1f, false); - } - }); - setVisible(inOutAnimation.getValue() == 1f, false); - addAnimation(inOutAnimation); - } else { - setVisible(true, false); + } + + Layer getLayerModel() { + return layerModel; + } + + void setParentLayer(@Nullable LayerView parentLayer) { + this.parentLayer = parentLayer; + } + + @Nullable + private LayerView getParentLayer() { + return parentLayer; + } + + private void setMask(MaskLayer mask) { + this.mask = mask; + // TODO: make this a field like other animation listeners and remove existing ones. + for (KeyframeAnimation animation : mask.getMasks()) { + addAnimation(animation); + animation.addUpdateListener(new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Path progress) { + invalidateSelf(); } + }); } + } - Layer getLayerModel() { - return layerModel; + void setMatteLayer(LayerView matteLayer) { + if (matteBitmap == null) { + throw new IllegalArgumentException("Cannot set a matte if no matte bitmap was given!"); } - - void setParentLayer(@Nullable LayerView parentLayer) { - this.parentLayer = parentLayer; + this.matteLayer = matteLayer; + matteCanvas = new Canvas(matteBitmap); + } + + @Override + public void draw(@NonNull Canvas mainCanvas) { + if (contentBitmap != null) { + if (contentBitmap.isRecycled()) { + return; + } + contentBitmap.eraseColor(Color.TRANSPARENT); } - - @Nullable - private LayerView getParentLayer() { - return parentLayer; + if (maskBitmap != null) { + maskBitmap.eraseColor(Color.TRANSPARENT); } - - private void setMask(MaskLayer mask) { - this.mask = mask; - // TODO: make this a field like other animation listeners and remove existing ones. - for (KeyframeAnimation animation : mask.getMasks()) { - addAnimation(animation); - animation.addUpdateListener(new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Path progress) { - invalidateSelf(); - } - }); - } + if (matteBitmap != null) { + matteBitmap.eraseColor(Color.TRANSPARENT); } - - void setMatteLayer(LayerView matteLayer) { - if (matteBitmap == null) { - throw new IllegalArgumentException("Cannot set a matte if no matte bitmap was given!"); - } - this.matteLayer = matteLayer; - matteCanvas = new Canvas(matteBitmap); + if (!isVisible() || mainCanvasPaint.getAlpha() == 0) { + return; } - @Override - public void draw(@NonNull Canvas mainCanvas) { - if (contentBitmap != null) { - if (contentBitmap.isRecycled()) { - return; - } - contentBitmap.eraseColor(Color.TRANSPARENT); - } - if (maskBitmap != null) { - maskBitmap.eraseColor(Color.TRANSPARENT); - } - if (matteBitmap != null) { - matteBitmap.eraseColor(Color.TRANSPARENT); - } - if (!isVisible() || mainCanvasPaint.getAlpha() == 0) { - return; - } - - // Make a list of all parent layers. - transformLayers.clear(); - LayerView parent = parentLayer; - while (parent != null) { - transformLayers.add(parent); - parent = parent.getParentLayer(); - } - Collections.reverse(transformLayers); - - if (contentCanvas == null || contentBitmap == null) { - int mainCanvasCount = saveCanvas(mainCanvas); - // Now apply the parent transformations from the top down. - for (LayerView layer : transformLayers) { - applyTransformForLayer(mainCanvas, layer); - } - super.draw(mainCanvas); - mainCanvas.restoreToCount(mainCanvasCount); - return; - } + // Make a list of all parent layers. + transformLayers.clear(); + LayerView parent = parentLayer; + while (parent != null) { + transformLayers.add(parent); + parent = parent.getParentLayer(); + } + Collections.reverse(transformLayers); + + if (contentCanvas == null || contentBitmap == null) { + int mainCanvasCount = saveCanvas(mainCanvas); + // Now apply the parent transformations from the top down. + for (LayerView layer : transformLayers) { + applyTransformForLayer(mainCanvas, layer); + } + super.draw(mainCanvas); + mainCanvas.restoreToCount(mainCanvasCount); + return; + } - int contentCanvasCount = saveCanvas(contentCanvas); - int maskCanvasCount = saveCanvas(maskCanvas); - // Now apply the parent transformations from the top down. - for (LayerView layer : transformLayers) { - applyTransformForLayer(contentCanvas, layer); - applyTransformForLayer(maskCanvas, layer); - } - // We only have to apply the transformation to the mask because it's normally handed in AnimatableLayer#draw but masks don't go through that. - applyTransformForLayer(maskCanvas, this); - - super.draw(contentCanvas); - - Bitmap mainBitmap; - if (hasMasks()) { - for (int i = 0; i < mask.getMasks().size(); i++) { - Path path = mask.getMasks().get(i).getValue(); - //noinspection ConstantConditions - maskCanvas.drawPath(path, maskShapePaint); - } - if (!hasMattes()) { - mainCanvas.drawBitmap(maskBitmap, 0, 0, maskPaint); - } - mainBitmap = maskBitmap; - } else { - if (!hasMattes()) { - mainCanvas.drawBitmap(contentBitmap, 0, 0, mainCanvasPaint); - } - mainBitmap = contentBitmap; - } + int contentCanvasCount = saveCanvas(contentCanvas); + int maskCanvasCount = saveCanvas(maskCanvas); + // Now apply the parent transformations from the top down. + for (LayerView layer : transformLayers) { + applyTransformForLayer(contentCanvas, layer); + applyTransformForLayer(maskCanvas, layer); + } + // We only have to apply the transformation to the mask because it's normally handed in AnimatableLayer#draw but masks don't go through that. + applyTransformForLayer(maskCanvas, this); + + super.draw(contentCanvas); + + Bitmap mainBitmap; + if (hasMasks()) { + for (int i = 0; i < mask.getMasks().size(); i++) { + Path path = mask.getMasks().get(i).getValue(); + //noinspection ConstantConditions + maskCanvas.drawPath(path, maskShapePaint); + } + if (!hasMattes()) { + mainCanvas.drawBitmap(maskBitmap, 0, 0, maskPaint); + } + mainBitmap = maskBitmap; + } else { + if (!hasMattes()) { + mainCanvas.drawBitmap(contentBitmap, 0, 0, mainCanvasPaint); + } + mainBitmap = contentBitmap; + } - restoreCanvas(contentCanvas, contentCanvasCount); - restoreCanvas(maskCanvas, maskCanvasCount); + restoreCanvas(contentCanvas, contentCanvasCount); + restoreCanvas(maskCanvas, maskCanvasCount); - if (hasMattes()) { - //noinspection ConstantConditions - matteLayer.draw(matteCanvas); - matteCanvas.drawBitmap(mainBitmap, 0, 0, mattePaint); - mainCanvas.drawBitmap(matteBitmap, 0, 0, mainCanvasPaint); - } + if (hasMattes()) { + //noinspection ConstantConditions + matteLayer.draw(matteCanvas); + matteCanvas.drawBitmap(mainBitmap, 0, 0, mattePaint); + mainCanvas.drawBitmap(matteBitmap, 0, 0, mainCanvasPaint); } + } - private boolean hasMattes() { - return matteCanvas != null && matteBitmap != null && matteLayer != null; - } + private boolean hasMattes() { + return matteCanvas != null && matteBitmap != null && matteLayer != null; + } - private boolean hasMasks() { - return maskBitmap != null && maskCanvas != null && mask != null && !mask.getMasks().isEmpty(); - } + private boolean hasMasks() { + return maskBitmap != null && maskCanvas != null && mask != null && !mask.getMasks().isEmpty(); + } - @Override - public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { - super.setProgress(progress); - if (matteLayer != null) { - matteLayer.setProgress(progress); - } + @Override + public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { + super.setProgress(progress); + if (matteLayer != null) { + matteLayer.setProgress(progress); } + } - public long getId() { - return layerModel.getId(); - } + public long getId() { + return layerModel.getId(); + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/layers/LottieDrawable.java b/lottie/src/main/java/com/airbnb/lottie/layers/LottieDrawable.java index 7a4b1e8bd2..fe65a67538 100644 --- a/lottie/src/main/java/com/airbnb/lottie/layers/LottieDrawable.java +++ b/lottie/src/main/java/com/airbnb/lottie/layers/LottieDrawable.java @@ -21,224 +21,224 @@ /** * This can be used to show an lottie animation in any place that would normally take a drawable. * If there are masks or mattes, then you MUST call {@link #recycleBitmaps()} when you are done or else you will leak bitmaps. - * + *

* It is preferable to use {@link com.airbnb.lottie.LottieAnimationView} when possible because it handles bitmap recycling and asynchronous loading * of compositions. */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class LottieDrawable extends AnimatableLayer { - private LottieComposition composition; - - private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); - - @Nullable private Bitmap mainBitmap = null; - @Nullable private Bitmap maskBitmap = null; - @Nullable private Bitmap matteBitmap = null; - @Nullable private Bitmap mainBitmapForMatte = null; - @Nullable private Bitmap maskBitmapForMatte = null; - private boolean playAnimationWhenLayerAdded; - - public LottieDrawable() { - super(null); - - animator.setRepeatCount(0); - animator.setInterpolator(new LinearInterpolator()); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - setProgress(animation.getAnimatedFraction()); - } - }); - } - - public void setComposition(LottieComposition composition) { - if (getCallback() == null) { - throw new IllegalStateException("You or your view must set a Drawable.Callback before setting the composition. This gets done automatically when added to an ImageView. " + - "Either call ImageView.setImageDrawable() before setComposition() or call setCallback(yourView.getCallback()) first."); - } - clearComposition(); - this.composition = composition; - animator.setDuration(composition.getDuration()); - setBounds(0, 0, composition.getBounds().width(), composition.getBounds().height()); - buildLayersForComposition(composition); - - getCallback().invalidateDrawable(this); - } - - private void clearComposition() { - recycleBitmaps(); - clearLayers(); - } - - private void buildLayersForComposition(LottieComposition composition) { - if (composition == null) { - throw new IllegalStateException("Composition is null"); - } - Rect bounds = composition.getBounds(); - if (composition.hasMasks() || composition.hasMattes()) { - mainBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888); - } - if (composition.hasMasks()) { - maskBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8); - } - if (composition.hasMattes()) { - matteBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888); - } - LongSparseArray layerMap = new LongSparseArray<>(composition.getLayers().size()); - List layers = new ArrayList<>(composition.getLayers().size()); - LayerView maskedLayer = null; - for (int i = composition.getLayers().size() - 1; i >= 0; i--) { - Layer layer = composition.getLayers().get(i); - LayerView layerView; - if (maskedLayer == null) { - layerView = new LayerView(layer, composition, getCallback(), mainBitmap, maskBitmap, matteBitmap); - } else { - if (mainBitmapForMatte == null) { - mainBitmapForMatte = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8); - } - if (maskBitmapForMatte == null && !layer.getMasks().isEmpty()) { - maskBitmapForMatte = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8); - } - - layerView = new LayerView(layer, composition, getCallback(), mainBitmapForMatte, maskBitmapForMatte, null); - } - layerMap.put(layerView.getId(), layerView); - if (maskedLayer != null) { - maskedLayer.setMatteLayer(layerView); - maskedLayer = null; - } else { - layers.add(layerView); - if (layer.getMatteType() == Layer.MatteType.Add) { - maskedLayer = layerView; - } - } - } - - for (int i = 0; i < layers.size(); i++) { - LayerView layerView = layers.get(i); - addLayer(layerView); - } - - for (int i = 0; i < layerMap.size(); i++) { - long key = layerMap.keyAt(i); - LayerView layerView = layerMap.get(key); - LayerView parentLayer = layerMap.get(layerView.getLayerModel().getParentId()); - if (parentLayer != null) { - layerView.setParentLayer(parentLayer); - } - } - } - - @Override - public void invalidateSelf() { - final Callback callback = getCallback(); - if (callback != null) { - callback.invalidateDrawable(this); - } - } - - @Override - public void draw(@NonNull Canvas canvas) { - if (composition == null) { - return; - } - Rect bounds = getBounds(); - Rect compBounds = composition.getBounds(); - int saveCount = canvas.save(); - if (!bounds.equals(compBounds)) { - float scaleX = bounds.width() / (float) compBounds.width(); - float scaleY = bounds.height() / (float) compBounds.height(); - canvas.scale(scaleX, scaleY); - } - super.draw(canvas); - canvas.clipRect(getBounds()); - canvas.restoreToCount(saveCount); - - } - - public void loop(boolean loop) { - animator.setRepeatCount(loop ? ValueAnimator.INFINITE : 0); - } - - public boolean isLooping() { - return animator.getRepeatCount() == ValueAnimator.INFINITE; - } - - public boolean isAnimating() { - return animator.isRunning(); - } - - public void playAnimation() { - if (layers.isEmpty()) { - playAnimationWhenLayerAdded = true; - return; - } - animator.setCurrentPlayTime((long) (getProgress() * animator.getDuration())); - animator.start(); - } - - public void cancelAnimation() { - playAnimationWhenLayerAdded = false; - animator.cancel(); - } - - @Override - public void addLayer(AnimatableLayer layer) { - super.addLayer(layer); - if (playAnimationWhenLayerAdded) { - playAnimationWhenLayerAdded = false; - playAnimation(); - } - } - - public void addAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) { - animator.addUpdateListener(updateListener); - } - - public void removeAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) { - animator.removeUpdateListener(updateListener); - } - - public void addAnimatorListener(Animator.AnimatorListener listener) { - animator.addListener(listener); - } - - public void removeAnimatorListener(Animator.AnimatorListener listener) { - animator.removeListener(listener); - } - - @Override - public int getIntrinsicWidth() { - return composition == null ? -1 : composition.getBounds().width(); - } - - @Override - public int getIntrinsicHeight() { - return composition == null ? -1 : composition.getBounds().height(); - } - - @VisibleForTesting - public void recycleBitmaps() { - if (mainBitmap != null) { - mainBitmap.recycle(); - mainBitmap = null; - } - if (maskBitmap != null) { - maskBitmap.recycle(); - maskBitmap = null; - } - if (matteBitmap != null) { - matteBitmap.recycle(); - matteBitmap = null; - } - if (mainBitmapForMatte != null) { - mainBitmapForMatte.recycle(); - mainBitmapForMatte = null; - } - if (maskBitmapForMatte != null) { - maskBitmapForMatte.recycle(); - maskBitmapForMatte = null; - } - } + private LottieComposition composition; + + private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); + + @Nullable private Bitmap mainBitmap = null; + @Nullable private Bitmap maskBitmap = null; + @Nullable private Bitmap matteBitmap = null; + @Nullable private Bitmap mainBitmapForMatte = null; + @Nullable private Bitmap maskBitmapForMatte = null; + private boolean playAnimationWhenLayerAdded; + + public LottieDrawable() { + super(null); + + animator.setRepeatCount(0); + animator.setInterpolator(new LinearInterpolator()); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + setProgress(animation.getAnimatedFraction()); + } + }); + } + + public void setComposition(LottieComposition composition) { + if (getCallback() == null) { + throw new IllegalStateException("You or your view must set a Drawable.Callback before setting the composition. This gets done automatically when added to an ImageView. " + + "Either call ImageView.setImageDrawable() before setComposition() or call setCallback(yourView.getCallback()) first."); + } + clearComposition(); + this.composition = composition; + animator.setDuration(composition.getDuration()); + setBounds(0, 0, composition.getBounds().width(), composition.getBounds().height()); + buildLayersForComposition(composition); + + getCallback().invalidateDrawable(this); + } + + private void clearComposition() { + recycleBitmaps(); + clearLayers(); + } + + private void buildLayersForComposition(LottieComposition composition) { + if (composition == null) { + throw new IllegalStateException("Composition is null"); + } + Rect bounds = composition.getBounds(); + if (composition.hasMasks() || composition.hasMattes()) { + mainBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888); + } + if (composition.hasMasks()) { + maskBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8); + } + if (composition.hasMattes()) { + matteBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888); + } + LongSparseArray layerMap = new LongSparseArray<>(composition.getLayers().size()); + List layers = new ArrayList<>(composition.getLayers().size()); + LayerView maskedLayer = null; + for (int i = composition.getLayers().size() - 1; i >= 0; i--) { + Layer layer = composition.getLayers().get(i); + LayerView layerView; + if (maskedLayer == null) { + layerView = new LayerView(layer, composition, getCallback(), mainBitmap, maskBitmap, matteBitmap); + } else { + if (mainBitmapForMatte == null) { + mainBitmapForMatte = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8); + } + if (maskBitmapForMatte == null && !layer.getMasks().isEmpty()) { + maskBitmapForMatte = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8); + } + + layerView = new LayerView(layer, composition, getCallback(), mainBitmapForMatte, maskBitmapForMatte, null); + } + layerMap.put(layerView.getId(), layerView); + if (maskedLayer != null) { + maskedLayer.setMatteLayer(layerView); + maskedLayer = null; + } else { + layers.add(layerView); + if (layer.getMatteType() == Layer.MatteType.Add) { + maskedLayer = layerView; + } + } + } + + for (int i = 0; i < layers.size(); i++) { + LayerView layerView = layers.get(i); + addLayer(layerView); + } + + for (int i = 0; i < layerMap.size(); i++) { + long key = layerMap.keyAt(i); + LayerView layerView = layerMap.get(key); + LayerView parentLayer = layerMap.get(layerView.getLayerModel().getParentId()); + if (parentLayer != null) { + layerView.setParentLayer(parentLayer); + } + } + } + + @Override + public void invalidateSelf() { + final Callback callback = getCallback(); + if (callback != null) { + callback.invalidateDrawable(this); + } + } + + @Override + public void draw(@NonNull Canvas canvas) { + if (composition == null) { + return; + } + Rect bounds = getBounds(); + Rect compBounds = composition.getBounds(); + int saveCount = canvas.save(); + if (!bounds.equals(compBounds)) { + float scaleX = bounds.width() / (float) compBounds.width(); + float scaleY = bounds.height() / (float) compBounds.height(); + canvas.scale(scaleX, scaleY); + } + super.draw(canvas); + canvas.clipRect(getBounds()); + canvas.restoreToCount(saveCount); + + } + + public void loop(boolean loop) { + animator.setRepeatCount(loop ? ValueAnimator.INFINITE : 0); + } + + public boolean isLooping() { + return animator.getRepeatCount() == ValueAnimator.INFINITE; + } + + public boolean isAnimating() { + return animator.isRunning(); + } + + public void playAnimation() { + if (layers.isEmpty()) { + playAnimationWhenLayerAdded = true; + return; + } + animator.setCurrentPlayTime((long) (getProgress() * animator.getDuration())); + animator.start(); + } + + public void cancelAnimation() { + playAnimationWhenLayerAdded = false; + animator.cancel(); + } + + @Override + public void addLayer(AnimatableLayer layer) { + super.addLayer(layer); + if (playAnimationWhenLayerAdded) { + playAnimationWhenLayerAdded = false; + playAnimation(); + } + } + + public void addAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) { + animator.addUpdateListener(updateListener); + } + + public void removeAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) { + animator.removeUpdateListener(updateListener); + } + + public void addAnimatorListener(Animator.AnimatorListener listener) { + animator.addListener(listener); + } + + public void removeAnimatorListener(Animator.AnimatorListener listener) { + animator.removeListener(listener); + } + + @Override + public int getIntrinsicWidth() { + return composition == null ? -1 : composition.getBounds().width(); + } + + @Override + public int getIntrinsicHeight() { + return composition == null ? -1 : composition.getBounds().height(); + } + + @VisibleForTesting + public void recycleBitmaps() { + if (mainBitmap != null) { + mainBitmap.recycle(); + mainBitmap = null; + } + if (maskBitmap != null) { + maskBitmap.recycle(); + maskBitmap = null; + } + if (matteBitmap != null) { + matteBitmap.recycle(); + matteBitmap = null; + } + if (mainBitmapForMatte != null) { + mainBitmapForMatte.recycle(); + mainBitmapForMatte = null; + } + if (maskBitmapForMatte != null) { + maskBitmapForMatte.recycle(); + maskBitmapForMatte = null; + } + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/layers/MaskLayer.java b/lottie/src/main/java/com/airbnb/lottie/layers/MaskLayer.java index 1acb652b84..f7f91039df 100644 --- a/lottie/src/main/java/com/airbnb/lottie/layers/MaskLayer.java +++ b/lottie/src/main/java/com/airbnb/lottie/layers/MaskLayer.java @@ -11,17 +11,17 @@ class MaskLayer extends AnimatableLayer { - private final List> masks; + private final List> masks; - MaskLayer(List masks, Drawable.Callback callback) { - super(callback); - this.masks = new ArrayList<>(masks.size()); - for (int i = 0; i < masks.size(); i++) { - this.masks.add(masks.get(i).getMaskPath().createAnimation()); - } + MaskLayer(List masks, Drawable.Callback callback) { + super(callback); + this.masks = new ArrayList<>(masks.size()); + for (int i = 0; i < masks.size(); i++) { + this.masks.add(masks.get(i).getMaskPath().createAnimation()); } + } - List> getMasks() { - return masks; - } + List> getMasks() { + return masks; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/layers/RectLayer.java b/lottie/src/main/java/com/airbnb/lottie/layers/RectLayer.java index 7d3c8b0371..201015eaac 100644 --- a/lottie/src/main/java/com/airbnb/lottie/layers/RectLayer.java +++ b/lottie/src/main/java/com/airbnb/lottie/layers/RectLayer.java @@ -22,325 +22,325 @@ class RectLayer extends AnimatableLayer { - @Nullable private RoundRectLayer fillLayer; - @Nullable private RoundRectLayer strokeLayer; - - RectLayer(RectangleShape rectShape, @Nullable ShapeFill fill, - @Nullable ShapeStroke stroke, Transform transform, Drawable.Callback callback) { - super(callback); - - setBounds(transform.getBounds()); - setAnchorPoint(transform.getAnchor().createAnimation()); - setAlpha(transform.getOpacity().createAnimation()); - setPosition(transform.getPosition().createAnimation()); - setTransform(transform.getScale().createAnimation()); - setRotation(transform.getRotation().createAnimation()); - - if (fill != null) { - fillLayer = new RoundRectLayer(getCallback()); - fillLayer.setColor(fill.getColor().createAnimation()); - fillLayer.setShapeAlpha(fill.getOpacity().createAnimation()); - fillLayer.setTransformAlpha(transform.getOpacity().createAnimation()); - fillLayer.setRectCornerRadius(rectShape.getCornerRadius().createAnimation()); - fillLayer.setRectSize(rectShape.getSize().createAnimation()); - fillLayer.setRectPosition(rectShape.getPosition().createAnimation()); - addLayer(fillLayer); - } - - if (stroke != null) { - strokeLayer = new RoundRectLayer(getCallback()); - strokeLayer.setIsStroke(); - strokeLayer.setColor(stroke.getColor().createAnimation()); - strokeLayer.setShapeAlpha(stroke.getOpacity().createAnimation()); - strokeLayer.setTransformAlpha(transform.getOpacity().createAnimation()); - strokeLayer.setLineWidth(stroke.getWidth().createAnimation()); - if (!stroke.getLineDashPattern().isEmpty()) { - List> dashPatternAnimations = new ArrayList<>(stroke.getLineDashPattern().size()); - for (AnimatableFloatValue dashPattern : stroke.getLineDashPattern()) { - dashPatternAnimations.add(dashPattern.createAnimation()); - } - strokeLayer.setDashPattern(dashPatternAnimations, stroke.getDashOffset().createAnimation()); - } - strokeLayer.setLineCapType(stroke.getCapType()); - strokeLayer.setRectCornerRadius(rectShape.getCornerRadius().createAnimation()); - strokeLayer.setRectSize(rectShape.getSize().createAnimation()); - strokeLayer.setRectPosition(rectShape.getPosition().createAnimation()); - strokeLayer.setLineJoinType(stroke.getJoinType()); - addLayer(strokeLayer); - } + @Nullable private RoundRectLayer fillLayer; + @Nullable private RoundRectLayer strokeLayer; + + RectLayer(RectangleShape rectShape, @Nullable ShapeFill fill, + @Nullable ShapeStroke stroke, Transform transform, Drawable.Callback callback) { + super(callback); + + setBounds(transform.getBounds()); + setAnchorPoint(transform.getAnchor().createAnimation()); + setAlpha(transform.getOpacity().createAnimation()); + setPosition(transform.getPosition().createAnimation()); + setTransform(transform.getScale().createAnimation()); + setRotation(transform.getRotation().createAnimation()); + + if (fill != null) { + fillLayer = new RoundRectLayer(getCallback()); + fillLayer.setColor(fill.getColor().createAnimation()); + fillLayer.setShapeAlpha(fill.getOpacity().createAnimation()); + fillLayer.setTransformAlpha(transform.getOpacity().createAnimation()); + fillLayer.setRectCornerRadius(rectShape.getCornerRadius().createAnimation()); + fillLayer.setRectSize(rectShape.getSize().createAnimation()); + fillLayer.setRectPosition(rectShape.getPosition().createAnimation()); + addLayer(fillLayer); } - @Override - public void setAlpha(int alpha) { - super.setAlpha(alpha); - if (fillLayer != null) { - fillLayer.setAlpha(alpha); - } - if (strokeLayer != null) { - strokeLayer.setAlpha(alpha); + if (stroke != null) { + strokeLayer = new RoundRectLayer(getCallback()); + strokeLayer.setIsStroke(); + strokeLayer.setColor(stroke.getColor().createAnimation()); + strokeLayer.setShapeAlpha(stroke.getOpacity().createAnimation()); + strokeLayer.setTransformAlpha(transform.getOpacity().createAnimation()); + strokeLayer.setLineWidth(stroke.getWidth().createAnimation()); + if (!stroke.getLineDashPattern().isEmpty()) { + List> dashPatternAnimations = new ArrayList<>(stroke.getLineDashPattern().size()); + for (AnimatableFloatValue dashPattern : stroke.getLineDashPattern()) { + dashPatternAnimations.add(dashPattern.createAnimation()); } + strokeLayer.setDashPattern(dashPatternAnimations, stroke.getDashOffset().createAnimation()); + } + strokeLayer.setLineCapType(stroke.getCapType()); + strokeLayer.setRectCornerRadius(rectShape.getCornerRadius().createAnimation()); + strokeLayer.setRectSize(rectShape.getSize().createAnimation()); + strokeLayer.setRectPosition(rectShape.getPosition().createAnimation()); + strokeLayer.setLineJoinType(stroke.getJoinType()); + addLayer(strokeLayer); } + } - private static class RoundRectLayer extends AnimatableLayer { - private final KeyframeAnimation.AnimationListener alphaChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Integer value) { - invalidateSelf(); - } - }; - - private final KeyframeAnimation.AnimationListener colorChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Integer value) { - onColorChanged(); - } - }; - - private final KeyframeAnimation.AnimationListener lineWidthChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Float value) { - onLineWidthChanged(); - } - }; - - private final KeyframeAnimation.AnimationListener dashPatternChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Float value) { - onDashPatternChanged(); - } - }; - - private final KeyframeAnimation.AnimationListener cornerRadiusChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Float value) { - invalidateSelf(); - } - }; - - private final KeyframeAnimation.AnimationListener rectPositionChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(PointF value) { - invalidateSelf(); - } - }; - - private final KeyframeAnimation.AnimationListener rectSizeChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(PointF value) { - invalidateSelf(); - } - }; - - private final Paint paint = new Paint(); - private final RectF fillRect = new RectF(); - - private KeyframeAnimation color; - private KeyframeAnimation lineWidth; - private KeyframeAnimation shapeAlpha; - private KeyframeAnimation transformAlpha; - private KeyframeAnimation rectCornerRadius; - private KeyframeAnimation rectPosition; - private KeyframeAnimation rectSize; - - @Nullable private List> lineDashPattern; - @Nullable private KeyframeAnimation lineDashPatternOffset; - - RoundRectLayer(Drawable.Callback callback) { - super(callback); - paint.setAntiAlias(true); - paint.setStyle(Paint.Style.FILL); - } + @Override + public void setAlpha(int alpha) { + super.setAlpha(alpha); + if (fillLayer != null) { + fillLayer.setAlpha(alpha); + } + if (strokeLayer != null) { + strokeLayer.setAlpha(alpha); + } + } + + private static class RoundRectLayer extends AnimatableLayer { + private final KeyframeAnimation.AnimationListener alphaChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Integer value) { + invalidateSelf(); + } + }; + + private final KeyframeAnimation.AnimationListener colorChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Integer value) { + onColorChanged(); + } + }; + + private final KeyframeAnimation.AnimationListener lineWidthChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Float value) { + onLineWidthChanged(); + } + }; + + private final KeyframeAnimation.AnimationListener dashPatternChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Float value) { + onDashPatternChanged(); + } + }; + + private final KeyframeAnimation.AnimationListener cornerRadiusChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Float value) { + invalidateSelf(); + } + }; + + private final KeyframeAnimation.AnimationListener rectPositionChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(PointF value) { + invalidateSelf(); + } + }; + + private final KeyframeAnimation.AnimationListener rectSizeChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(PointF value) { + invalidateSelf(); + } + }; + + private final Paint paint = new Paint(); + private final RectF fillRect = new RectF(); + + private KeyframeAnimation color; + private KeyframeAnimation lineWidth; + private KeyframeAnimation shapeAlpha; + private KeyframeAnimation transformAlpha; + private KeyframeAnimation rectCornerRadius; + private KeyframeAnimation rectPosition; + private KeyframeAnimation rectSize; + + @Nullable private List> lineDashPattern; + @Nullable private KeyframeAnimation lineDashPatternOffset; + + RoundRectLayer(Drawable.Callback callback) { + super(callback); + paint.setAntiAlias(true); + paint.setStyle(Paint.Style.FILL); + } - void setShapeAlpha(KeyframeAnimation shapeAlpha) { - if (this.shapeAlpha != null) { - removeAnimation(this.shapeAlpha); - this.shapeAlpha.removeUpdateListener(alphaChangedListener); - } - this.shapeAlpha = shapeAlpha; - addAnimation(shapeAlpha); - shapeAlpha.addUpdateListener(alphaChangedListener); - invalidateSelf(); - } + void setShapeAlpha(KeyframeAnimation shapeAlpha) { + if (this.shapeAlpha != null) { + removeAnimation(this.shapeAlpha); + this.shapeAlpha.removeUpdateListener(alphaChangedListener); + } + this.shapeAlpha = shapeAlpha; + addAnimation(shapeAlpha); + shapeAlpha.addUpdateListener(alphaChangedListener); + invalidateSelf(); + } - void setTransformAlpha(KeyframeAnimation transformAlpha) { - if (this.transformAlpha != null) { - removeAnimation(this.transformAlpha); - this.transformAlpha.removeUpdateListener(alphaChangedListener); - } - this.transformAlpha = transformAlpha; - addAnimation(transformAlpha); - transformAlpha.addUpdateListener(alphaChangedListener); - invalidateSelf(); - } + void setTransformAlpha(KeyframeAnimation transformAlpha) { + if (this.transformAlpha != null) { + removeAnimation(this.transformAlpha); + this.transformAlpha.removeUpdateListener(alphaChangedListener); + } + this.transformAlpha = transformAlpha; + addAnimation(transformAlpha); + transformAlpha.addUpdateListener(alphaChangedListener); + invalidateSelf(); + } - @Override - public void setAlpha(int alpha) { - paint.setAlpha(alpha); - } + @Override + public void setAlpha(int alpha) { + paint.setAlpha(alpha); + } - @Override - public int getAlpha() { - Integer shapeAlpha = this.shapeAlpha == null ? 255 : this.shapeAlpha.getValue(); - Integer transformAlpha = this.transformAlpha == null ? 255 : this.transformAlpha.getValue(); - int layerAlpha = super.getAlpha(); - return (int) ((shapeAlpha / 255f * transformAlpha / 255f * layerAlpha / 255f) * 255); - } + @Override + public int getAlpha() { + Integer shapeAlpha = this.shapeAlpha == null ? 255 : this.shapeAlpha.getValue(); + Integer transformAlpha = this.transformAlpha == null ? 255 : this.transformAlpha.getValue(); + int layerAlpha = super.getAlpha(); + return (int) ((shapeAlpha / 255f * transformAlpha / 255f * layerAlpha / 255f) * 255); + } - public void setColor(KeyframeAnimation color) { - if (this.color != null) { - removeAnimation(this.color); - this.color.removeUpdateListener(colorChangedListener); - } - this.color = color; - addAnimation(color); - color.addUpdateListener(colorChangedListener); - onColorChanged(); - } + public void setColor(KeyframeAnimation color) { + if (this.color != null) { + removeAnimation(this.color); + this.color.removeUpdateListener(colorChangedListener); + } + this.color = color; + addAnimation(color); + color.addUpdateListener(colorChangedListener); + onColorChanged(); + } - private void onColorChanged() { - paint.setColor(color.getValue()); - invalidateSelf(); - } + private void onColorChanged() { + paint.setColor(color.getValue()); + invalidateSelf(); + } - private void setIsStroke() { - paint.setStyle(Paint.Style.STROKE); - invalidateSelf(); - } + private void setIsStroke() { + paint.setStyle(Paint.Style.STROKE); + invalidateSelf(); + } - void setLineWidth(KeyframeAnimation lineWidth) { - if (this.lineWidth != null) { - removeAnimation(this.lineWidth); - this.lineWidth.removeUpdateListener(lineWidthChangedListener); - } - this.lineWidth = lineWidth; - addAnimation(lineWidth); - lineWidth.addUpdateListener(lineWidthChangedListener); - onLineWidthChanged(); - } + void setLineWidth(KeyframeAnimation lineWidth) { + if (this.lineWidth != null) { + removeAnimation(this.lineWidth); + this.lineWidth.removeUpdateListener(lineWidthChangedListener); + } + this.lineWidth = lineWidth; + addAnimation(lineWidth); + lineWidth.addUpdateListener(lineWidthChangedListener); + onLineWidthChanged(); + } - private void onLineWidthChanged() { - paint.setStrokeWidth(lineWidth.getValue()); - invalidateSelf(); - } + private void onLineWidthChanged() { + paint.setStrokeWidth(lineWidth.getValue()); + invalidateSelf(); + } - void setDashPattern(List> lineDashPattern, KeyframeAnimation offset) { - if (this.lineDashPattern != null) { - removeAnimation(this.lineDashPattern.get(0)); - this.lineDashPattern.get(0).removeUpdateListener(dashPatternChangedListener); - removeAnimation(this.lineDashPattern.get(1)); - this.lineDashPattern.get(1).removeUpdateListener(dashPatternChangedListener); - } - if (this.lineDashPatternOffset != null) { - removeAnimation(this.lineDashPatternOffset); - this.lineDashPatternOffset.removeUpdateListener(dashPatternChangedListener); - } - if (lineDashPattern.isEmpty()) { - return; - } - this.lineDashPattern = lineDashPattern; - this.lineDashPatternOffset = offset; - addAnimation(lineDashPattern.get(0)); - addAnimation(lineDashPattern.get(1)); - lineDashPattern.get(0).addUpdateListener(dashPatternChangedListener); - if (!lineDashPattern.get(1).equals(lineDashPattern.get(1))) { - lineDashPattern.get(1).addUpdateListener(dashPatternChangedListener); - } - addAnimation(offset); - offset.addUpdateListener(dashPatternChangedListener); - onDashPatternChanged(); - } + void setDashPattern(List> lineDashPattern, KeyframeAnimation offset) { + if (this.lineDashPattern != null) { + removeAnimation(this.lineDashPattern.get(0)); + this.lineDashPattern.get(0).removeUpdateListener(dashPatternChangedListener); + removeAnimation(this.lineDashPattern.get(1)); + this.lineDashPattern.get(1).removeUpdateListener(dashPatternChangedListener); + } + if (this.lineDashPatternOffset != null) { + removeAnimation(this.lineDashPatternOffset); + this.lineDashPatternOffset.removeUpdateListener(dashPatternChangedListener); + } + if (lineDashPattern.isEmpty()) { + return; + } + this.lineDashPattern = lineDashPattern; + this.lineDashPatternOffset = offset; + addAnimation(lineDashPattern.get(0)); + addAnimation(lineDashPattern.get(1)); + lineDashPattern.get(0).addUpdateListener(dashPatternChangedListener); + if (!lineDashPattern.get(1).equals(lineDashPattern.get(1))) { + lineDashPattern.get(1).addUpdateListener(dashPatternChangedListener); + } + addAnimation(offset); + offset.addUpdateListener(dashPatternChangedListener); + onDashPatternChanged(); + } - private void onDashPatternChanged() { - if (lineDashPattern == null || lineDashPatternOffset == null) { - throw new IllegalStateException("LineDashPattern is null"); - } - float[] values = new float[lineDashPattern.size()]; - for (int i = 0; i < lineDashPattern.size(); i++) { - values[i] = lineDashPattern.get(i).getValue(); - } - paint.setPathEffect(new DashPathEffect(values, lineDashPatternOffset.getValue())); - invalidateSelf(); - } + private void onDashPatternChanged() { + if (lineDashPattern == null || lineDashPatternOffset == null) { + throw new IllegalStateException("LineDashPattern is null"); + } + float[] values = new float[lineDashPattern.size()]; + for (int i = 0; i < lineDashPattern.size(); i++) { + values[i] = lineDashPattern.get(i).getValue(); + } + paint.setPathEffect(new DashPathEffect(values, lineDashPatternOffset.getValue())); + invalidateSelf(); + } - void setLineCapType(ShapeStroke.LineCapType lineCapType) { - switch (lineCapType) { - case Round: - paint.setStrokeCap(Paint.Cap.ROUND); - break; - case Butt: - paint.setStrokeCap(Paint.Cap.BUTT); - default: - } - } + void setLineCapType(ShapeStroke.LineCapType lineCapType) { + switch (lineCapType) { + case Round: + paint.setStrokeCap(Paint.Cap.ROUND); + break; + case Butt: + paint.setStrokeCap(Paint.Cap.BUTT); + default: + } + } - void setLineJoinType(ShapeStroke.LineJoinType lineJoinType) { - switch (lineJoinType) { - case Bevel: - paint.setStrokeJoin(Paint.Join.BEVEL); - break; - case Miter: - paint.setStrokeJoin(Paint.Join.MITER); - break; - case Round: - paint.setStrokeJoin(Paint.Join.ROUND); - break; - } - } + void setLineJoinType(ShapeStroke.LineJoinType lineJoinType) { + switch (lineJoinType) { + case Bevel: + paint.setStrokeJoin(Paint.Join.BEVEL); + break; + case Miter: + paint.setStrokeJoin(Paint.Join.MITER); + break; + case Round: + paint.setStrokeJoin(Paint.Join.ROUND); + break; + } + } - void setRectCornerRadius(KeyframeAnimation rectCornerRadius) { - if (this.rectCornerRadius != null) { - removeAnimation(rectCornerRadius); - this.rectCornerRadius.removeUpdateListener(cornerRadiusChangedListener); - } - this.rectCornerRadius = rectCornerRadius; - addAnimation(rectCornerRadius); - rectCornerRadius.addUpdateListener(cornerRadiusChangedListener); - invalidateSelf(); - } + void setRectCornerRadius(KeyframeAnimation rectCornerRadius) { + if (this.rectCornerRadius != null) { + removeAnimation(rectCornerRadius); + this.rectCornerRadius.removeUpdateListener(cornerRadiusChangedListener); + } + this.rectCornerRadius = rectCornerRadius; + addAnimation(rectCornerRadius); + rectCornerRadius.addUpdateListener(cornerRadiusChangedListener); + invalidateSelf(); + } - void setRectPosition(KeyframeAnimation rectPosition) { - if (this.rectPosition != null) { - removeAnimation(this.rectPosition); - this.rectPosition.removeUpdateListener(rectPositionChangedListener); - } - this.rectPosition = rectPosition; - addAnimation(rectPosition); - rectPosition.addUpdateListener(rectPositionChangedListener); - invalidateSelf(); - } + void setRectPosition(KeyframeAnimation rectPosition) { + if (this.rectPosition != null) { + removeAnimation(this.rectPosition); + this.rectPosition.removeUpdateListener(rectPositionChangedListener); + } + this.rectPosition = rectPosition; + addAnimation(rectPosition); + rectPosition.addUpdateListener(rectPositionChangedListener); + invalidateSelf(); + } - void setRectSize(KeyframeAnimation rectSize) { - if (this.rectSize != null) { - removeAnimation(this.rectSize); - this.rectSize.removeUpdateListener(rectSizeChangedListener); - } - this.rectSize = rectSize; - addAnimation(rectSize); - rectSize.addUpdateListener(rectSizeChangedListener); - invalidateSelf(); - } + void setRectSize(KeyframeAnimation rectSize) { + if (this.rectSize != null) { + removeAnimation(this.rectSize); + this.rectSize.removeUpdateListener(rectSizeChangedListener); + } + this.rectSize = rectSize; + addAnimation(rectSize); + rectSize.addUpdateListener(rectSizeChangedListener); + invalidateSelf(); + } - @SuppressLint("NewApi") - @Override - public void draw(@NonNull Canvas canvas) { - if (paint.getStyle() == Paint.Style.STROKE && paint.getStrokeWidth() == 0f) { - return; - } - paint.setAlpha(getAlpha()); - float halfWidth = rectSize.getValue().x / 2f; - float halfHeight = rectSize.getValue().y / 2f; - - fillRect.set(rectPosition.getValue().x - halfWidth, - rectPosition.getValue().y - halfHeight, - rectPosition.getValue().x + halfWidth, - rectPosition.getValue().y + halfHeight); - if (rectCornerRadius.getValue() == 0) { - canvas.drawRect(fillRect, paint); - } else { - canvas.drawRoundRect(fillRect, rectCornerRadius.getValue(), rectCornerRadius.getValue(), paint); - } - } + @SuppressLint("NewApi") + @Override + public void draw(@NonNull Canvas canvas) { + if (paint.getStyle() == Paint.Style.STROKE && paint.getStrokeWidth() == 0f) { + return; + } + paint.setAlpha(getAlpha()); + float halfWidth = rectSize.getValue().x / 2f; + float halfHeight = rectSize.getValue().y / 2f; + + fillRect.set(rectPosition.getValue().x - halfWidth, + rectPosition.getValue().y - halfHeight, + rectPosition.getValue().x + halfWidth, + rectPosition.getValue().y + halfHeight); + if (rectCornerRadius.getValue() == 0) { + canvas.drawRect(fillRect, paint); + } else { + canvas.drawRoundRect(fillRect, rectCornerRadius.getValue(), rectCornerRadius.getValue(), paint); + } } + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/layers/ShapeLayer.java b/lottie/src/main/java/com/airbnb/lottie/layers/ShapeLayer.java index 5c34452a19..50ae7bd734 100644 --- a/lottie/src/main/java/com/airbnb/lottie/layers/ShapeLayer.java +++ b/lottie/src/main/java/com/airbnb/lottie/layers/ShapeLayer.java @@ -22,367 +22,369 @@ import java.util.List; class ShapeLayer extends AnimatableLayer { - private final KeyframeAnimation.AnimationListener pathChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Path value) { - onPathChanged(); - } - }; - - private final KeyframeAnimation.AnimationListener alphaChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Integer value) { - invalidateSelf(); - } - }; - - private final KeyframeAnimation.AnimationListener colorChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Integer value) { - onColorChanged(); - } - }; - - private final KeyframeAnimation.AnimationListener lineWidthChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Float value) { - onLineWidthChanged(); - } - }; - - private final KeyframeAnimation.AnimationListener dashPatternChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Float value) { - onDashPatternChanged(); - } - }; - - private final KeyframeAnimation.AnimationListener strokeChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(Float value) { - onPathPropertiesChanged(); - } - }; - - private final KeyframeAnimation.AnimationListener scaleChangedListener = new KeyframeAnimation.AnimationListener() { - @Override - public void onValueChanged(ScaleXY value) { - onPathPropertiesChanged(); - } - }; - - - private final Paint paint = new Paint(); - private final Path tempPath = new Path(); - private final Path currentPath = new Path(); - /** Path for the trim path when it loops around the end back to the start */ - private final Path extraTrimPath = new Path(); - private final PathMeasure pathMeasure = new PathMeasure(); - - private float currentPathScaleX; - private float currentPathScaleY; - private float currentPathStrokeStart; - private float currentPathStrokeEnd = 100; - private float currentPathStrokeOffset = 0; - - @Nullable private KeyframeAnimation scale; - private final RectF tempRect = new RectF(); - private final Matrix scaleMatrix = new Matrix(); - - private KeyframeAnimation path; - private KeyframeAnimation color; - private KeyframeAnimation lineWidth; - @Nullable private KeyframeAnimation strokeStart; - @Nullable private KeyframeAnimation strokeEnd; - @Nullable private KeyframeAnimation strokeOffset; - - private KeyframeAnimation shapeAlpha; - private KeyframeAnimation transformAlpha; - private List> lineDashPattern; - private KeyframeAnimation lineDashPatternOffset; - - ShapeLayer(Drawable.Callback callback) { - super(callback); - paint.setStyle(Paint.Style.FILL); - paint.setAntiAlias(true); - } - - void setIsStroke() { - paint.setStyle(Paint.Style.STROKE); - invalidateSelf(); - } - - public void setColor(KeyframeAnimation color) { - if (this.color != null) { - removeAnimation(this.color); - this.color.removeUpdateListener(colorChangedListener); - } - this.color = color; - addAnimation(color); - color.addUpdateListener(colorChangedListener); - onColorChanged(); + private final KeyframeAnimation.AnimationListener pathChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Path value) { + onPathChanged(); } + }; - private void onColorChanged() { - paint.setColor(color.getValue()); - invalidateSelf(); + private final KeyframeAnimation.AnimationListener alphaChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Integer value) { + invalidateSelf(); } + }; - public void setPath(KeyframeAnimation path) { - if (this.path != null) { - removeAnimation(this.path); - this.path.removeUpdateListener(pathChangedListener); - } - - this.path = path; - addAnimation(path); - // TODO: When the path changes, we probably have to scale it again. - path.addUpdateListener(pathChangedListener); - onPathChanged(); + private final KeyframeAnimation.AnimationListener colorChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Integer value) { + onColorChanged(); } + }; - void onPathChanged() { - currentPath.reset(); - currentPath.set(path.getValue()); - currentPathStrokeStart = Float.NaN; - currentPathStrokeEnd = Float.NaN; - currentPathScaleX = Float.NaN; - currentPathScaleY = Float.NaN; - onPathPropertiesChanged(); - invalidateSelf(); + private final KeyframeAnimation.AnimationListener lineWidthChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Float value) { + onLineWidthChanged(); } + }; - private void onPathPropertiesChanged() { - boolean needsStrokeStart = strokeStart != null && strokeStart.getValue() != currentPathStrokeStart; - boolean needsStrokeEnd = strokeEnd != null && strokeEnd.getValue() != currentPathStrokeEnd; - boolean needsStrokeOffset = strokeOffset != null && strokeOffset.getValue() != currentPathStrokeOffset; - boolean needsScaleX = scale != null && scale.getValue().getScaleX() != currentPathScaleX; - boolean needsScaleY = scale != null && scale.getValue().getScaleY() != currentPathScaleY; - - if (!needsStrokeStart && !needsStrokeEnd && !needsScaleX && !needsScaleY && !needsStrokeOffset) { - return; - } - currentPath.set(path.getValue()); - - if (needsScaleX || needsScaleY) { - currentPath.computeBounds(tempRect, false); - currentPathScaleX = scale.getValue().getScaleX(); - currentPathScaleY = scale.getValue().getScaleY(); - scaleMatrix.setScale(currentPathScaleX, currentPathScaleY, tempRect.centerX(), tempRect.centerY()); - currentPath.transform(scaleMatrix, currentPath); - } - - if (needsStrokeStart || needsStrokeEnd || needsStrokeOffset) { - tempPath.set(currentPath); - pathMeasure.setPath(tempPath, false); - currentPathStrokeStart = strokeStart.getValue(); - currentPathStrokeEnd = strokeEnd.getValue(); - float length = pathMeasure.getLength(); - float start = length * currentPathStrokeStart / 100f; - float end = length * currentPathStrokeEnd / 100f; - float newStart = Math.min(start, end); - float newEnd = Math.max(start, end); - - currentPath.reset(); - currentPathStrokeOffset = strokeOffset.getValue() / 360f * length; - newStart += currentPathStrokeOffset; - newEnd += currentPathStrokeOffset; - - pathMeasure.getSegment( - newStart, - newEnd, - currentPath, - true); - - extraTrimPath.reset(); - if (newEnd > length) { - pathMeasure.getSegment( - 0, - newEnd % length, - extraTrimPath, - true); - } - } - - currentPath.computeBounds(tempRect, false); - setBounds((int) tempRect.left, (int) tempRect.top, (int) tempRect.right, (int) tempRect.bottom); - - invalidateSelf(); + private final KeyframeAnimation.AnimationListener dashPatternChangedListener = new KeyframeAnimation.AnimationListener() { + @Override + public void onValueChanged(Float value) { + onDashPatternChanged(); } + }; - @SuppressLint("NewApi") + private final KeyframeAnimation.AnimationListener strokeChangedListener = new KeyframeAnimation.AnimationListener() { @Override - public void draw(@NonNull Canvas canvas) { - if (paint.getStyle() == Paint.Style.STROKE && paint.getStrokeWidth() == 0f) { - return; - } - paint.setAlpha(getAlpha()); - canvas.drawPath(currentPath, paint); - if (!extraTrimPath.isEmpty()) { - canvas.drawPath(extraTrimPath, paint); - } + public void onValueChanged(Float value) { + onPathPropertiesChanged(); } + }; + private final KeyframeAnimation.AnimationListener scaleChangedListener = new KeyframeAnimation.AnimationListener() { @Override - public int getAlpha() { - Integer shapeAlpha = this.shapeAlpha == null ? 255 : this.shapeAlpha.getValue(); - Integer transformAlpha = this.transformAlpha == null ? 255 : this.transformAlpha.getValue(); - int layerAlpha = super.getAlpha(); - return (int) ((shapeAlpha / 255f * transformAlpha / 255f * layerAlpha / 255f) * 255); + public void onValueChanged(ScaleXY value) { + onPathPropertiesChanged(); } - - void setShapeAlpha(KeyframeAnimation shapeAlpha) { - if (this.shapeAlpha != null) { - removeAnimation(this.shapeAlpha); - this.shapeAlpha.removeUpdateListener(alphaChangedListener); - } - this.shapeAlpha = shapeAlpha; - addAnimation(shapeAlpha); - shapeAlpha.addUpdateListener(alphaChangedListener); - invalidateSelf(); + }; + + + private final Paint paint = new Paint(); + private final Path tempPath = new Path(); + private final Path currentPath = new Path(); + /** + * Path for the trim path when it loops around the end back to the start + */ + private final Path extraTrimPath = new Path(); + private final PathMeasure pathMeasure = new PathMeasure(); + + private float currentPathScaleX; + private float currentPathScaleY; + private float currentPathStrokeStart; + private float currentPathStrokeEnd = 100; + private float currentPathStrokeOffset = 0; + + @Nullable private KeyframeAnimation scale; + private final RectF tempRect = new RectF(); + private final Matrix scaleMatrix = new Matrix(); + + private KeyframeAnimation path; + private KeyframeAnimation color; + private KeyframeAnimation lineWidth; + @Nullable private KeyframeAnimation strokeStart; + @Nullable private KeyframeAnimation strokeEnd; + @Nullable private KeyframeAnimation strokeOffset; + + private KeyframeAnimation shapeAlpha; + private KeyframeAnimation transformAlpha; + private List> lineDashPattern; + private KeyframeAnimation lineDashPatternOffset; + + ShapeLayer(Drawable.Callback callback) { + super(callback); + paint.setStyle(Paint.Style.FILL); + paint.setAntiAlias(true); + } + + void setIsStroke() { + paint.setStyle(Paint.Style.STROKE); + invalidateSelf(); + } + + public void setColor(KeyframeAnimation color) { + if (this.color != null) { + removeAnimation(this.color); + this.color.removeUpdateListener(colorChangedListener); + } + this.color = color; + addAnimation(color); + color.addUpdateListener(colorChangedListener); + onColorChanged(); + } + + private void onColorChanged() { + paint.setColor(color.getValue()); + invalidateSelf(); + } + + public void setPath(KeyframeAnimation path) { + if (this.path != null) { + removeAnimation(this.path); + this.path.removeUpdateListener(pathChangedListener); } - void setTransformAlpha(KeyframeAnimation transformAlpha) { - if (this.transformAlpha != null) { - removeAnimation(this.transformAlpha); - this.transformAlpha.removeUpdateListener(alphaChangedListener); - } - this.transformAlpha = transformAlpha; - addAnimation(transformAlpha); - transformAlpha.addUpdateListener(alphaChangedListener); - invalidateSelf(); + this.path = path; + addAnimation(path); + // TODO: When the path changes, we probably have to scale it again. + path.addUpdateListener(pathChangedListener); + onPathChanged(); + } + + void onPathChanged() { + currentPath.reset(); + currentPath.set(path.getValue()); + currentPathStrokeStart = Float.NaN; + currentPathStrokeEnd = Float.NaN; + currentPathScaleX = Float.NaN; + currentPathScaleY = Float.NaN; + onPathPropertiesChanged(); + invalidateSelf(); + } + + private void onPathPropertiesChanged() { + boolean needsStrokeStart = strokeStart != null && strokeStart.getValue() != currentPathStrokeStart; + boolean needsStrokeEnd = strokeEnd != null && strokeEnd.getValue() != currentPathStrokeEnd; + boolean needsStrokeOffset = strokeOffset != null && strokeOffset.getValue() != currentPathStrokeOffset; + boolean needsScaleX = scale != null && scale.getValue().getScaleX() != currentPathScaleX; + boolean needsScaleY = scale != null && scale.getValue().getScaleY() != currentPathScaleY; + + if (!needsStrokeStart && !needsStrokeEnd && !needsScaleX && !needsScaleY && !needsStrokeOffset) { + return; + } + currentPath.set(path.getValue()); + + if (needsScaleX || needsScaleY) { + currentPath.computeBounds(tempRect, false); + currentPathScaleX = scale.getValue().getScaleX(); + currentPathScaleY = scale.getValue().getScaleY(); + scaleMatrix.setScale(currentPathScaleX, currentPathScaleY, tempRect.centerX(), tempRect.centerY()); + currentPath.transform(scaleMatrix, currentPath); } - @Override - public void setAlpha(@IntRange(from = 0, to = 255) int alpha) { - paint.setAlpha(alpha); - invalidateSelf(); + if (needsStrokeStart || needsStrokeEnd || needsStrokeOffset) { + tempPath.set(currentPath); + pathMeasure.setPath(tempPath, false); + currentPathStrokeStart = strokeStart.getValue(); + currentPathStrokeEnd = strokeEnd.getValue(); + float length = pathMeasure.getLength(); + float start = length * currentPathStrokeStart / 100f; + float end = length * currentPathStrokeEnd / 100f; + float newStart = Math.min(start, end); + float newEnd = Math.max(start, end); + + currentPath.reset(); + currentPathStrokeOffset = strokeOffset.getValue() / 360f * length; + newStart += currentPathStrokeOffset; + newEnd += currentPathStrokeOffset; + + pathMeasure.getSegment( + newStart, + newEnd, + currentPath, + true); + + extraTrimPath.reset(); + if (newEnd > length) { + pathMeasure.getSegment( + 0, + newEnd % length, + extraTrimPath, + true); + } } - @Override - public void setColorFilter(ColorFilter colorFilter) { + currentPath.computeBounds(tempRect, false); + setBounds((int) tempRect.left, (int) tempRect.top, (int) tempRect.right, (int) tempRect.bottom); - } + invalidateSelf(); + } - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; + @SuppressLint("NewApi") + @Override + public void draw(@NonNull Canvas canvas) { + if (paint.getStyle() == Paint.Style.STROKE && paint.getStrokeWidth() == 0f) { + return; } - - void setLineWidth(KeyframeAnimation lineWidth) { - if (this.lineWidth != null) { - removeAnimation(this.lineWidth); - this.lineWidth.removeUpdateListener(lineWidthChangedListener); - } - this.lineWidth = lineWidth; - addAnimation(lineWidth); - lineWidth.addUpdateListener(lineWidthChangedListener); - onLineWidthChanged(); + paint.setAlpha(getAlpha()); + canvas.drawPath(currentPath, paint); + if (!extraTrimPath.isEmpty()) { + canvas.drawPath(extraTrimPath, paint); } - - private void onLineWidthChanged() { - paint.setStrokeWidth(lineWidth.getValue()); - invalidateSelf(); + } + + @Override + public int getAlpha() { + Integer shapeAlpha = this.shapeAlpha == null ? 255 : this.shapeAlpha.getValue(); + Integer transformAlpha = this.transformAlpha == null ? 255 : this.transformAlpha.getValue(); + int layerAlpha = super.getAlpha(); + return (int) ((shapeAlpha / 255f * transformAlpha / 255f * layerAlpha / 255f) * 255); + } + + void setShapeAlpha(KeyframeAnimation shapeAlpha) { + if (this.shapeAlpha != null) { + removeAnimation(this.shapeAlpha); + this.shapeAlpha.removeUpdateListener(alphaChangedListener); } - - void setDashPattern(List> lineDashPattern, KeyframeAnimation offset) { - if (this.lineDashPattern != null) { - removeAnimation(this.lineDashPattern.get(0)); - this.lineDashPattern.get(0).removeUpdateListener(dashPatternChangedListener); - removeAnimation(this.lineDashPattern.get(1)); - this.lineDashPattern.get(1).removeUpdateListener(dashPatternChangedListener); - } - if (this.lineDashPatternOffset != null) { - removeAnimation(this.lineDashPatternOffset); - this.lineDashPatternOffset.removeUpdateListener(dashPatternChangedListener); - } - if (lineDashPattern.isEmpty()) { - return; - } - this.lineDashPattern = lineDashPattern; - this.lineDashPatternOffset = offset; - addAnimation(lineDashPattern.get(0)); - lineDashPattern.get(0).addUpdateListener(dashPatternChangedListener); - if (!lineDashPattern.get(1).equals(lineDashPattern.get(1))) { - addAnimation(lineDashPattern.get(1)); - lineDashPattern.get(1).addUpdateListener(dashPatternChangedListener); - } - addAnimation(offset); - offset.addUpdateListener(dashPatternChangedListener); - onDashPatternChanged(); + this.shapeAlpha = shapeAlpha; + addAnimation(shapeAlpha); + shapeAlpha.addUpdateListener(alphaChangedListener); + invalidateSelf(); + } + + void setTransformAlpha(KeyframeAnimation transformAlpha) { + if (this.transformAlpha != null) { + removeAnimation(this.transformAlpha); + this.transformAlpha.removeUpdateListener(alphaChangedListener); } - - private void onDashPatternChanged() { - float[] values = new float[lineDashPattern.size()]; - for (int i = 0; i < lineDashPattern.size(); i++) { - values[i] = lineDashPattern.get(i).getValue(); - if (values[i] == 0) { - values[i] = 0.01f; - } - } - paint.setPathEffect(new DashPathEffect(values, lineDashPatternOffset.getValue())); - invalidateSelf(); + this.transformAlpha = transformAlpha; + addAnimation(transformAlpha); + transformAlpha.addUpdateListener(alphaChangedListener); + invalidateSelf(); + } + + @Override + public void setAlpha(@IntRange(from = 0, to = 255) int alpha) { + paint.setAlpha(alpha); + invalidateSelf(); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + void setLineWidth(KeyframeAnimation lineWidth) { + if (this.lineWidth != null) { + removeAnimation(this.lineWidth); + this.lineWidth.removeUpdateListener(lineWidthChangedListener); } - - void setLineCapType(ShapeStroke.LineCapType lineCapType) { - switch (lineCapType) { - case Round: - paint.setStrokeCap(Paint.Cap.ROUND); - break; - case Butt: - default: - paint.setStrokeCap(Paint.Cap.BUTT); - } - invalidateSelf(); + this.lineWidth = lineWidth; + addAnimation(lineWidth); + lineWidth.addUpdateListener(lineWidthChangedListener); + onLineWidthChanged(); + } + + private void onLineWidthChanged() { + paint.setStrokeWidth(lineWidth.getValue()); + invalidateSelf(); + } + + void setDashPattern(List> lineDashPattern, KeyframeAnimation offset) { + if (this.lineDashPattern != null) { + removeAnimation(this.lineDashPattern.get(0)); + this.lineDashPattern.get(0).removeUpdateListener(dashPatternChangedListener); + removeAnimation(this.lineDashPattern.get(1)); + this.lineDashPattern.get(1).removeUpdateListener(dashPatternChangedListener); } - - void setLineJoinType(ShapeStroke.LineJoinType lineJoinType) { - switch (lineJoinType) { - case Bevel: - paint.setStrokeJoin(Paint.Join.BEVEL); - break; - case Miter: - paint.setStrokeJoin(Paint.Join.MITER); - break; - case Round: - paint.setStrokeJoin(Paint.Join.ROUND); - break; - } + if (this.lineDashPatternOffset != null) { + removeAnimation(this.lineDashPatternOffset); + this.lineDashPatternOffset.removeUpdateListener(dashPatternChangedListener); } - - void setTrimPath(KeyframeAnimation strokeStart, KeyframeAnimation strokeEnd, KeyframeAnimation strokeOffset) { - if (this.strokeStart != null) { - removeAnimation(this.strokeStart); - this.strokeStart.removeUpdateListener(strokeChangedListener); - } - if (this.strokeEnd != null) { - removeAnimation(this.strokeEnd); - this.strokeEnd.removeUpdateListener(strokeChangedListener); - } - if (this.strokeOffset != null) { - removeAnimation(this.strokeOffset); - this.strokeOffset.removeUpdateListener(strokeChangedListener); - } - this.strokeStart = strokeStart; - this.strokeEnd = strokeEnd; - this.strokeOffset = strokeOffset; - addAnimation(strokeStart); - strokeStart.addUpdateListener(strokeChangedListener); - addAnimation(strokeEnd); - strokeEnd.addUpdateListener(strokeChangedListener); - addAnimation(strokeOffset); - strokeOffset.addUpdateListener(strokeChangedListener); - onPathPropertiesChanged(); + if (lineDashPattern.isEmpty()) { + return; } + this.lineDashPattern = lineDashPattern; + this.lineDashPatternOffset = offset; + addAnimation(lineDashPattern.get(0)); + lineDashPattern.get(0).addUpdateListener(dashPatternChangedListener); + if (!lineDashPattern.get(1).equals(lineDashPattern.get(1))) { + addAnimation(lineDashPattern.get(1)); + lineDashPattern.get(1).addUpdateListener(dashPatternChangedListener); + } + addAnimation(offset); + offset.addUpdateListener(dashPatternChangedListener); + onDashPatternChanged(); + } + + private void onDashPatternChanged() { + float[] values = new float[lineDashPattern.size()]; + for (int i = 0; i < lineDashPattern.size(); i++) { + values[i] = lineDashPattern.get(i).getValue(); + if (values[i] == 0) { + values[i] = 0.01f; + } + } + paint.setPathEffect(new DashPathEffect(values, lineDashPatternOffset.getValue())); + invalidateSelf(); + } + + void setLineCapType(ShapeStroke.LineCapType lineCapType) { + switch (lineCapType) { + case Round: + paint.setStrokeCap(Paint.Cap.ROUND); + break; + case Butt: + default: + paint.setStrokeCap(Paint.Cap.BUTT); + } + invalidateSelf(); + } + + void setLineJoinType(ShapeStroke.LineJoinType lineJoinType) { + switch (lineJoinType) { + case Bevel: + paint.setStrokeJoin(Paint.Join.BEVEL); + break; + case Miter: + paint.setStrokeJoin(Paint.Join.MITER); + break; + case Round: + paint.setStrokeJoin(Paint.Join.ROUND); + break; + } + } - void setScale(@SuppressWarnings("NullableProblems") KeyframeAnimation scale) { - if (this.scale != null) { - removeAnimation(this.scale); - this.scale.removeUpdateListener(scaleChangedListener); - } - this.scale = scale; - addAnimation(scale); - scale.addUpdateListener(scaleChangedListener); - onPathPropertiesChanged(); + void setTrimPath(KeyframeAnimation strokeStart, KeyframeAnimation strokeEnd, KeyframeAnimation strokeOffset) { + if (this.strokeStart != null) { + removeAnimation(this.strokeStart); + this.strokeStart.removeUpdateListener(strokeChangedListener); + } + if (this.strokeEnd != null) { + removeAnimation(this.strokeEnd); + this.strokeEnd.removeUpdateListener(strokeChangedListener); + } + if (this.strokeOffset != null) { + removeAnimation(this.strokeOffset); + this.strokeOffset.removeUpdateListener(strokeChangedListener); + } + this.strokeStart = strokeStart; + this.strokeEnd = strokeEnd; + this.strokeOffset = strokeOffset; + addAnimation(strokeStart); + strokeStart.addUpdateListener(strokeChangedListener); + addAnimation(strokeEnd); + strokeEnd.addUpdateListener(strokeChangedListener); + addAnimation(strokeOffset); + strokeOffset.addUpdateListener(strokeChangedListener); + onPathPropertiesChanged(); + } + + void setScale(@SuppressWarnings("NullableProblems") KeyframeAnimation scale) { + if (this.scale != null) { + removeAnimation(this.scale); + this.scale.removeUpdateListener(scaleChangedListener); } + this.scale = scale; + addAnimation(scale); + scale.addUpdateListener(scaleChangedListener); + onPathPropertiesChanged(); + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/layers/ShapeLayerView.java b/lottie/src/main/java/com/airbnb/lottie/layers/ShapeLayerView.java index 0831550aed..6d0b654e5e 100644 --- a/lottie/src/main/java/com/airbnb/lottie/layers/ShapeLayerView.java +++ b/lottie/src/main/java/com/airbnb/lottie/layers/ShapeLayerView.java @@ -17,63 +17,63 @@ class ShapeLayerView extends AnimatableLayer { - @Nullable private ShapeLayer fillLayer; - @Nullable private ShapeLayer strokeLayer; + @Nullable private ShapeLayer fillLayer; + @Nullable private ShapeLayer strokeLayer; - ShapeLayerView(ShapePath shape, @Nullable ShapeFill fill, - @Nullable ShapeStroke stroke, @Nullable ShapeTrimPath trim, - Transform transformModel, Drawable.Callback callback) { - super(callback); - setBounds(transformModel.getBounds()); - setAnchorPoint(transformModel.getAnchor().createAnimation()); - setPosition(transformModel.getPosition().createAnimation()); - setRotation(transformModel.getRotation().createAnimation()); + ShapeLayerView(ShapePath shape, @Nullable ShapeFill fill, + @Nullable ShapeStroke stroke, @Nullable ShapeTrimPath trim, + Transform transformModel, Drawable.Callback callback) { + super(callback); + setBounds(transformModel.getBounds()); + setAnchorPoint(transformModel.getAnchor().createAnimation()); + setPosition(transformModel.getPosition().createAnimation()); + setRotation(transformModel.getRotation().createAnimation()); - AnimatableScaleValue scale = transformModel.getScale(); - setTransform(transformModel.getScale().createAnimation()); - if (fill != null) { - fillLayer = new ShapeLayer(getCallback()); - fillLayer.setPath(shape.getShapePath().createAnimation()); - fillLayer.setColor(fill.getColor().createAnimation()); - fillLayer.setShapeAlpha(fill.getOpacity().createAnimation()); - fillLayer.setTransformAlpha(transformModel.getOpacity().createAnimation()); - fillLayer.setScale(scale.createAnimation()); - addLayer(fillLayer); - } + AnimatableScaleValue scale = transformModel.getScale(); + setTransform(transformModel.getScale().createAnimation()); + if (fill != null) { + fillLayer = new ShapeLayer(getCallback()); + fillLayer.setPath(shape.getShapePath().createAnimation()); + fillLayer.setColor(fill.getColor().createAnimation()); + fillLayer.setShapeAlpha(fill.getOpacity().createAnimation()); + fillLayer.setTransformAlpha(transformModel.getOpacity().createAnimation()); + fillLayer.setScale(scale.createAnimation()); + addLayer(fillLayer); + } - if (stroke != null) { - strokeLayer = new ShapeLayer(getCallback()); - strokeLayer.setIsStroke(); - strokeLayer.setPath(shape.getShapePath().createAnimation()); - strokeLayer.setColor(stroke.getColor().createAnimation()); - strokeLayer.setShapeAlpha(stroke.getOpacity().createAnimation()); - strokeLayer.setTransformAlpha(transformModel.getOpacity().createAnimation()); - strokeLayer.setLineWidth(stroke.getWidth().createAnimation()); - if (!stroke.getLineDashPattern().isEmpty()) { - List> dashPatternAnimations = new ArrayList<>(stroke.getLineDashPattern().size()); - for (AnimatableFloatValue dashPattern : stroke.getLineDashPattern()) { - dashPatternAnimations.add(dashPattern.createAnimation()); - } - strokeLayer.setDashPattern(dashPatternAnimations, stroke.getDashOffset().createAnimation()); - } - strokeLayer.setLineCapType(stroke.getCapType()); - strokeLayer.setLineJoinType(stroke.getJoinType()); - strokeLayer.setScale(scale.createAnimation()); - if (trim != null) { - strokeLayer.setTrimPath(trim.getStart().createAnimation(), trim.getEnd().createAnimation(), trim.getOffset().createAnimation()); - } - addLayer(strokeLayer); + if (stroke != null) { + strokeLayer = new ShapeLayer(getCallback()); + strokeLayer.setIsStroke(); + strokeLayer.setPath(shape.getShapePath().createAnimation()); + strokeLayer.setColor(stroke.getColor().createAnimation()); + strokeLayer.setShapeAlpha(stroke.getOpacity().createAnimation()); + strokeLayer.setTransformAlpha(transformModel.getOpacity().createAnimation()); + strokeLayer.setLineWidth(stroke.getWidth().createAnimation()); + if (!stroke.getLineDashPattern().isEmpty()) { + List> dashPatternAnimations = new ArrayList<>(stroke.getLineDashPattern().size()); + for (AnimatableFloatValue dashPattern : stroke.getLineDashPattern()) { + dashPatternAnimations.add(dashPattern.createAnimation()); } + strokeLayer.setDashPattern(dashPatternAnimations, stroke.getDashOffset().createAnimation()); + } + strokeLayer.setLineCapType(stroke.getCapType()); + strokeLayer.setLineJoinType(stroke.getJoinType()); + strokeLayer.setScale(scale.createAnimation()); + if (trim != null) { + strokeLayer.setTrimPath(trim.getStart().createAnimation(), trim.getEnd().createAnimation(), trim.getOffset().createAnimation()); + } + addLayer(strokeLayer); } + } - @Override - public void setAlpha(int alpha) { - super.setAlpha(alpha); - if (fillLayer != null) { - fillLayer.setAlpha(alpha); - } - if (strokeLayer != null) { - strokeLayer.setAlpha(alpha); - } + @Override + public void setAlpha(int alpha) { + super.setAlpha(alpha); + if (fillLayer != null) { + fillLayer.setAlpha(alpha); + } + if (strokeLayer != null) { + strokeLayer.setAlpha(alpha); } + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/CircleShape.java b/lottie/src/main/java/com/airbnb/lottie/model/CircleShape.java index 9b3667fb39..dad9571066 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/CircleShape.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/CircleShape.java @@ -10,23 +10,23 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class CircleShape { - private final AnimatablePathValue position; - private final AnimatablePointValue size; + private final AnimatablePathValue position; + private final AnimatablePointValue size; - CircleShape(JSONObject json, int frameRate, LottieComposition composition) { - try { - position = new AnimatablePathValue(json.getJSONObject("p"), frameRate, composition); - size = new AnimatablePointValue(json.getJSONObject("s"), frameRate, composition); - } catch (JSONException e) { - throw new IllegalArgumentException("Unable to parse circle " + json, e); - } + CircleShape(JSONObject json, int frameRate, LottieComposition composition) { + try { + position = new AnimatablePathValue(json.getJSONObject("p"), frameRate, composition); + size = new AnimatablePointValue(json.getJSONObject("s"), frameRate, composition); + } catch (JSONException e) { + throw new IllegalArgumentException("Unable to parse circle " + json, e); } + } - public AnimatablePathValue getPosition() { - return position; - } + public AnimatablePathValue getPosition() { + return position; + } - public AnimatablePointValue getSize() { - return size; - } + public AnimatablePointValue getSize() { + return size; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/CubicCurveData.java b/lottie/src/main/java/com/airbnb/lottie/model/CubicCurveData.java index 388894e45c..7372bfd2ac 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/CubicCurveData.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/CubicCurveData.java @@ -5,43 +5,43 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class CubicCurveData { - private final PointF controlPoint1; - private final PointF controlPoint2; - private final PointF vertex; - - CubicCurveData() { - controlPoint1 = new PointF(); - controlPoint2 = new PointF(); - vertex = new PointF(); - } - - public CubicCurveData(PointF controlPoint1, PointF controlPoint2, PointF vertex) { - this.controlPoint1 = controlPoint1; - this.controlPoint2 = controlPoint2; - this.vertex = vertex; - } - - void setControlPoint1(float x, float y) { - controlPoint1.set(x, y); - } - - public PointF getControlPoint1() { - return controlPoint1; - } - - void setControlPoint2(float x, float y) { - controlPoint2.set(x, y); - } - - public PointF getControlPoint2() { - return controlPoint2; - } - - void setVertex(float x, float y) { - vertex.set(x, y); - } - - public PointF getVertex() { - return vertex; - } + private final PointF controlPoint1; + private final PointF controlPoint2; + private final PointF vertex; + + CubicCurveData() { + controlPoint1 = new PointF(); + controlPoint2 = new PointF(); + vertex = new PointF(); + } + + public CubicCurveData(PointF controlPoint1, PointF controlPoint2, PointF vertex) { + this.controlPoint1 = controlPoint1; + this.controlPoint2 = controlPoint2; + this.vertex = vertex; + } + + void setControlPoint1(float x, float y) { + controlPoint1.set(x, y); + } + + public PointF getControlPoint1() { + return controlPoint1; + } + + void setControlPoint2(float x, float y) { + controlPoint2.set(x, y); + } + + public PointF getControlPoint2() { + return controlPoint2; + } + + void setVertex(float x, float y) { + vertex.set(x, y); + } + + public PointF getVertex() { + return vertex; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/Layer.java b/lottie/src/main/java/com/airbnb/lottie/model/Layer.java index 48ff40e63e..27a81bc868 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/Layer.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/Layer.java @@ -22,361 +22,361 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class Layer implements Transform { - private static final String TAG = Layer.class.getSimpleName(); - private final LottieComposition composition; - - public enum LottieLayerType { - None, - Solid, - Unknown, - Null, - Shape - } - - public enum MatteType { - None, - Add, - Invert, - Unknown - } - - static Layer fromJson(JSONObject json, LottieComposition composition) { - Layer layer = new Layer(composition); - try { - if (L.DBG) Log.d(TAG, "Parsing new layer."); - layer.layerName = json.getString("nm"); - if (L.DBG) Log.d(TAG, "\tName=" + layer.layerName); - layer.layerId = json.getLong("ind"); - if (L.DBG) Log.d(TAG, "\tId=" + layer.layerId); - layer.frameRate = composition.getFrameRate(); - - int layerType = json.getInt("ty"); - if (layerType <= LottieLayerType.Shape.ordinal()) { - layer.layerType = LottieLayerType.values()[layerType]; - } else { - layer.layerType = LottieLayerType.Unknown; - } - - try { - layer.parentId = json.getLong("parent"); - } catch (JSONException e) { - // Do nothing. - } - layer.inFrame = json.getLong("ip"); - layer.outFrame = json.getLong("op"); - if (L.DBG) Log.d(TAG, "\tFrames=" + layer.inFrame + "->" + layer.outFrame); - - if (layer.layerType == LottieLayerType.Solid) { - layer.solidWidth = (int) (json.getInt("sw") * composition.getScale()); - layer.solidHeight = (int) (json.getInt("sh") * composition.getScale()); - layer.solidColor = Color.parseColor(json.getString("sc")); - if (L.DBG) { - Log.d(TAG, "\tSolid=" + Integer.toHexString(layer.solidColor) + " " + - layer.solidWidth + "x" + layer.solidHeight + " " + composition.getBounds()); - } - } - - JSONObject ks = json.getJSONObject("ks"); - - JSONObject opacity = null; - try { - opacity = ks.getJSONObject("o"); - } catch (JSONException e) { - // Do nothing. - } - if (opacity != null) { - layer.opacity = new AnimatableIntegerValue(opacity, layer.frameRate, composition, false, true); - if (L.DBG) Log.d(TAG, "\tOpacity=" + layer.opacity.getInitialValue()); - } - - JSONObject rotation; - try { - rotation = ks.getJSONObject("r"); - } catch (JSONException e) { - rotation = ks.getJSONObject("rz"); - } - - if (rotation != null) { - layer.rotation = new AnimatableFloatValue(rotation, layer.frameRate, composition, false); - if (L.DBG) Log.d(TAG, "\tRotation=" + layer.rotation.getInitialValue()); - } - - JSONObject position = null; - try { - position = ks.getJSONObject("p"); - } catch (JSONException e) { - // Do nothing. - } - if (position != null) { - layer.position = new AnimatablePathValue(position, layer.frameRate, composition); - if (L.DBG) Log.d(TAG, "\tPosition=" + layer.getPosition().toString()); - } - - JSONObject anchor = null; - try { - anchor = ks.getJSONObject("a"); - } catch (JSONException e) { - // DO nothing. - } - if (anchor != null) { - layer.anchor = new AnimatablePathValue(anchor, layer.frameRate, composition); - if (L.DBG) Log.d(TAG, "\tAnchor=" + layer.anchor.toString()); - } - - JSONObject scale = null; - try { - scale = ks.getJSONObject("s"); - } catch (JSONException e) { - // Do nothing. - } - if (scale != null) { - layer.scale = new AnimatableScaleValue(scale, layer.frameRate, composition, false); - if (L.DBG) Log.d(TAG, "\tScale=" + layer.scale.toString()); - } - - try { - layer.matteType = MatteType.values()[json.getInt("tt")]; - if (L.DBG) Log.d(TAG, "\tMatte=" + layer.matteType); - } catch (JSONException e) { - // Do nothing. - } - - JSONArray jsonMasks = null; - try { - jsonMasks = json.getJSONArray("masksProperties"); - } catch (JSONException e) { - // Do nothing. - } - if (jsonMasks != null) { - for (int i = 0; i < jsonMasks.length(); i++) { - Mask mask = new Mask(jsonMasks.getJSONObject(i), layer.frameRate, composition); - layer.masks.add(mask); - if (L.DBG) Log.d(TAG, "\tMask=" + mask.getMaskMode()); - } - } - - JSONArray shapes = null; - try { - shapes = json.getJSONArray("shapes"); - } catch (JSONException e) { - // Do nothing. - } - if (shapes != null) { - for (int i = 0; i < shapes.length(); i++) { - Object shape = ShapeGroup.shapeItemWithJson(shapes.getJSONObject(i), layer.frameRate, composition); - if (shape != null) { - layer.shapes.add(shape); - if (L.DBG) Log.d(TAG, "\tShapes+=" + shape.getClass().getSimpleName()); - } - } - } - } catch (JSONException e) { - throw new IllegalStateException("Unable to parse layer json.", e); + private static final String TAG = Layer.class.getSimpleName(); + private final LottieComposition composition; + + public enum LottieLayerType { + None, + Solid, + Unknown, + Null, + Shape + } + + public enum MatteType { + None, + Add, + Invert, + Unknown + } + + static Layer fromJson(JSONObject json, LottieComposition composition) { + Layer layer = new Layer(composition); + try { + if (L.DBG) Log.d(TAG, "Parsing new layer."); + layer.layerName = json.getString("nm"); + if (L.DBG) Log.d(TAG, "\tName=" + layer.layerName); + layer.layerId = json.getLong("ind"); + if (L.DBG) Log.d(TAG, "\tId=" + layer.layerId); + layer.frameRate = composition.getFrameRate(); + + int layerType = json.getInt("ty"); + if (layerType <= LottieLayerType.Shape.ordinal()) { + layer.layerType = LottieLayerType.values()[layerType]; + } else { + layer.layerType = LottieLayerType.Unknown; + } + + try { + layer.parentId = json.getLong("parent"); + } catch (JSONException e) { + // Do nothing. + } + layer.inFrame = json.getLong("ip"); + layer.outFrame = json.getLong("op"); + if (L.DBG) Log.d(TAG, "\tFrames=" + layer.inFrame + "->" + layer.outFrame); + + if (layer.layerType == LottieLayerType.Solid) { + layer.solidWidth = (int) (json.getInt("sw") * composition.getScale()); + layer.solidHeight = (int) (json.getInt("sh") * composition.getScale()); + layer.solidColor = Color.parseColor(json.getString("sc")); + if (L.DBG) { + Log.d(TAG, "\tSolid=" + Integer.toHexString(layer.solidColor) + " " + + layer.solidWidth + "x" + layer.solidHeight + " " + composition.getBounds()); } - - layer.hasInAnimation = layer.inFrame > composition.getStartFrame(); - layer.hasOutAnimation = layer.outFrame < composition.getEndFrame(); - layer.hasInOutAnimation = layer.hasInAnimation || layer.hasOutAnimation; - - if (layer.hasInOutAnimation) { - List keys = new ArrayList<>(); - List keyTimes = new ArrayList<>(); - long length = composition.getEndFrame() - composition.getStartFrame(); - - if (layer.hasInAnimation) { - keys.add(0f); - keyTimes.add(0f); - keys.add(1f); - float inTime = layer.inFrame / (float) length; - keyTimes.add(inTime); - } else { - keys.add(1f); - keyTimes.add(0f); - } - - if (layer.hasOutAnimation) { - keys.add(0f); - keyTimes.add(layer.outFrame / (float) length); - keys.add(0f); - keyTimes.add(1f); - } else { - keys.add(1f); - keyTimes.add(1f); - } - - layer.inOutKeyTimes = keyTimes; - layer.inOutKeyFrames = keys; - + } + + JSONObject ks = json.getJSONObject("ks"); + + JSONObject opacity = null; + try { + opacity = ks.getJSONObject("o"); + } catch (JSONException e) { + // Do nothing. + } + if (opacity != null) { + layer.opacity = new AnimatableIntegerValue(opacity, layer.frameRate, composition, false, true); + if (L.DBG) Log.d(TAG, "\tOpacity=" + layer.opacity.getInitialValue()); + } + + JSONObject rotation; + try { + rotation = ks.getJSONObject("r"); + } catch (JSONException e) { + rotation = ks.getJSONObject("rz"); + } + + if (rotation != null) { + layer.rotation = new AnimatableFloatValue(rotation, layer.frameRate, composition, false); + if (L.DBG) Log.d(TAG, "\tRotation=" + layer.rotation.getInitialValue()); + } + + JSONObject position = null; + try { + position = ks.getJSONObject("p"); + } catch (JSONException e) { + // Do nothing. + } + if (position != null) { + layer.position = new AnimatablePathValue(position, layer.frameRate, composition); + if (L.DBG) Log.d(TAG, "\tPosition=" + layer.getPosition().toString()); + } + + JSONObject anchor = null; + try { + anchor = ks.getJSONObject("a"); + } catch (JSONException e) { + // DO nothing. + } + if (anchor != null) { + layer.anchor = new AnimatablePathValue(anchor, layer.frameRate, composition); + if (L.DBG) Log.d(TAG, "\tAnchor=" + layer.anchor.toString()); + } + + JSONObject scale = null; + try { + scale = ks.getJSONObject("s"); + } catch (JSONException e) { + // Do nothing. + } + if (scale != null) { + layer.scale = new AnimatableScaleValue(scale, layer.frameRate, composition, false); + if (L.DBG) Log.d(TAG, "\tScale=" + layer.scale.toString()); + } + + try { + layer.matteType = MatteType.values()[json.getInt("tt")]; + if (L.DBG) Log.d(TAG, "\tMatte=" + layer.matteType); + } catch (JSONException e) { + // Do nothing. + } + + JSONArray jsonMasks = null; + try { + jsonMasks = json.getJSONArray("masksProperties"); + } catch (JSONException e) { + // Do nothing. + } + if (jsonMasks != null) { + for (int i = 0; i < jsonMasks.length(); i++) { + Mask mask = new Mask(jsonMasks.getJSONObject(i), layer.frameRate, composition); + layer.masks.add(mask); + if (L.DBG) Log.d(TAG, "\tMask=" + mask.getMaskMode()); } - - return layer; - } - - private final List shapes = new ArrayList<>(); - - private String layerName; - private long layerId; - private LottieLayerType layerType; - private long parentId = -1; - private long inFrame; - private long outFrame; - private int frameRate; - - private final List masks = new ArrayList<>(); - - private int solidWidth; - private int solidHeight; - private int solidColor; - - private AnimatableIntegerValue opacity; - private AnimatableFloatValue rotation; - private AnimatablePathValue position; - - private AnimatablePathValue anchor; - private AnimatableScaleValue scale; - - private boolean hasOutAnimation; - private boolean hasInAnimation; - private boolean hasInOutAnimation; - @Nullable private List inOutKeyFrames; - @Nullable private List inOutKeyTimes; - - private MatteType matteType; - - private Layer(LottieComposition composition) { - this.composition = composition; - } - - @Override - public Rect getBounds() { - return composition.getBounds(); - } - - @Override - public AnimatablePathValue getAnchor() { - return anchor; - } - - public LottieComposition getComposition() { - return composition; - } - - public boolean hasInAnimation() { - return hasInAnimation; - } - - public boolean hasInOutAnimation() { - return hasInOutAnimation; - } - - @Nullable - public List getInOutKeyFrames() { - return inOutKeyFrames; - } - - @Nullable - public List getInOutKeyTimes() { - return inOutKeyTimes; - } - - public long getId() { - return layerId; - } - - public String getName() { - return layerName; + } + + JSONArray shapes = null; + try { + shapes = json.getJSONArray("shapes"); + } catch (JSONException e) { + // Do nothing. + } + if (shapes != null) { + for (int i = 0; i < shapes.length(); i++) { + Object shape = ShapeGroup.shapeItemWithJson(shapes.getJSONObject(i), layer.frameRate, composition); + if (shape != null) { + layer.shapes.add(shape); + if (L.DBG) Log.d(TAG, "\tShapes+=" + shape.getClass().getSimpleName()); + } + } + } + } catch (JSONException e) { + throw new IllegalStateException("Unable to parse layer json.", e); } - public List getMasks() { - return masks; - } + layer.hasInAnimation = layer.inFrame > composition.getStartFrame(); + layer.hasOutAnimation = layer.outFrame < composition.getEndFrame(); + layer.hasInOutAnimation = layer.hasInAnimation || layer.hasOutAnimation; + + if (layer.hasInOutAnimation) { + List keys = new ArrayList<>(); + List keyTimes = new ArrayList<>(); + long length = composition.getEndFrame() - composition.getStartFrame(); + + if (layer.hasInAnimation) { + keys.add(0f); + keyTimes.add(0f); + keys.add(1f); + float inTime = layer.inFrame / (float) length; + keyTimes.add(inTime); + } else { + keys.add(1f); + keyTimes.add(0f); + } + + if (layer.hasOutAnimation) { + keys.add(0f); + keyTimes.add(layer.outFrame / (float) length); + keys.add(0f); + keyTimes.add(1f); + } else { + keys.add(1f); + keyTimes.add(1f); + } + + layer.inOutKeyTimes = keyTimes; + layer.inOutKeyFrames = keys; - public MatteType getMatteType() { - return matteType; } - @Override - public AnimatableIntegerValue getOpacity() { - return opacity; - } + return layer; + } + + private final List shapes = new ArrayList<>(); - public long getParentId() { - return parentId; - } + private String layerName; + private long layerId; + private LottieLayerType layerType; + private long parentId = -1; + private long inFrame; + private long outFrame; + private int frameRate; + + private final List masks = new ArrayList<>(); + + private int solidWidth; + private int solidHeight; + private int solidColor; + + private AnimatableIntegerValue opacity; + private AnimatableFloatValue rotation; + private AnimatablePathValue position; + + private AnimatablePathValue anchor; + private AnimatableScaleValue scale; - @Override - public AnimatablePathValue getPosition() { - return position; + private boolean hasOutAnimation; + private boolean hasInAnimation; + private boolean hasInOutAnimation; + @Nullable private List inOutKeyFrames; + @Nullable private List inOutKeyTimes; + + private MatteType matteType; + + private Layer(LottieComposition composition) { + this.composition = composition; + } + + @Override + public Rect getBounds() { + return composition.getBounds(); + } + + @Override + public AnimatablePathValue getAnchor() { + return anchor; + } + + public LottieComposition getComposition() { + return composition; + } + + public boolean hasInAnimation() { + return hasInAnimation; + } + + public boolean hasInOutAnimation() { + return hasInOutAnimation; + } + + @Nullable + public List getInOutKeyFrames() { + return inOutKeyFrames; + } + + @Nullable + public List getInOutKeyTimes() { + return inOutKeyTimes; + } + + public long getId() { + return layerId; + } + + public String getName() { + return layerName; + } + + public List getMasks() { + return masks; + } + + public MatteType getMatteType() { + return matteType; + } + + @Override + public AnimatableIntegerValue getOpacity() { + return opacity; + } + + public long getParentId() { + return parentId; + } + + @Override + public AnimatablePathValue getPosition() { + return position; + } + + @Override + public AnimatableFloatValue getRotation() { + return rotation; + } + + @Override + public AnimatableScaleValue getScale() { + return scale; + } + + public List getShapes() { + return shapes; + } + + public int getSolidColor() { + return solidColor; + } + + public int getSolidHeight() { + return solidHeight; + } + + public int getSolidWidth() { + return solidWidth; + } + + @Override + public String toString() { + return toString(""); + } + + public String toString(String prefix) { + StringBuilder sb = new StringBuilder(); + sb.append(prefix).append(getName()).append("\n"); + Layer parent = composition.layerModelForId(getParentId()); + if (parent != null) { + sb.append("\t\tParents: ").append(parent.getName()); + parent = composition.layerModelForId(parent.getParentId()); + while (parent != null) { + sb.append("->").append(parent.getName()); + parent = composition.layerModelForId(parent.getParentId()); + } + sb.append(prefix).append("\n"); } - - @Override - public AnimatableFloatValue getRotation() { - return rotation; - } - - @Override - public AnimatableScaleValue getScale() { - return scale; + if (getPosition().hasAnimation() || getPosition().getInitialPoint().length() != 0) { + sb.append(prefix).append("\tPosition: ").append(getPosition()).append("\n"); } - - public List getShapes() { - return shapes; + if (getRotation().hasAnimation() || getRotation().getInitialValue() != 0f) { + sb.append(prefix).append("\tRotation: ").append(getRotation()).append("\n"); } - - public int getSolidColor() { - return solidColor; + if (getScale().hasAnimation() || !getScale().getInitialValue().isDefault()) { + sb.append(prefix).append("\tScale: ").append(getScale()).append("\n"); } - - public int getSolidHeight() { - return solidHeight; + if (getAnchor().hasAnimation() || getAnchor().getInitialPoint().length() != 0) { + sb.append(prefix).append("\tAnchor: ").append(getAnchor()).append("\n"); } - - public int getSolidWidth() { - return solidWidth; + if (!getMasks().isEmpty()) { + sb.append(prefix).append("\tMasks: ").append(getMasks().size()).append("\n"); } - - @Override - public String toString() { - return toString(""); + if (getSolidWidth() != 0 && getSolidHeight() != 0) { + sb.append(prefix).append("\tBackground: ").append(String.format(Locale.US, "%dx%d %X\n", getSolidWidth(), getSolidHeight(), getSolidColor())); } - - public String toString(String prefix) { - StringBuilder sb = new StringBuilder(); - sb.append(prefix).append(getName()).append("\n"); - Layer parent = composition.layerModelForId(getParentId()); - if (parent != null) { - sb.append("\t\tParents: ").append(parent.getName()); - parent = composition.layerModelForId(parent.getParentId()); - while (parent != null) { - sb.append("->").append(parent.getName()); - parent = composition.layerModelForId(parent.getParentId()); - } - sb.append(prefix).append("\n"); - } - if (getPosition().hasAnimation() || getPosition().getInitialPoint().length() != 0) { - sb.append(prefix).append("\tPosition: ").append(getPosition()).append("\n"); - } - if (getRotation().hasAnimation() || getRotation().getInitialValue() != 0f) { - sb.append(prefix).append("\tRotation: ").append(getRotation()).append("\n"); - } - if (getScale().hasAnimation() || !getScale().getInitialValue().isDefault()) { - sb.append(prefix).append("\tScale: ").append(getScale()).append("\n"); - } - if (getAnchor().hasAnimation() || getAnchor().getInitialPoint().length() != 0) { - sb.append(prefix).append("\tAnchor: ").append(getAnchor()).append("\n"); - } - if (!getMasks().isEmpty()) { - sb.append(prefix).append("\tMasks: ").append(getMasks().size()).append("\n"); - } - if (getSolidWidth() != 0 && getSolidHeight() != 0) { - sb.append(prefix).append("\tBackground: ").append(String.format(Locale.US, "%dx%d %X\n", getSolidWidth(), getSolidHeight(), getSolidColor())); - } - if (!shapes.isEmpty()) { - sb.append(prefix).append("\tShapes:\n"); - for (Object shape : shapes) { - sb.append(prefix).append("\t\t").append(shape).append("\n"); - } - } - return sb.toString(); + if (!shapes.isEmpty()) { + sb.append(prefix).append("\tShapes:\n"); + for (Object shape : shapes) { + sb.append(prefix).append("\t\t").append(shape).append("\n"); + } } + return sb.toString(); + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/LottieComposition.java b/lottie/src/main/java/com/airbnb/lottie/model/LottieComposition.java index 52b41c3340..4927d23c4b 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/LottieComposition.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/LottieComposition.java @@ -24,282 +24,282 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class LottieComposition { - public interface OnCompositionLoadedListener { - void onCompositionLoaded(LottieComposition composition); + public interface OnCompositionLoadedListener { + void onCompositionLoaded(LottieComposition composition); + } + + public interface Cancellable { + void cancel(); + } + + /** + * The largest bitmap drawing cache can be is 8,294,400 bytes. There are 4 bytes per pixel leaving ~2.3M pixels available. + * Reduce the number a little bit for safety. + *

+ * Hopefully this can be hardware accelerated someday. + */ + private static final int MAX_PIXELS = 1000; + + /** + * Loads a composition from a file stored in /assets. + */ + public static Cancellable fromAssetFileName(Context context, String fileName, OnCompositionLoadedListener loadedListener) { + InputStream stream; + try { + stream = context.getAssets().open(fileName); + } catch (IOException e) { + throw new IllegalStateException("Unable to find file " + fileName, e); } - - public interface Cancellable { - void cancel(); + return fromInputStream(context, stream, loadedListener); + } + + /** + * Loads a composition from an arbitrary input stream. + *

+ * ex: fromInputStream(context, new FileInputStream(filePath), (composition) -> {}); + */ + public static Cancellable fromInputStream(Context context, InputStream stream, OnCompositionLoadedListener loadedListener) { + FileCompositionLoader loader = new FileCompositionLoader(context.getResources(), loadedListener); + loader.execute(stream); + return loader; + } + + public static LottieComposition fromFileSync(Context context, String fileName) { + InputStream file; + try { + file = context.getAssets().open(fileName); + } catch (IOException e) { + throw new IllegalStateException("Unable to find file " + fileName, e); } - - /** - * The largest bitmap drawing cache can be is 8,294,400 bytes. There are 4 bytes per pixel leaving ~2.3M pixels available. - * Reduce the number a little bit for safety. - * - * Hopefully this can be hardware accelerated someday. - */ - private static final int MAX_PIXELS = 1000; - - /** - * Loads a composition from a file stored in /assets. - */ - public static Cancellable fromAssetFileName(Context context, String fileName, OnCompositionLoadedListener loadedListener) { - InputStream stream; - try { - stream = context.getAssets().open(fileName); - } catch (IOException e) { - throw new IllegalStateException("Unable to find file " + fileName, e); - } - return fromInputStream(context, stream, loadedListener); + return fromInputStream(context.getResources(), file); + } + + /** + * Loads a composition from a raw json object. This is useful for animations loaded from the network. + */ + public static Cancellable fromJson(Resources res, JSONObject json, OnCompositionLoadedListener loadedListener) { + JsonCompositionLoader loader = new JsonCompositionLoader(res, loadedListener); + loader.execute(json); + return loader; + } + + @SuppressWarnings("WeakerAccess") + public static LottieComposition fromInputStream(Resources res, InputStream file) { + try { + int size = file.available(); + byte[] buffer = new byte[size]; + //noinspection ResultOfMethodCallIgnored + file.read(buffer); + file.close(); + String json = new String(buffer, "UTF-8"); + + JSONObject jsonObject = new JSONObject(json); + return LottieComposition.fromJsonSync(res, jsonObject); + } catch (IOException e) { + throw new IllegalStateException("Unable to find file.", e); + } catch (JSONException e) { + throw new IllegalStateException("Unable to load JSON.", e); } - - /** - * Loads a composition from an arbitrary input stream. - * - * ex: fromInputStream(context, new FileInputStream(filePath), (composition) -> {}); - */ - public static Cancellable fromInputStream(Context context, InputStream stream, OnCompositionLoadedListener loadedListener) { - FileCompositionLoader loader = new FileCompositionLoader(context.getResources(), loadedListener); - loader.execute(stream); - return loader; + } + + @SuppressWarnings("WeakerAccess") + public static LottieComposition fromJsonSync(Resources res, JSONObject json) { + LottieComposition composition = new LottieComposition(res); + + int width = -1; + int height = -1; + try { + width = json.getInt("w"); + height = json.getInt("h"); + } catch (JSONException e) { + // ignore. } - - public static LottieComposition fromFileSync(Context context, String fileName) { - InputStream file; - try { - file = context.getAssets().open(fileName); - } catch (IOException e) { - throw new IllegalStateException("Unable to find file " + fileName, e); - } - return fromInputStream(context.getResources(), file); + if (width != -1 && height != -1) { + int scaledWidth = (int) (width * composition.scale); + int scaledHeight = (int) (height * composition.scale); + if (Math.max(scaledWidth, scaledHeight) > MAX_PIXELS) { + float factor = (float) MAX_PIXELS / (float) Math.max(scaledWidth, scaledHeight); + scaledWidth *= factor; + scaledHeight *= factor; + composition.scale *= factor; + } + composition.bounds = new Rect(0, 0, scaledWidth, scaledHeight); } - /** - * Loads a composition from a raw json object. This is useful for animations loaded from the network. - */ - public static Cancellable fromJson(Resources res, JSONObject json, OnCompositionLoadedListener loadedListener) { - JsonCompositionLoader loader = new JsonCompositionLoader(res, loadedListener); - loader.execute(json); - return loader; + try { + composition.startFrame = json.getLong("ip"); + composition.endFrame = json.getLong("op"); + composition.frameRate = json.getInt("fr"); + } catch (JSONException e) { + // } - @SuppressWarnings("WeakerAccess") - public static LottieComposition fromInputStream(Resources res, InputStream file) { - try { - int size = file.available(); - byte[] buffer = new byte[size]; - //noinspection ResultOfMethodCallIgnored - file.read(buffer); - file.close(); - String json = new String(buffer, "UTF-8"); - - JSONObject jsonObject = new JSONObject(json); - return LottieComposition.fromJsonSync(res,jsonObject); - } catch (IOException e) { - throw new IllegalStateException("Unable to find file.", e); - } catch (JSONException e) { - throw new IllegalStateException("Unable to load JSON.", e); - } + if (composition.endFrame != 0 && composition.frameRate != 0) { + long frameDuration = composition.endFrame - composition.startFrame; + composition.duration = (long) (frameDuration / (float) composition.frameRate * 1000); } - @SuppressWarnings("WeakerAccess") - public static LottieComposition fromJsonSync(Resources res, JSONObject json) { - LottieComposition composition = new LottieComposition(res); - - int width = -1; - int height = -1; - try { - width = json.getInt("w"); - height = json.getInt("h"); - } catch (JSONException e) { - // ignore. - } - if (width != -1 && height != -1) { - int scaledWidth = (int) (width * composition.scale); - int scaledHeight = (int) (height * composition.scale); - if (Math.max(scaledWidth, scaledHeight) > MAX_PIXELS) { - float factor = (float) MAX_PIXELS / (float) Math.max(scaledWidth, scaledHeight); - scaledWidth *= factor; - scaledHeight *= factor; - composition.scale *= factor; - } - composition.bounds = new Rect(0, 0, scaledWidth, scaledHeight); - } - - try { - composition.startFrame = json.getLong("ip"); - composition.endFrame = json.getLong("op"); - composition.frameRate = json.getInt("fr"); - } catch (JSONException e) { - // - } - - if (composition.endFrame != 0 && composition.frameRate != 0) { - long frameDuration = composition.endFrame - composition.startFrame; - composition.duration = (long) (frameDuration / (float) composition.frameRate * 1000); - } - - try { - JSONArray jsonLayers = json.getJSONArray("layers"); - for (int i = 0; i < jsonLayers.length(); i++) { - Layer layer = Layer.fromJson(jsonLayers.getJSONObject(i), composition); - addLayer(composition, layer); - } - } catch (JSONException e) { - throw new IllegalStateException("Unable to find layers.", e); - } - - // These are precomps. This naively adds the precomp layers to the main composition. - // TODO: Significant work will have to be done to properly support them. - try { - JSONArray assets = json.getJSONArray("assets"); - for (int i = 0; i < assets.length(); i++) { - JSONObject asset = assets.getJSONObject(i); - JSONArray layers = asset.getJSONArray("layers"); - for (int j = 0; j < layers.length(); j++) { - Layer layer = Layer.fromJson(layers.getJSONObject(j), composition); - addLayer(composition, layer); - } - } - } catch (JSONException e) { - // Do nothing. - } - - - return composition; + try { + JSONArray jsonLayers = json.getJSONArray("layers"); + for (int i = 0; i < jsonLayers.length(); i++) { + Layer layer = Layer.fromJson(jsonLayers.getJSONObject(i), composition); + addLayer(composition, layer); + } + } catch (JSONException e) { + throw new IllegalStateException("Unable to find layers.", e); } - private static void addLayer(LottieComposition composition, Layer layer) { - composition.layers.add(layer); - composition.layerMap.put(layer.getId(), layer); - if (!layer.getMasks().isEmpty()) { - composition.hasMasks = true; - } - if (layer.getMatteType() != null && layer.getMatteType() != Layer.MatteType.None) { - composition.hasMattes = true; + // These are precomps. This naively adds the precomp layers to the main composition. + // TODO: Significant work will have to be done to properly support them. + try { + JSONArray assets = json.getJSONArray("assets"); + for (int i = 0; i < assets.length(); i++) { + JSONObject asset = assets.getJSONObject(i); + JSONArray layers = asset.getJSONArray("layers"); + for (int j = 0; j < layers.length(); j++) { + Layer layer = Layer.fromJson(layers.getJSONObject(j), composition); + addLayer(composition, layer); } + } + } catch (JSONException e) { + // Do nothing. } - private final LongSparseArray layerMap = new LongSparseArray<>(); - private final List layers = new ArrayList<>(); - private Rect bounds; - private long startFrame; - private long endFrame; - private int frameRate; - private long duration; - private boolean hasMasks; - private boolean hasMattes; - private float scale; - - private LottieComposition(Resources res) { - scale = res.getDisplayMetrics().density; - } - - @VisibleForTesting - public LottieComposition(long duration) { - scale = 1f; - this.duration = duration; - } + return composition; + } - Layer layerModelForId(long id) { - return layerMap.get(id); + private static void addLayer(LottieComposition composition, Layer layer) { + composition.layers.add(layer); + composition.layerMap.put(layer.getId(), layer); + if (!layer.getMasks().isEmpty()) { + composition.hasMasks = true; } - - public Rect getBounds() { - return bounds; + if (layer.getMatteType() != null && layer.getMatteType() != Layer.MatteType.None) { + composition.hasMattes = true; } - - public long getDuration() { - return duration; + } + + private final LongSparseArray layerMap = new LongSparseArray<>(); + private final List layers = new ArrayList<>(); + private Rect bounds; + private long startFrame; + private long endFrame; + private int frameRate; + private long duration; + private boolean hasMasks; + private boolean hasMattes; + private float scale; + + private LottieComposition(Resources res) { + scale = res.getDisplayMetrics().density; + } + + @VisibleForTesting + public LottieComposition(long duration) { + scale = 1f; + this.duration = duration; + } + + + Layer layerModelForId(long id) { + return layerMap.get(id); + } + + public Rect getBounds() { + return bounds; + } + + public long getDuration() { + return duration; + } + + long getEndFrame() { + return endFrame; + } + + public int getFrameRate() { + return frameRate; + } + + public List getLayers() { + return layers; + } + + long getStartFrame() { + return startFrame; + } + + public boolean hasMasks() { + return hasMasks; + } + + public boolean hasMattes() { + return hasMattes; + } + + public float getScale() { + return scale; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("LottieComposition:\n"); + for (Layer layer : layers) { + sb.append(layer.toString("\t")); } + return sb.toString(); + } - long getEndFrame() { - return endFrame; - } + private static final class FileCompositionLoader extends CompositionLoader { - public int getFrameRate() { - return frameRate; - } - - public List getLayers() { - return layers; - } - - long getStartFrame() { - return startFrame; - } - - public boolean hasMasks() { - return hasMasks; - } + private final Resources res; + private final OnCompositionLoadedListener loadedListener; - public boolean hasMattes() { - return hasMattes; + FileCompositionLoader(Resources res, OnCompositionLoadedListener loadedListener) { + this.res = res; + this.loadedListener = loadedListener; } - public float getScale() { - return scale; + @Override + protected LottieComposition doInBackground(InputStream... params) { + return fromInputStream(res, params[0]); } @Override - public String toString() { - final StringBuilder sb = new StringBuilder("LottieComposition:\n"); - for (Layer layer : layers) { - sb.append(layer.toString("\t")); - } - return sb.toString(); + protected void onPostExecute(LottieComposition composition) { + loadedListener.onCompositionLoaded(composition); } + } - private static final class FileCompositionLoader extends CompositionLoader { - - private final Resources res; - private final OnCompositionLoadedListener loadedListener; + private static final class JsonCompositionLoader extends CompositionLoader { - FileCompositionLoader(Resources res, OnCompositionLoadedListener loadedListener) { - this.res = res; - this.loadedListener = loadedListener; - } - - @Override - protected LottieComposition doInBackground(InputStream... params) { - return fromInputStream(res, params[0]); - } + private final Resources res; + private final OnCompositionLoadedListener loadedListener; - @Override - protected void onPostExecute(LottieComposition composition) { - loadedListener.onCompositionLoaded(composition); - } + JsonCompositionLoader(Resources res, OnCompositionLoadedListener loadedListener) { + this.res = res; + this.loadedListener = loadedListener; } - private static final class JsonCompositionLoader extends CompositionLoader { - - private final Resources res; - private final OnCompositionLoadedListener loadedListener; - - JsonCompositionLoader(Resources res, OnCompositionLoadedListener loadedListener) { - this.res = res; - this.loadedListener = loadedListener; - } - - @Override - protected LottieComposition doInBackground(JSONObject... params) { - return fromJsonSync(res, params[0]); - } + @Override + protected LottieComposition doInBackground(JSONObject... params) { + return fromJsonSync(res, params[0]); + } - @Override - protected void onPostExecute(LottieComposition composition) { - loadedListener.onCompositionLoaded(composition); - } + @Override + protected void onPostExecute(LottieComposition composition) { + loadedListener.onCompositionLoaded(composition); } + } - private abstract static class CompositionLoader - extends AsyncTask - implements Cancellable { + private abstract static class CompositionLoader + extends AsyncTask + implements Cancellable { - @Override - public void cancel() { - cancel(true); - } + @Override + public void cancel() { + cancel(true); } + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/Mask.java b/lottie/src/main/java/com/airbnb/lottie/model/Mask.java index f7a0eaaebf..d72a7c35d4 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/Mask.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/Mask.java @@ -11,52 +11,52 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class Mask { - private enum MaskMode { - MaskModeAdd, - MaskModeSubtract, - MaskModeIntersect, - MaskModeUnknown - } - - private final MaskMode maskMode; - private final AnimatableShapeValue maskPath; - - Mask(JSONObject json, int frameRate, LottieComposition composition) { - try { - boolean closed = false; - if (json.has("cl")) { - closed = json.getBoolean("cl"); - } - String mode = json.getString("mode"); - switch (mode) { - case "a": - maskMode = MaskMode.MaskModeAdd; - break; - case "s": - maskMode = MaskMode.MaskModeSubtract; - break; - case "i": - maskMode = MaskMode.MaskModeIntersect; - break; - default: - maskMode = MaskMode.MaskModeUnknown; - } - - maskPath = new AnimatableShapeValue(json.getJSONObject("pt"), frameRate, composition, closed); - //noinspection unused - AnimatableIntegerValue opacity = new AnimatableIntegerValue(json.getJSONObject("o"), frameRate, composition, false, true); - // TODO: use this. - } catch (JSONException e) { - throw new IllegalArgumentException("Unable to parse mask. " + json, e); - } + private enum MaskMode { + MaskModeAdd, + MaskModeSubtract, + MaskModeIntersect, + MaskModeUnknown + } + + private final MaskMode maskMode; + private final AnimatableShapeValue maskPath; + + Mask(JSONObject json, int frameRate, LottieComposition composition) { + try { + boolean closed = false; + if (json.has("cl")) { + closed = json.getBoolean("cl"); + } + String mode = json.getString("mode"); + switch (mode) { + case "a": + maskMode = MaskMode.MaskModeAdd; + break; + case "s": + maskMode = MaskMode.MaskModeSubtract; + break; + case "i": + maskMode = MaskMode.MaskModeIntersect; + break; + default: + maskMode = MaskMode.MaskModeUnknown; + } + + maskPath = new AnimatableShapeValue(json.getJSONObject("pt"), frameRate, composition, closed); + //noinspection unused + AnimatableIntegerValue opacity = new AnimatableIntegerValue(json.getJSONObject("o"), frameRate, composition, false, true); + // TODO: use this. + } catch (JSONException e) { + throw new IllegalArgumentException("Unable to parse mask. " + json, e); } + } - MaskMode getMaskMode() { - return maskMode; - } + MaskMode getMaskMode() { + return maskMode; + } - public AnimatableShapeValue getMaskPath() { - return maskPath; - } + public AnimatableShapeValue getMaskPath() { + return maskPath; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/RectangleShape.java b/lottie/src/main/java/com/airbnb/lottie/model/RectangleShape.java index 330e63dd14..34f91aa000 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/RectangleShape.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/RectangleShape.java @@ -13,54 +13,54 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class RectangleShape { - private static final String TAG = RectangleShape.class.getSimpleName(); + private static final String TAG = RectangleShape.class.getSimpleName(); - private final AnimatablePathValue position; - private final AnimatablePointValue size; - private final AnimatableFloatValue cornerRadius; + private final AnimatablePathValue position; + private final AnimatablePointValue size; + private final AnimatableFloatValue cornerRadius; - RectangleShape(JSONObject json, int frameRate, LottieComposition composition) { - try { - JSONObject positionJson = json.getJSONObject("p"); - position = new AnimatablePathValue(positionJson, frameRate, composition); - } catch (JSONException e) { - throw new IllegalArgumentException("Unable to parse rectangle position.", e); - } - - try { - JSONObject cornerRadiusJson = json.getJSONObject("r"); - cornerRadius = new AnimatableFloatValue(cornerRadiusJson, frameRate, composition); - } catch (JSONException e) { - throw new IllegalArgumentException("Unable to parse rectangle corner radius.", e); - } - - try { - JSONObject sizeJson = json.getJSONObject("s"); - size = new AnimatablePointValue(sizeJson, frameRate, composition); - } catch (JSONException e) { - throw new IllegalArgumentException("Unable to parse rectangle size.", e); - } - - if (L.DBG) Log.d(TAG, "Parsed new rectangle " + toString()); + RectangleShape(JSONObject json, int frameRate, LottieComposition composition) { + try { + JSONObject positionJson = json.getJSONObject("p"); + position = new AnimatablePathValue(positionJson, frameRate, composition); + } catch (JSONException e) { + throw new IllegalArgumentException("Unable to parse rectangle position.", e); } - public AnimatableFloatValue getCornerRadius() { - return cornerRadius; + try { + JSONObject cornerRadiusJson = json.getJSONObject("r"); + cornerRadius = new AnimatableFloatValue(cornerRadiusJson, frameRate, composition); + } catch (JSONException e) { + throw new IllegalArgumentException("Unable to parse rectangle corner radius.", e); } - public AnimatablePointValue getSize() { - return size; + try { + JSONObject sizeJson = json.getJSONObject("s"); + size = new AnimatablePointValue(sizeJson, frameRate, composition); + } catch (JSONException e) { + throw new IllegalArgumentException("Unable to parse rectangle size.", e); } - public AnimatablePathValue getPosition() { - return position; - } + if (L.DBG) Log.d(TAG, "Parsed new rectangle " + toString()); + } - @Override - public String toString() { - return "RectangleShape{" + "cornerRadius=" + cornerRadius.getInitialValue() + - ", position=" + position + - ", size=" + size + - '}'; - } + public AnimatableFloatValue getCornerRadius() { + return cornerRadius; + } + + public AnimatablePointValue getSize() { + return size; + } + + public AnimatablePathValue getPosition() { + return position; + } + + @Override + public String toString() { + return "RectangleShape{" + "cornerRadius=" + cornerRadius.getInitialValue() + + ", position=" + position + + ", size=" + size + + '}'; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/ShapeData.java b/lottie/src/main/java/com/airbnb/lottie/model/ShapeData.java index 456129ea24..babeb9e22f 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/ShapeData.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/ShapeData.java @@ -12,65 +12,65 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ShapeData { - private final List curves = new ArrayList<>(); - private PointF initialPoint; + private final List curves = new ArrayList<>(); + private PointF initialPoint; - public void setInitialPoint(PointF initialPoint) { - this.initialPoint = initialPoint; - } + public void setInitialPoint(PointF initialPoint) { + this.initialPoint = initialPoint; + } - private void setInitialPoint(float x, float y) { - if (initialPoint == null) { - initialPoint = new PointF(); - } - initialPoint.set(x, y); + private void setInitialPoint(float x, float y) { + if (initialPoint == null) { + initialPoint = new PointF(); } + initialPoint.set(x, y); + } - public PointF getInitialPoint() { - return initialPoint; - } + public PointF getInitialPoint() { + return initialPoint; + } - public void addCurve(CubicCurveData curve) { - curves.add(curve); - } + public void addCurve(CubicCurveData curve) { + curves.add(curve); + } - public List getCurves() { - return curves; + public List getCurves() { + return curves; + } + + public void interpolateBetween(ShapeData shapeData1, ShapeData shapeData2, @FloatRange(from = 0f, to = 1f) float percentage) { + if (initialPoint == null) { + initialPoint = new PointF(); + } + if (!curves.isEmpty() && curves.size() != shapeData1.getCurves().size() && curves.size() != shapeData2.getCurves().size()) { + throw new IllegalStateException("Curves must have the same number of control points. This: " + getCurves().size() + + "\tShape 1: " + shapeData1.getCurves().size() + "\tShape 2: " + shapeData2.getCurves().size()); + } else if (curves.isEmpty()) { + for (int i = shapeData1.getCurves().size() - 1; i >= 0; i--) { + curves.add(new CubicCurveData()); + } } - public void interpolateBetween(ShapeData shapeData1, ShapeData shapeData2, @FloatRange(from=0f, to=1f) float percentage) { - if (initialPoint == null) { - initialPoint = new PointF(); - } - if (!curves.isEmpty() && curves.size() != shapeData1.getCurves().size() && curves.size() != shapeData2.getCurves().size()) { - throw new IllegalStateException("Curves must have the same number of control points. This: " + getCurves().size() + - "\tShape 1: " + shapeData1.getCurves().size() + "\tShape 2: " + shapeData2.getCurves().size()); - } else if (curves.isEmpty()) { - for (int i = shapeData1.getCurves().size() - 1; i >= 0; i--) { - curves.add(new CubicCurveData()); - } - } - - PointF initialPoint1 = shapeData1.getInitialPoint(); - PointF initialPoint2 = shapeData2.getInitialPoint(); - - setInitialPoint(lerp(initialPoint1.x, initialPoint2.x, percentage), lerp(initialPoint1.y, initialPoint2.y, percentage)); - - for (int i = curves.size() - 1; i >= 0 ; i--) { - CubicCurveData curve1 = shapeData1.getCurves().get(i); - CubicCurveData curve2 = shapeData2.getCurves().get(i); - - PointF cp11 = curve1.getControlPoint1(); - PointF cp21 = curve1.getControlPoint2(); - PointF vertex1 = curve1.getVertex(); - - PointF cp12 = curve2.getControlPoint1(); - PointF cp22 = curve2.getControlPoint2(); - PointF vertex2 = curve2.getVertex(); - - curves.get(i).setControlPoint1(lerp(cp11.x, cp12.x, percentage), lerp(cp11.y, cp12.y, percentage)); - curves.get(i).setControlPoint2(lerp(cp21.x, cp22.x, percentage), lerp(cp21.y, cp22.y, percentage)); - curves.get(i).setVertex(lerp(vertex1.x, vertex2.x, percentage), lerp(vertex1.y, vertex2.y, percentage)); - } + PointF initialPoint1 = shapeData1.getInitialPoint(); + PointF initialPoint2 = shapeData2.getInitialPoint(); + + setInitialPoint(lerp(initialPoint1.x, initialPoint2.x, percentage), lerp(initialPoint1.y, initialPoint2.y, percentage)); + + for (int i = curves.size() - 1; i >= 0; i--) { + CubicCurveData curve1 = shapeData1.getCurves().get(i); + CubicCurveData curve2 = shapeData2.getCurves().get(i); + + PointF cp11 = curve1.getControlPoint1(); + PointF cp21 = curve1.getControlPoint2(); + PointF vertex1 = curve1.getVertex(); + + PointF cp12 = curve2.getControlPoint1(); + PointF cp22 = curve2.getControlPoint2(); + PointF vertex2 = curve2.getVertex(); + + curves.get(i).setControlPoint1(lerp(cp11.x, cp12.x, percentage), lerp(cp11.y, cp12.y, percentage)); + curves.get(i).setControlPoint2(lerp(cp21.x, cp22.x, percentage), lerp(cp21.y, cp22.y, percentage)); + curves.get(i).setVertex(lerp(vertex1.x, vertex2.x, percentage), lerp(vertex1.y, vertex2.y, percentage)); } + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/ShapeFill.java b/lottie/src/main/java/com/airbnb/lottie/model/ShapeFill.java index 8009a5b55f..f6c6d6c02b 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/ShapeFill.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/ShapeFill.java @@ -12,54 +12,54 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ShapeFill { - private static final String TAG = ShapeFill.class.getSimpleName(); + private static final String TAG = ShapeFill.class.getSimpleName(); - private boolean fillEnabled; - private AnimatableColorValue color; - private AnimatableIntegerValue opacity; + private boolean fillEnabled; + private AnimatableColorValue color; + private AnimatableIntegerValue opacity; - ShapeFill(JSONObject json, int frameRate, LottieComposition composition) { - JSONObject jsonColor = null; - try { - jsonColor = json.getJSONObject("c"); - } catch (JSONException e) { - // Do nothing. - } - if (jsonColor != null) { - color = new AnimatableColorValue(jsonColor, frameRate, composition); - } - - JSONObject jsonOpacity = null; - try { - jsonOpacity = json.getJSONObject("o"); - } catch (JSONException e) { - // Do nothing. - } - if (jsonOpacity != null) { - opacity = new AnimatableIntegerValue(jsonOpacity, frameRate, composition, false, true); - } - - try { - fillEnabled = json.getBoolean("fillEnabled"); - } catch (JSONException e) { - // Do nothing. - } - if (L.DBG) Log.d(TAG, "Parsed new shape fill " + toString()); + ShapeFill(JSONObject json, int frameRate, LottieComposition composition) { + JSONObject jsonColor = null; + try { + jsonColor = json.getJSONObject("c"); + } catch (JSONException e) { + // Do nothing. } - - public AnimatableColorValue getColor() { - return color; + if (jsonColor != null) { + color = new AnimatableColorValue(jsonColor, frameRate, composition); } - public AnimatableIntegerValue getOpacity() { - return opacity; + JSONObject jsonOpacity = null; + try { + jsonOpacity = json.getJSONObject("o"); + } catch (JSONException e) { + // Do nothing. + } + if (jsonOpacity != null) { + opacity = new AnimatableIntegerValue(jsonOpacity, frameRate, composition, false, true); } - @Override - public String toString() { - return "ShapeFill{" + "color=" + Integer.toHexString(color.getInitialValue()) + - ", fillEnabled=" + fillEnabled + - ", opacity=" + opacity.getInitialValue() + - '}'; + try { + fillEnabled = json.getBoolean("fillEnabled"); + } catch (JSONException e) { + // Do nothing. } + if (L.DBG) Log.d(TAG, "Parsed new shape fill " + toString()); + } + + public AnimatableColorValue getColor() { + return color; + } + + public AnimatableIntegerValue getOpacity() { + return opacity; + } + + @Override + public String toString() { + return "ShapeFill{" + "color=" + Integer.toHexString(color.getInitialValue()) + + ", fillEnabled=" + fillEnabled + + ", opacity=" + opacity.getInitialValue() + + '}'; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/ShapeGroup.java b/lottie/src/main/java/com/airbnb/lottie/model/ShapeGroup.java index 1ad1b0dac1..caff2e95de 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/ShapeGroup.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/ShapeGroup.java @@ -14,86 +14,86 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ShapeGroup { - @Nullable - static Object shapeItemWithJson(JSONObject json, int framerate, LottieComposition composition) { - String type = null; - try { - type = json.getString("ty"); - } catch (JSONException e) { - // Do nothing. - } - if (type == null) { - throw new IllegalStateException("Shape has no type."); - } + @Nullable + static Object shapeItemWithJson(JSONObject json, int framerate, LottieComposition composition) { + String type = null; + try { + type = json.getString("ty"); + } catch (JSONException e) { + // Do nothing. + } + if (type == null) { + throw new IllegalStateException("Shape has no type."); + } - switch (type) { - case "gr": - return new ShapeGroup(json, framerate, composition); - case "st": - return new ShapeStroke(json, framerate, composition); - case "fl": - return new ShapeFill(json, framerate, composition); - case "tr": - return new ShapeTransform(json, framerate, composition); - case "sh": - return new ShapePath(json, framerate, composition); - case "el": - return new CircleShape(json, framerate, composition); - case "rc": - return new RectangleShape(json, framerate, composition); - case "tm": - return new ShapeTrimPath(json, framerate, composition); - } - return null; + switch (type) { + case "gr": + return new ShapeGroup(json, framerate, composition); + case "st": + return new ShapeStroke(json, framerate, composition); + case "fl": + return new ShapeFill(json, framerate, composition); + case "tr": + return new ShapeTransform(json, framerate, composition); + case "sh": + return new ShapePath(json, framerate, composition); + case "el": + return new CircleShape(json, framerate, composition); + case "rc": + return new RectangleShape(json, framerate, composition); + case "tm": + return new ShapeTrimPath(json, framerate, composition); } + return null; + } - private String name; - private final List items = new ArrayList<>(); + private String name; + private final List items = new ArrayList<>(); - private ShapeGroup(JSONObject json, int frameRate, LottieComposition composition) { - JSONArray jsonItems = null; - try { - jsonItems = json.getJSONArray("it"); - } catch (JSONException e) { - // Do nothing. - } - if (jsonItems == null) { - // Thought this was necessary but maybe not? - // throw new IllegalStateException("There are no items."); - jsonItems = new JSONArray(); - } + private ShapeGroup(JSONObject json, int frameRate, LottieComposition composition) { + JSONArray jsonItems = null; + try { + jsonItems = json.getJSONArray("it"); + } catch (JSONException e) { + // Do nothing. + } + if (jsonItems == null) { + // Thought this was necessary but maybe not? + // throw new IllegalStateException("There are no items."); + jsonItems = new JSONArray(); + } - try { - name = json.getString("nm"); - } catch (JSONException e) { - // Do nothing. - } + try { + name = json.getString("nm"); + } catch (JSONException e) { + // Do nothing. + } - for (int i = 0; i < jsonItems.length(); i++) { - JSONObject jsonItem = null; - try { - jsonItem = jsonItems.getJSONObject(i); - } catch (JSONException e) { - // Do nothing. - } - if (jsonItem == null) { - throw new IllegalStateException("Unable to get jsonItem"); - } + for (int i = 0; i < jsonItems.length(); i++) { + JSONObject jsonItem = null; + try { + jsonItem = jsonItems.getJSONObject(i); + } catch (JSONException e) { + // Do nothing. + } + if (jsonItem == null) { + throw new IllegalStateException("Unable to get jsonItem"); + } - Object newItem = shapeItemWithJson(jsonItem, frameRate, composition); - if (newItem != null) { - items.add(newItem); - } - } + Object newItem = shapeItemWithJson(jsonItem, frameRate, composition); + if (newItem != null) { + items.add(newItem); + } } + } - public List getItems() { - return items; - } + public List getItems() { + return items; + } - @Override - public String toString() { - return "ShapeGroup{" + "name='" + name + "\' Shapes: " + Arrays.toString(items.toArray()) + '}'; - } + @Override + public String toString() { + return "ShapeGroup{" + "name='" + name + "\' Shapes: " + Arrays.toString(items.toArray()) + '}'; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/ShapePath.java b/lottie/src/main/java/com/airbnb/lottie/model/ShapePath.java index d1f203376b..5bc5af838d 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/ShapePath.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/ShapePath.java @@ -11,52 +11,52 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ShapePath { - private static final String TAG = ShapePath.class.getSimpleName(); - - private final String name; - private final int index; - private AnimatableShapeValue shapePath; - - ShapePath(JSONObject json, int frameRate, LottieComposition composition) { - try { - index = json.getInt("ind"); - } catch (JSONException e) { - throw new IllegalArgumentException("ShapePath has no index.", e); - } - - try { - name = json.getString("nm"); - } catch (JSONException e) { - throw new IllegalArgumentException("Layer has no name.", e); - } - - boolean closed = false; - try { - closed = json.getBoolean("closed"); - } catch (JSONException e) { - // Do nothing. Bodymovin 4.4 moved "closed" to be "c" inside of the shape json itself. - } - - JSONObject shape; - try { - shape = json.getJSONObject("ks"); - shapePath = new AnimatableShapeValue(shape, frameRate, composition, closed); - } catch (JSONException e) { - // Ignore - } - - if (L.DBG) Log.d(TAG, "Parsed new shape path " + toString()); + private static final String TAG = ShapePath.class.getSimpleName(); + + private final String name; + private final int index; + private AnimatableShapeValue shapePath; + + ShapePath(JSONObject json, int frameRate, LottieComposition composition) { + try { + index = json.getInt("ind"); + } catch (JSONException e) { + throw new IllegalArgumentException("ShapePath has no index.", e); + } + + try { + name = json.getString("nm"); + } catch (JSONException e) { + throw new IllegalArgumentException("Layer has no name.", e); } - public AnimatableShapeValue getShapePath() { - return shapePath; + boolean closed = false; + try { + closed = json.getBoolean("closed"); + } catch (JSONException e) { + // Do nothing. Bodymovin 4.4 moved "closed" to be "c" inside of the shape json itself. } - @Override - public String toString() { - return "ShapePath{" + "name=" + name + - ", index=" + index + - ", hasAnimation=" + shapePath.hasAnimation() + - '}'; + JSONObject shape; + try { + shape = json.getJSONObject("ks"); + shapePath = new AnimatableShapeValue(shape, frameRate, composition, closed); + } catch (JSONException e) { + // Ignore } + + if (L.DBG) Log.d(TAG, "Parsed new shape path " + toString()); + } + + public AnimatableShapeValue getShapePath() { + return shapePath; + } + + @Override + public String toString() { + return "ShapePath{" + "name=" + name + + ", index=" + index + + ", hasAnimation=" + shapePath.hasAnimation() + + '}'; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/ShapeStroke.java b/lottie/src/main/java/com/airbnb/lottie/model/ShapeStroke.java index f2d6657bd7..964a58d762 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/ShapeStroke.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/ShapeStroke.java @@ -16,89 +16,89 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ShapeStroke { - public enum LineCapType { - Butt, - Round, - Unknown - } - - public enum LineJoinType { - Miter, - Round, - Bevel - } - - private AnimatableFloatValue offset; - private final List lineDashPattern = new ArrayList<>(); - - private final AnimatableColorValue color; - private final AnimatableIntegerValue opacity; - private final AnimatableFloatValue width; - private final LineCapType capType; - private final LineJoinType joinType; - - ShapeStroke(JSONObject json, int frameRate, LottieComposition composition) { - try { - JSONObject colorJson = json.getJSONObject("c"); - color = new AnimatableColorValue(colorJson, frameRate, composition); - - JSONObject widthJson = json.getJSONObject("w"); - width = new AnimatableFloatValue(widthJson, frameRate, composition); - - JSONObject opacityJson = json.getJSONObject("o"); - opacity = new AnimatableIntegerValue(opacityJson, frameRate, composition, false, true); - - capType = LineCapType.values()[json.getInt("lc") - 1]; - joinType = LineJoinType.values()[json.getInt("lj") - 1]; - - if (json.has("d")) { - JSONArray dashesJson = json.getJSONArray("d"); - for (int i = 0; i < dashesJson.length(); i++) { - JSONObject dashJson = dashesJson.getJSONObject(i); - String n = dashJson.getString("n"); - if (n.equals("o")) { - JSONObject value = dashJson.getJSONObject("v"); - offset = new AnimatableFloatValue(value, frameRate, composition); - } else if (n.equals("d") || n.equals("g")) { - JSONObject value = dashJson.getJSONObject("v"); - lineDashPattern.add(new AnimatableFloatValue(value, frameRate, composition)); - } - } - if (lineDashPattern.size() == 1) { - // If there is only 1 value then it is assumed to be equal parts on and off. - lineDashPattern.add(lineDashPattern.get(0)); - } - } - } catch (JSONException e) { - throw new IllegalArgumentException("Unable to parse stroke " + json, e); + public enum LineCapType { + Butt, + Round, + Unknown + } + + public enum LineJoinType { + Miter, + Round, + Bevel + } + + private AnimatableFloatValue offset; + private final List lineDashPattern = new ArrayList<>(); + + private final AnimatableColorValue color; + private final AnimatableIntegerValue opacity; + private final AnimatableFloatValue width; + private final LineCapType capType; + private final LineJoinType joinType; + + ShapeStroke(JSONObject json, int frameRate, LottieComposition composition) { + try { + JSONObject colorJson = json.getJSONObject("c"); + color = new AnimatableColorValue(colorJson, frameRate, composition); + + JSONObject widthJson = json.getJSONObject("w"); + width = new AnimatableFloatValue(widthJson, frameRate, composition); + + JSONObject opacityJson = json.getJSONObject("o"); + opacity = new AnimatableIntegerValue(opacityJson, frameRate, composition, false, true); + + capType = LineCapType.values()[json.getInt("lc") - 1]; + joinType = LineJoinType.values()[json.getInt("lj") - 1]; + + if (json.has("d")) { + JSONArray dashesJson = json.getJSONArray("d"); + for (int i = 0; i < dashesJson.length(); i++) { + JSONObject dashJson = dashesJson.getJSONObject(i); + String n = dashJson.getString("n"); + if (n.equals("o")) { + JSONObject value = dashJson.getJSONObject("v"); + offset = new AnimatableFloatValue(value, frameRate, composition); + } else if (n.equals("d") || n.equals("g")) { + JSONObject value = dashJson.getJSONObject("v"); + lineDashPattern.add(new AnimatableFloatValue(value, frameRate, composition)); + } + } + if (lineDashPattern.size() == 1) { + // If there is only 1 value then it is assumed to be equal parts on and off. + lineDashPattern.add(lineDashPattern.get(0)); } + } + } catch (JSONException e) { + throw new IllegalArgumentException("Unable to parse stroke " + json, e); } + } - public AnimatableColorValue getColor() { - return color; - } + public AnimatableColorValue getColor() { + return color; + } - public AnimatableIntegerValue getOpacity() { - return opacity; - } + public AnimatableIntegerValue getOpacity() { + return opacity; + } - public AnimatableFloatValue getWidth() { - return width; - } + public AnimatableFloatValue getWidth() { + return width; + } - public List getLineDashPattern() { - return lineDashPattern; - } + public List getLineDashPattern() { + return lineDashPattern; + } - public AnimatableFloatValue getDashOffset() { - return offset; - } + public AnimatableFloatValue getDashOffset() { + return offset; + } - public LineCapType getCapType() { - return capType; - } + public LineCapType getCapType() { + return capType; + } - public LineJoinType getJoinType() { - return joinType; - } + public LineJoinType getJoinType() { + return joinType; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/ShapeTransform.java b/lottie/src/main/java/com/airbnb/lottie/model/ShapeTransform.java index 399922ea88..896fa7ad94 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/ShapeTransform.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/ShapeTransform.java @@ -15,102 +15,102 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ShapeTransform implements Transform { - private static final String TAG = ShapeTransform.class.getSimpleName(); - - private final Rect compBounds; - private final AnimatablePathValue position; - private final AnimatablePathValue anchor; - private final AnimatableScaleValue scale; - private final AnimatableFloatValue rotation; - private final AnimatableIntegerValue opacity; - - public ShapeTransform(LottieComposition composition) { - this.compBounds = composition.getBounds(); - this.position = new AnimatablePathValue(composition); - this.anchor = new AnimatablePathValue(composition); - this.scale = new AnimatableScaleValue(composition); - this.rotation = new AnimatableFloatValue(composition, 0f); - this.opacity = new AnimatableIntegerValue(composition, 255); + private static final String TAG = ShapeTransform.class.getSimpleName(); + + private final Rect compBounds; + private final AnimatablePathValue position; + private final AnimatablePathValue anchor; + private final AnimatableScaleValue scale; + private final AnimatableFloatValue rotation; + private final AnimatableIntegerValue opacity; + + public ShapeTransform(LottieComposition composition) { + this.compBounds = composition.getBounds(); + this.position = new AnimatablePathValue(composition); + this.anchor = new AnimatablePathValue(composition); + this.scale = new AnimatableScaleValue(composition); + this.rotation = new AnimatableFloatValue(composition, 0f); + this.opacity = new AnimatableIntegerValue(composition, 255); + } + + ShapeTransform(JSONObject json, int frameRate, LottieComposition composition) { + this.compBounds = composition.getBounds(); + + JSONObject jsonPosition; + try { + jsonPosition = json.getJSONObject("p"); + } catch (JSONException e) { + throw new IllegalStateException("Transform has no position."); } + position = new AnimatablePathValue(jsonPosition, frameRate, composition); - ShapeTransform(JSONObject json, int frameRate, LottieComposition composition) { - this.compBounds = composition.getBounds(); - - JSONObject jsonPosition; - try { - jsonPosition = json.getJSONObject("p"); - } catch (JSONException e) { - throw new IllegalStateException("Transform has no position."); - } - position = new AnimatablePathValue(jsonPosition, frameRate, composition); - - JSONObject jsonAnchor; - try { - jsonAnchor = json.getJSONObject("a"); - } catch (JSONException e) { - throw new IllegalStateException("Transform has no anchor."); - } - anchor = new AnimatablePathValue(jsonAnchor, frameRate, composition); - - JSONObject jsonScale; - try { - jsonScale = json.getJSONObject("s"); - } catch (JSONException e) { - throw new IllegalStateException("Transform has no scale."); - } - scale = new AnimatableScaleValue(jsonScale, frameRate, composition, false); - - JSONObject jsonRotation; - try { - jsonRotation = json.getJSONObject("r"); - } catch (JSONException e) { - throw new IllegalStateException("Transform has no rotation."); - } - rotation = new AnimatableFloatValue(jsonRotation, frameRate, composition, false); - - JSONObject jsonOpacity; - try { - jsonOpacity = json.getJSONObject("o"); - } catch (JSONException e) { - throw new IllegalStateException("Transform has no opacity."); - } - opacity = new AnimatableIntegerValue(jsonOpacity, frameRate, composition, false, true); - - if (L.DBG) Log.d(TAG, "Parsed new shape transform " + toString()); + JSONObject jsonAnchor; + try { + jsonAnchor = json.getJSONObject("a"); + } catch (JSONException e) { + throw new IllegalStateException("Transform has no anchor."); } + anchor = new AnimatablePathValue(jsonAnchor, frameRate, composition); - public Rect getBounds() { - return compBounds; + JSONObject jsonScale; + try { + jsonScale = json.getJSONObject("s"); + } catch (JSONException e) { + throw new IllegalStateException("Transform has no scale."); } + scale = new AnimatableScaleValue(jsonScale, frameRate, composition, false); - public AnimatablePathValue getPosition() { - return position; + JSONObject jsonRotation; + try { + jsonRotation = json.getJSONObject("r"); + } catch (JSONException e) { + throw new IllegalStateException("Transform has no rotation."); } + rotation = new AnimatableFloatValue(jsonRotation, frameRate, composition, false); - public AnimatablePathValue getAnchor() { - return anchor; - } - - public AnimatableScaleValue getScale() { - return scale; - } - - public AnimatableFloatValue getRotation() { - return rotation; - } - - public AnimatableIntegerValue getOpacity() { - return opacity; - } - - @Override - public String toString() { - return "ShapeTransform{" + "anchor=" + anchor.toString() + - ", compBounds=" + compBounds + - ", position=" + position.toString() + - ", scale=" + scale.toString() + - ", rotation=" + rotation.getInitialValue() + - ", opacity=" + opacity.getInitialValue() + - '}'; + JSONObject jsonOpacity; + try { + jsonOpacity = json.getJSONObject("o"); + } catch (JSONException e) { + throw new IllegalStateException("Transform has no opacity."); } + opacity = new AnimatableIntegerValue(jsonOpacity, frameRate, composition, false, true); + + if (L.DBG) Log.d(TAG, "Parsed new shape transform " + toString()); + } + + public Rect getBounds() { + return compBounds; + } + + public AnimatablePathValue getPosition() { + return position; + } + + public AnimatablePathValue getAnchor() { + return anchor; + } + + public AnimatableScaleValue getScale() { + return scale; + } + + public AnimatableFloatValue getRotation() { + return rotation; + } + + public AnimatableIntegerValue getOpacity() { + return opacity; + } + + @Override + public String toString() { + return "ShapeTransform{" + "anchor=" + anchor.toString() + + ", compBounds=" + compBounds + + ", position=" + position.toString() + + ", scale=" + scale.toString() + + ", rotation=" + rotation.getInitialValue() + + ", opacity=" + opacity.getInitialValue() + + '}'; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/ShapeTrimPath.java b/lottie/src/main/java/com/airbnb/lottie/model/ShapeTrimPath.java index 6af63ba83f..e43423fa49 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/ShapeTrimPath.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/ShapeTrimPath.java @@ -9,34 +9,34 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ShapeTrimPath { - private final AnimatableFloatValue start; - private final AnimatableFloatValue end; - private final AnimatableFloatValue offset; - - ShapeTrimPath(JSONObject json, int frameRate, LottieComposition composition) { - try { - start = new AnimatableFloatValue(json.getJSONObject("s"), frameRate, composition, false); - end = new AnimatableFloatValue(json.getJSONObject("e"), frameRate, composition, false); - offset = new AnimatableFloatValue(json.getJSONObject("o"), frameRate, composition, false); - } catch (JSONException e) { - throw new IllegalArgumentException("Unable to parse trim path " + json, e); - } + private final AnimatableFloatValue start; + private final AnimatableFloatValue end; + private final AnimatableFloatValue offset; + + ShapeTrimPath(JSONObject json, int frameRate, LottieComposition composition) { + try { + start = new AnimatableFloatValue(json.getJSONObject("s"), frameRate, composition, false); + end = new AnimatableFloatValue(json.getJSONObject("e"), frameRate, composition, false); + offset = new AnimatableFloatValue(json.getJSONObject("o"), frameRate, composition, false); + } catch (JSONException e) { + throw new IllegalArgumentException("Unable to parse trim path " + json, e); } + } - public AnimatableFloatValue getEnd() { - return end; - } + public AnimatableFloatValue getEnd() { + return end; + } - public AnimatableFloatValue getStart() { - return start; - } + public AnimatableFloatValue getStart() { + return start; + } - public AnimatableFloatValue getOffset() { - return offset; - } + public AnimatableFloatValue getOffset() { + return offset; + } - @Override - public String toString() { - return "Trim Path: {start: " + start + ", end: " + end + ", offset: " + offset + "}"; - } + @Override + public String toString() { + return "Trim Path: {start: " + start + ", end: " + end + ", offset: " + offset + "}"; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/model/Transform.java b/lottie/src/main/java/com/airbnb/lottie/model/Transform.java index f600473e10..1e28f21bf8 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/Transform.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/Transform.java @@ -10,15 +10,15 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public interface Transform { - Rect getBounds(); + Rect getBounds(); - AnimatablePathValue getPosition(); + AnimatablePathValue getPosition(); - AnimatablePathValue getAnchor(); + AnimatablePathValue getAnchor(); - AnimatableScaleValue getScale(); + AnimatableScaleValue getScale(); - AnimatableFloatValue getRotation(); + AnimatableFloatValue getRotation(); - AnimatableIntegerValue getOpacity(); + AnimatableIntegerValue getOpacity(); } \ No newline at end of file diff --git a/lottie/src/main/java/com/airbnb/lottie/utils/JsonUtils.java b/lottie/src/main/java/com/airbnb/lottie/utils/JsonUtils.java index 7ffbc29096..456baeb3e3 100644 --- a/lottie/src/main/java/com/airbnb/lottie/utils/JsonUtils.java +++ b/lottie/src/main/java/com/airbnb/lottie/utils/JsonUtils.java @@ -11,49 +11,49 @@ public class JsonUtils { - private JsonUtils() { + private JsonUtils() { + } + + public static PointF pointValueFromJsonObject(JSONObject values, float scale) { + PointF point = new PointF(); + try { + Object x = values.get("x"); + if (x instanceof Float) { + point.x = (float) x; + } else if (x instanceof Integer) { + point.x = (Integer) x; + } else if (x instanceof Double) { + point.x = (float) (double) x; + } else if (x instanceof JSONArray) { + point.x = (float) ((JSONArray) x).getDouble(0); + } + + Object y = values.get("y"); + if (y instanceof Float) { + point.y = (float) y; + } else if (y instanceof Integer) { + point.y = (Integer) y; + } else if (y instanceof Double) { + point.y = (float) (double) y; + } else if (y instanceof JSONArray) { + point.y = (float) ((JSONArray) y).getDouble(0); + } + } catch (JSONException e) { + throw new IllegalArgumentException("Unable to parse point " + values, e); } - - public static PointF pointValueFromJsonObject(JSONObject values, float scale) { - PointF point = new PointF(); - try { - Object x = values.get("x"); - if (x instanceof Float) { - point.x = (float) x; - } else if (x instanceof Integer) { - point.x = (Integer) x; - } else if (x instanceof Double) { - point.x = (float) (double) x; - } else if (x instanceof JSONArray) { - point.x = (float) ((JSONArray) x).getDouble(0); - } - - Object y = values.get("y"); - if (y instanceof Float) { - point.y = (float) y; - } else if (y instanceof Integer) { - point.y = (Integer) y; - } else if (y instanceof Double) { - point.y = (float) (double) y; - } else if (y instanceof JSONArray) { - point.y = (float) ((JSONArray) y).getDouble(0); - } - } catch (JSONException e) { - throw new IllegalArgumentException("Unable to parse point " + values, e); - } - point.x *= scale; - point.y *= scale; - return point; + point.x *= scale; + point.y *= scale; + return point; + } + + public static PointF pointFromJsonArray(JSONArray values, float scale) { + if (values.length() < 2) { + throw new IllegalArgumentException("Unable to parse point for " + values); } - - public static PointF pointFromJsonArray(JSONArray values, float scale) { - if (values.length() < 2) { - throw new IllegalArgumentException("Unable to parse point for " + values); - } - try { - return new PointF((float) values.getDouble(0) * scale, (float) values.getDouble(1) * scale); - } catch (JSONException e) { - throw new IllegalArgumentException("Unable to parse point for " + values, e); - } + try { + return new PointF((float) values.getDouble(0) * scale, (float) values.getDouble(1) * scale); + } catch (JSONException e) { + throw new IllegalArgumentException("Unable to parse point for " + values, e); } + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/utils/MiscUtils.java b/lottie/src/main/java/com/airbnb/lottie/utils/MiscUtils.java index a207c5ce79..0cb4f45d8e 100644 --- a/lottie/src/main/java/com/airbnb/lottie/utils/MiscUtils.java +++ b/lottie/src/main/java/com/airbnb/lottie/utils/MiscUtils.java @@ -11,27 +11,27 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class MiscUtils { - public static PointF addPoints(PointF p1, PointF p2) { - return new PointF(p1.x + p2.x, p1.y + p2.y); - } + public static PointF addPoints(PointF p1, PointF p2) { + return new PointF(p1.x + p2.x, p1.y + p2.y); + } - public static void getPathFromData(ShapeData shapeData, Path outPath) { - outPath.reset(); - PointF initialPoint = shapeData.getInitialPoint(); - outPath.moveTo(initialPoint.x, initialPoint.y); - for (int i = 0; i < shapeData.getCurves().size(); i++) { - CubicCurveData curveData = shapeData.getCurves().get(i); - outPath.cubicTo(curveData.getControlPoint1().x, curveData.getControlPoint1().y, - curveData.getControlPoint2().x, curveData.getControlPoint2().y, - curveData.getVertex().x, curveData.getVertex().y); - } + public static void getPathFromData(ShapeData shapeData, Path outPath) { + outPath.reset(); + PointF initialPoint = shapeData.getInitialPoint(); + outPath.moveTo(initialPoint.x, initialPoint.y); + for (int i = 0; i < shapeData.getCurves().size(); i++) { + CubicCurveData curveData = shapeData.getCurves().get(i); + outPath.cubicTo(curveData.getControlPoint1().x, curveData.getControlPoint1().y, + curveData.getControlPoint2().x, curveData.getControlPoint2().y, + curveData.getVertex().x, curveData.getVertex().y); } + } - public static float lerp(float a, float b, @FloatRange(from = 0f, to = 1f) float percentage) { - return a + percentage * (b - a); - } + public static float lerp(float a, float b, @FloatRange(from = 0f, to = 1f) float percentage) { + return a + percentage * (b - a); + } - public static int lerp(int a, int b, @FloatRange(from = 0f, to = 1f) float percentage) { - return (int) (a + percentage * (b - a)); - } + public static int lerp(int a, int b, @FloatRange(from = 0f, to = 1f) float percentage) { + return (int) (a + percentage * (b - a)); + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/utils/ScaleXY.java b/lottie/src/main/java/com/airbnb/lottie/utils/ScaleXY.java index a2cbb29e82..48647396bc 100644 --- a/lottie/src/main/java/com/airbnb/lottie/utils/ScaleXY.java +++ b/lottie/src/main/java/com/airbnb/lottie/utils/ScaleXY.java @@ -4,29 +4,29 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ScaleXY { - private float scaleX = 1f; - private float scaleY = 1f; + private float scaleX = 1f; + private float scaleY = 1f; - public ScaleXY scale(float sx, float sy) { - this.scaleX = sx; - this.scaleY = sy; - return this; - } + public ScaleXY scale(float sx, float sy) { + this.scaleX = sx; + this.scaleY = sy; + return this; + } - public float getScaleX() { - return scaleX; - } + public float getScaleX() { + return scaleX; + } - public float getScaleY() { - return scaleY; - } + public float getScaleY() { + return scaleY; + } - public boolean isDefault() { - return scaleX == 1f && scaleY == 1f; - } + public boolean isDefault() { + return scaleX == 1f && scaleY == 1f; + } - @Override - public String toString() { - return getScaleX() + "x" + getScaleY(); - } + @Override + public String toString() { + return getScaleX() + "x" + getScaleY(); + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/utils/SegmentedPath.java b/lottie/src/main/java/com/airbnb/lottie/utils/SegmentedPath.java index 3224500a7e..434aa3ba56 100644 --- a/lottie/src/main/java/com/airbnb/lottie/utils/SegmentedPath.java +++ b/lottie/src/main/java/com/airbnb/lottie/utils/SegmentedPath.java @@ -10,45 +10,45 @@ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class SegmentedPath { - private final List segments = new ArrayList<>(); - private final PointF currentPoint = new PointF(); - - public void moveTo(float x, float y) { - currentPoint.set(x, y); - } - - public void lineTo(float x, float y) { - Path path = new Path(); - // This is a small hack that ensures that an actual path will get created. - // Without this, a lineTo call to the current point will end up creating an empty path - // which breaks animations. - if (currentPoint.x == x && currentPoint.y == y) { - x += 0.01f; - y += 0.01f; - } - path.moveTo(currentPoint.x, currentPoint.y); - path.lineTo(x, y); - segments.add(path); - currentPoint.set(x, y); - } - - public void cubicTo (float x1, float y1, float x2, float y2, float x3, float y3) { - Path path = new Path(); - path.moveTo(currentPoint.x, currentPoint.y); - path.cubicTo(x1, y1, x2, y2, x3, y3); - segments.add(path); - currentPoint.set(x3, y3); - } - - public Path getSegment(int index) { - return segments.get(index); - } - - public int getSegmentCount() { - return segments.size(); - } - - public boolean hasSegments() { - return !segments.isEmpty(); + private final List segments = new ArrayList<>(); + private final PointF currentPoint = new PointF(); + + public void moveTo(float x, float y) { + currentPoint.set(x, y); + } + + public void lineTo(float x, float y) { + Path path = new Path(); + // This is a small hack that ensures that an actual path will get created. + // Without this, a lineTo call to the current point will end up creating an empty path + // which breaks animations. + if (currentPoint.x == x && currentPoint.y == y) { + x += 0.01f; + y += 0.01f; } + path.moveTo(currentPoint.x, currentPoint.y); + path.lineTo(x, y); + segments.add(path); + currentPoint.set(x, y); + } + + public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { + Path path = new Path(); + path.moveTo(currentPoint.x, currentPoint.y); + path.cubicTo(x1, y1, x2, y2, x3, y3); + segments.add(path); + currentPoint.set(x3, y3); + } + + public Path getSegment(int index) { + return segments.get(index); + } + + public int getSegmentCount() { + return segments.size(); + } + + public boolean hasSegments() { + return !segments.isEmpty(); + } } diff --git a/lottie/src/test/java/com/airbnb/lottie/KeyframeAnimationTest.java b/lottie/src/test/java/com/airbnb/lottie/KeyframeAnimationTest.java index 035ce86a9f..27c1acfdc6 100644 --- a/lottie/src/test/java/com/airbnb/lottie/KeyframeAnimationTest.java +++ b/lottie/src/test/java/com/airbnb/lottie/KeyframeAnimationTest.java @@ -22,31 +22,31 @@ @Config(manifest = Config.NONE) public class KeyframeAnimationTest { - @Test - public void simpleAnimation() { - List keyTimes = Arrays.asList(0f, 0.5f, 1f); - List values = Arrays.asList(0f, 1f, 10f); - - NumberKeyframeAnimation animation = new NumberKeyframeAnimation<>( - 1000, - new LottieComposition(1000), - keyTimes, - Float.class, - values, - new ArrayList(1) {{ - add(new LinearInterpolator()); - add(new LinearInterpolator()); - }}); - - animation.setProgress(0f); - assertEquals(animation.getValue(), 0f); - animation.setProgress(0.25f); - assertEquals(animation.getValue(), 0.5f); - animation.setProgress(0.5f); - assertEquals(animation.getValue(), 1f); - animation.setProgress(0.75f); - assertEquals(animation.getValue(), 5.5f); - animation.setProgress(1f); - assertEquals(animation.getValue(), 10f); - } + @Test + public void simpleAnimation() { + List keyTimes = Arrays.asList(0f, 0.5f, 1f); + List values = Arrays.asList(0f, 1f, 10f); + + NumberKeyframeAnimation animation = new NumberKeyframeAnimation<>( + 1000, + new LottieComposition(1000), + keyTimes, + Float.class, + values, + new ArrayList(1) {{ + add(new LinearInterpolator()); + add(new LinearInterpolator()); + }}); + + animation.setProgress(0f); + assertEquals(animation.getValue(), 0f); + animation.setProgress(0.25f); + assertEquals(animation.getValue(), 0.5f); + animation.setProgress(0.5f); + assertEquals(animation.getValue(), 1f); + animation.setProgress(0.75f); + assertEquals(animation.getValue(), 5.5f); + animation.setProgress(1f); + assertEquals(animation.getValue(), 10f); + } } \ No newline at end of file