| /* | |
| * Copyright (C) 2014 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "Animator.h" | |
| #include <inttypes.h> | |
| #include <set> | |
| #include "AnimationContext.h" | |
| #include "Interpolator.h" | |
| #include "RenderNode.h" | |
| #include "RenderProperties.h" | |
| namespace android { | |
| namespace uirenderer { | |
| /************************************************************ | |
| * BaseRenderNodeAnimator | |
| ************************************************************/ | |
| BaseRenderNodeAnimator::BaseRenderNodeAnimator(float finalValue) | |
| : mTarget(nullptr) | |
| , mStagingTarget(nullptr) | |
| , mFinalValue(finalValue) | |
| , mDeltaValue(0) | |
| , mFromValue(0) | |
| , mStagingPlayState(PlayState::NotStarted) | |
| , mPlayState(PlayState::NotStarted) | |
| , mHasStartValue(false) | |
| , mStartTime(0) | |
| , mDuration(300) | |
| , mStartDelay(0) | |
| , mMayRunAsync(true) | |
| , mPlayTime(0) {} | |
| BaseRenderNodeAnimator::~BaseRenderNodeAnimator() {} | |
| void BaseRenderNodeAnimator::checkMutable() { | |
| // Should be impossible to hit as the Java-side also has guards for this | |
| LOG_ALWAYS_FATAL_IF(mStagingPlayState != PlayState::NotStarted, | |
| "Animator has already been started!"); | |
| } | |
| void BaseRenderNodeAnimator::setInterpolator(Interpolator* interpolator) { | |
| checkMutable(); | |
| mInterpolator.reset(interpolator); | |
| } | |
| void BaseRenderNodeAnimator::setStartValue(float value) { | |
| checkMutable(); | |
| doSetStartValue(value); | |
| } | |
| void BaseRenderNodeAnimator::doSetStartValue(float value) { | |
| mFromValue = value; | |
| mDeltaValue = (mFinalValue - mFromValue); | |
| mHasStartValue = true; | |
| } | |
| void BaseRenderNodeAnimator::setDuration(nsecs_t duration) { | |
| checkMutable(); | |
| mDuration = duration; | |
| } | |
| void BaseRenderNodeAnimator::setStartDelay(nsecs_t startDelay) { | |
| checkMutable(); | |
| mStartDelay = startDelay; | |
| } | |
| void BaseRenderNodeAnimator::attach(RenderNode* target) { | |
| mStagingTarget = target; | |
| onAttached(); | |
| } | |
| void BaseRenderNodeAnimator::start() { | |
| mStagingPlayState = PlayState::Running; | |
| mStagingRequests.push_back(Request::Start); | |
| onStagingPlayStateChanged(); | |
| } | |
| void BaseRenderNodeAnimator::cancel() { | |
| mStagingPlayState = PlayState::Finished; | |
| mStagingRequests.push_back(Request::Cancel); | |
| onStagingPlayStateChanged(); | |
| } | |
| void BaseRenderNodeAnimator::reset() { | |
| mStagingPlayState = PlayState::Finished; | |
| mStagingRequests.push_back(Request::Reset); | |
| onStagingPlayStateChanged(); | |
| } | |
| void BaseRenderNodeAnimator::reverse() { | |
| mStagingPlayState = PlayState::Reversing; | |
| mStagingRequests.push_back(Request::Reverse); | |
| onStagingPlayStateChanged(); | |
| } | |
| void BaseRenderNodeAnimator::end() { | |
| mStagingPlayState = PlayState::Finished; | |
| mStagingRequests.push_back(Request::End); | |
| onStagingPlayStateChanged(); | |
| } | |
| void BaseRenderNodeAnimator::resolveStagingRequest(Request request) { | |
| switch (request) { | |
| case Request::Start: | |
| mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) | |
| ? mPlayTime | |
| : 0; | |
| mPlayState = PlayState::Running; | |
| mPendingActionUponFinish = Action::None; | |
| break; | |
| case Request::Reverse: | |
| mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) | |
| ? mPlayTime | |
| : mDuration; | |
| mPlayState = PlayState::Reversing; | |
| mPendingActionUponFinish = Action::None; | |
| break; | |
| case Request::Reset: | |
| mPlayTime = 0; | |
| mPlayState = PlayState::Finished; | |
| mPendingActionUponFinish = Action::Reset; | |
| break; | |
| case Request::Cancel: | |
| mPlayState = PlayState::Finished; | |
| mPendingActionUponFinish = Action::None; | |
| break; | |
| case Request::End: | |
| mPlayTime = mPlayState == PlayState::Reversing ? 0 : mDuration; | |
| mPlayState = PlayState::Finished; | |
| mPendingActionUponFinish = Action::End; | |
| break; | |
| default: | |
| LOG_ALWAYS_FATAL("Invalid staging request: %d", static_cast<int>(request)); | |
| }; | |
| } | |
| void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) { | |
| if (mStagingTarget) { | |
| RenderNode* oldTarget = mTarget; | |
| mTarget = mStagingTarget; | |
| mStagingTarget = nullptr; | |
| if (oldTarget && oldTarget != mTarget) { | |
| oldTarget->onAnimatorTargetChanged(this); | |
| } | |
| } | |
| if (!mStagingRequests.empty()) { | |
| // No interpolator was set, use the default | |
| if (mPlayState == PlayState::NotStarted && !mInterpolator) { | |
| mInterpolator.reset(Interpolator::createDefaultInterpolator()); | |
| } | |
| // Keep track of the play state and play time before they are changed when | |
| // staging requests are resolved. | |
| nsecs_t currentPlayTime = mPlayTime; | |
| PlayState prevFramePlayState = mPlayState; | |
| // Resolve staging requests one by one. | |
| for (Request request : mStagingRequests) { | |
| resolveStagingRequest(request); | |
| } | |
| mStagingRequests.clear(); | |
| if (mStagingPlayState == PlayState::Finished) { | |
| callOnFinishedListener(context); | |
| } else if (mStagingPlayState == PlayState::Running || | |
| mStagingPlayState == PlayState::Reversing) { | |
| bool changed = currentPlayTime != mPlayTime || prevFramePlayState != mStagingPlayState; | |
| if (prevFramePlayState != mStagingPlayState) { | |
| transitionToRunning(context); | |
| } | |
| if (changed) { | |
| // Now we need to seek to the stagingPlayTime (i.e. the animation progress that was | |
| // requested from UI thread). It is achieved by modifying mStartTime, such that | |
| // current time - mStartTime = stagingPlayTime (or mDuration -stagingPlayTime in the | |
| // case of reversing) | |
| nsecs_t currentFrameTime = context.frameTimeMs(); | |
| if (mPlayState == PlayState::Reversing) { | |
| // Reverse is not supported for animations with a start delay, so here we | |
| // assume no start delay. | |
| mStartTime = currentFrameTime - (mDuration - mPlayTime); | |
| } else { | |
| // Animation should play forward | |
| if (mPlayTime == 0) { | |
| // If the request is to start from the beginning, include start delay. | |
| mStartTime = currentFrameTime + mStartDelay; | |
| } else { | |
| // If the request is to seek to a non-zero play time, then we skip start | |
| // delay. | |
| mStartTime = currentFrameTime - mPlayTime; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| onPushStaging(); | |
| } | |
| void BaseRenderNodeAnimator::transitionToRunning(AnimationContext& context) { | |
| nsecs_t frameTimeMs = context.frameTimeMs(); | |
| LOG_ALWAYS_FATAL_IF(frameTimeMs <= 0, "%" PRId64 " isn't a real frame time!", frameTimeMs); | |
| if (mStartDelay < 0 || mStartDelay > 50000) { | |
| ALOGW("Your start delay is strange and confusing: %" PRId64, mStartDelay); | |
| } | |
| mStartTime = frameTimeMs + mStartDelay; | |
| if (mStartTime < 0) { | |
| ALOGW("Ended up with a really weird start time of %" PRId64 " with frame time %" PRId64 | |
| " and start delay %" PRId64, | |
| mStartTime, frameTimeMs, mStartDelay); | |
| // Set to 0 so that the animate() basically instantly finishes | |
| mStartTime = 0; | |
| } | |
| if (mDuration < 0) { | |
| ALOGW("Your duration is strange and confusing: %" PRId64, mDuration); | |
| } | |
| } | |
| bool BaseRenderNodeAnimator::animate(AnimationContext& context) { | |
| if (mPlayState < PlayState::Running) { | |
| return false; | |
| } | |
| if (mPlayState == PlayState::Finished) { | |
| if (mPendingActionUponFinish == Action::Reset) { | |
| // Skip to start. | |
| updatePlayTime(0); | |
| } else if (mPendingActionUponFinish == Action::End) { | |
| // Skip to end. | |
| updatePlayTime(mDuration); | |
| } | |
| // Reset pending action. | |
| mPendingActionUponFinish = Action::None; | |
| return true; | |
| } | |
| // This should be set before setValue() so animators can query this time when setValue | |
| // is called. | |
| nsecs_t currentPlayTime = context.frameTimeMs() - mStartTime; | |
| bool finished = updatePlayTime(currentPlayTime); | |
| if (finished && mPlayState != PlayState::Finished) { | |
| mPlayState = PlayState::Finished; | |
| callOnFinishedListener(context); | |
| } | |
| return finished; | |
| } | |
| bool BaseRenderNodeAnimator::updatePlayTime(nsecs_t playTime) { | |
| mPlayTime = mPlayState == PlayState::Reversing ? mDuration - playTime : playTime; | |
| onPlayTimeChanged(mPlayTime); | |
| // If BaseRenderNodeAnimator is handling the delay (not typical), then | |
| // because the staging properties reflect the final value, we always need | |
| // to call setValue even if the animation isn't yet running or is still | |
| // being delayed as we need to override the staging value | |
| if (playTime < 0) { | |
| return false; | |
| } | |
| if (!this->mHasStartValue) { | |
| doSetStartValue(getValue(mTarget)); | |
| } | |
| float fraction = 1.0f; | |
| if ((mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) && mDuration > 0) { | |
| fraction = mPlayTime / (float)mDuration; | |
| } | |
| fraction = MathUtils::clamp(fraction, 0.0f, 1.0f); | |
| fraction = mInterpolator->interpolate(fraction); | |
| setValue(mTarget, mFromValue + (mDeltaValue * fraction)); | |
| return playTime >= mDuration; | |
| } | |
| nsecs_t BaseRenderNodeAnimator::getRemainingPlayTime() { | |
| return mPlayState == PlayState::Reversing ? mPlayTime : mDuration - mPlayTime; | |
| } | |
| void BaseRenderNodeAnimator::forceEndNow(AnimationContext& context) { | |
| if (mPlayState < PlayState::Finished) { | |
| mPlayState = PlayState::Finished; | |
| callOnFinishedListener(context); | |
| } | |
| } | |
| void BaseRenderNodeAnimator::callOnFinishedListener(AnimationContext& context) { | |
| if (mListener.get()) { | |
| context.callOnFinished(this, mListener.get()); | |
| } | |
| } | |
| /************************************************************ | |
| * RenderPropertyAnimator | |
| ************************************************************/ | |
| struct RenderPropertyAnimator::PropertyAccessors { | |
| RenderNode::DirtyPropertyMask dirtyMask; | |
| GetFloatProperty getter; | |
| SetFloatProperty setter; | |
| }; | |
| // Maps RenderProperty enum to accessors | |
| const RenderPropertyAnimator::PropertyAccessors RenderPropertyAnimator::PROPERTY_ACCESSOR_LUT[] = { | |
| {RenderNode::TRANSLATION_X, &RenderProperties::getTranslationX, | |
| &RenderProperties::setTranslationX}, | |
| {RenderNode::TRANSLATION_Y, &RenderProperties::getTranslationY, | |
| &RenderProperties::setTranslationY}, | |
| {RenderNode::TRANSLATION_Z, &RenderProperties::getTranslationZ, | |
| &RenderProperties::setTranslationZ}, | |
| {RenderNode::SCALE_X, &RenderProperties::getScaleX, &RenderProperties::setScaleX}, | |
| {RenderNode::SCALE_Y, &RenderProperties::getScaleY, &RenderProperties::setScaleY}, | |
| {RenderNode::ROTATION, &RenderProperties::getRotation, &RenderProperties::setRotation}, | |
| {RenderNode::ROTATION_X, &RenderProperties::getRotationX, &RenderProperties::setRotationX}, | |
| {RenderNode::ROTATION_Y, &RenderProperties::getRotationY, &RenderProperties::setRotationY}, | |
| {RenderNode::X, &RenderProperties::getX, &RenderProperties::setX}, | |
| {RenderNode::Y, &RenderProperties::getY, &RenderProperties::setY}, | |
| {RenderNode::Z, &RenderProperties::getZ, &RenderProperties::setZ}, | |
| {RenderNode::ALPHA, &RenderProperties::getAlpha, &RenderProperties::setAlpha}, | |
| }; | |
| RenderPropertyAnimator::RenderPropertyAnimator(RenderProperty property, float finalValue) | |
| : BaseRenderNodeAnimator(finalValue), mPropertyAccess(&(PROPERTY_ACCESSOR_LUT[property])) {} | |
| void RenderPropertyAnimator::onAttached() { | |
| if (!mHasStartValue && mStagingTarget->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) { | |
| setStartValue((mStagingTarget->stagingProperties().*mPropertyAccess->getter)()); | |
| } | |
| } | |
| void RenderPropertyAnimator::onStagingPlayStateChanged() { | |
| if (mStagingPlayState == PlayState::Running) { | |
| if (mStagingTarget) { | |
| (mStagingTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue()); | |
| } else { | |
| // In the case of start delay where stagingTarget has been sync'ed over and null'ed | |
| // we delay the properties update to push staging. | |
| mShouldUpdateStagingProperties = true; | |
| } | |
| } else if (mStagingPlayState == PlayState::Finished) { | |
| // We're being canceled, so make sure that whatever values the UI thread | |
| // is observing for us is pushed over | |
| mShouldSyncPropertyFields = true; | |
| } | |
| } | |
| void RenderPropertyAnimator::onPushStaging() { | |
| if (mShouldUpdateStagingProperties) { | |
| (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue()); | |
| mShouldUpdateStagingProperties = false; | |
| } | |
| if (mShouldSyncPropertyFields) { | |
| mTarget->setPropertyFieldsDirty(dirtyMask()); | |
| mShouldSyncPropertyFields = false; | |
| } | |
| } | |
| uint32_t RenderPropertyAnimator::dirtyMask() { | |
| return mPropertyAccess->dirtyMask; | |
| } | |
| float RenderPropertyAnimator::getValue(RenderNode* target) const { | |
| return (target->properties().*mPropertyAccess->getter)(); | |
| } | |
| void RenderPropertyAnimator::setValue(RenderNode* target, float value) { | |
| (target->animatorProperties().*mPropertyAccess->setter)(value); | |
| } | |
| /************************************************************ | |
| * CanvasPropertyPrimitiveAnimator | |
| ************************************************************/ | |
| CanvasPropertyPrimitiveAnimator::CanvasPropertyPrimitiveAnimator(CanvasPropertyPrimitive* property, | |
| float finalValue) | |
| : BaseRenderNodeAnimator(finalValue), mProperty(property) {} | |
| float CanvasPropertyPrimitiveAnimator::getValue(RenderNode* target) const { | |
| return mProperty->value; | |
| } | |
| void CanvasPropertyPrimitiveAnimator::setValue(RenderNode* target, float value) { | |
| mProperty->value = value; | |
| } | |
| uint32_t CanvasPropertyPrimitiveAnimator::dirtyMask() { | |
| return RenderNode::DISPLAY_LIST; | |
| } | |
| /************************************************************ | |
| * CanvasPropertySkPaintAnimator | |
| ************************************************************/ | |
| CanvasPropertyPaintAnimator::CanvasPropertyPaintAnimator(CanvasPropertyPaint* property, | |
| PaintField field, float finalValue) | |
| : BaseRenderNodeAnimator(finalValue), mProperty(property), mField(field) {} | |
| float CanvasPropertyPaintAnimator::getValue(RenderNode* target) const { | |
| switch (mField) { | |
| case STROKE_WIDTH: | |
| return mProperty->value.getStrokeWidth(); | |
| case ALPHA: | |
| return mProperty->value.getAlpha(); | |
| } | |
| LOG_ALWAYS_FATAL("Unknown field %d", (int)mField); | |
| return -1; | |
| } | |
| static uint8_t to_uint8(float value) { | |
| int c = (int)(value + .5f); | |
| return static_cast<uint8_t>(c < 0 ? 0 : c > 255 ? 255 : c); | |
| } | |
| void CanvasPropertyPaintAnimator::setValue(RenderNode* target, float value) { | |
| switch (mField) { | |
| case STROKE_WIDTH: | |
| mProperty->value.setStrokeWidth(value); | |
| return; | |
| case ALPHA: | |
| mProperty->value.setAlpha(to_uint8(value)); | |
| return; | |
| } | |
| LOG_ALWAYS_FATAL("Unknown field %d", (int)mField); | |
| } | |
| uint32_t CanvasPropertyPaintAnimator::dirtyMask() { | |
| return RenderNode::DISPLAY_LIST; | |
| } | |
| RevealAnimator::RevealAnimator(int centerX, int centerY, float startValue, float finalValue) | |
| : BaseRenderNodeAnimator(finalValue), mCenterX(centerX), mCenterY(centerY) { | |
| setStartValue(startValue); | |
| } | |
| float RevealAnimator::getValue(RenderNode* target) const { | |
| return target->properties().getRevealClip().getRadius(); | |
| } | |
| void RevealAnimator::setValue(RenderNode* target, float value) { | |
| target->animatorProperties().mutableRevealClip().set(true, mCenterX, mCenterY, value); | |
| } | |
| uint32_t RevealAnimator::dirtyMask() { | |
| return RenderNode::GENERIC; | |
| } | |
| } /* namespace uirenderer */ | |
| } /* namespace android */ |