Skip to content
This repository has been archived by the owner on Jan 23, 2021. It is now read-only.

Commit

Permalink
Introduce FAB animations on answer submission
Browse files Browse the repository at this point in the history
Using animated-vector drawables and a
CheckableFab to allow for an
icon exchanging animation.
  • Loading branch information
keyboardsurfer committed Jul 9, 2015
1 parent 084a872 commit cd465dd
Show file tree
Hide file tree
Showing 22 changed files with 518 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ public void onAnimationEnd(Animator animation) {
}

private void showQuizFabWithDoneIcon() {
mQuizFab.setImageResource(R.drawable.ic_done);
mQuizFab.setImageResource(R.drawable.ic_tick);
mQuizFab.setId(R.id.quiz_done);
mQuizFab.setVisibility(View.VISIBLE);
mQuizFab.setScaleX(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ private Drawable loadTintedCategoryDrawable(Category category, int categoryImage
* @return The tinted check mark
*/
private Drawable loadTintedDoneDrawable() {
final Drawable done = mActivity.getDrawable(R.drawable.ic_done);
final Drawable done = mActivity.getDrawable(R.drawable.ic_tick);
tintDrawable(done, android.R.color.white);
return done;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,14 @@ private void setSolvedStateForQuiz(ImageView solvedState, int position) {

private Drawable getSuccessIcon(Context context) {
if (null == mSuccessIcon) {
mSuccessIcon = loadAndTint(context, R.drawable.ic_done, R.color.theme_green_primary);
mSuccessIcon = loadAndTint(context, R.drawable.ic_tick, R.color.theme_green_primary);
}
return mSuccessIcon;
}

private Drawable getFailedIcon(Context context) {
if (null == mFailedIcon) {
mFailedIcon = loadAndTint(context, R.drawable.ic_fail, R.color.theme_red_primary);
mFailedIcon = loadAndTint(context, R.drawable.ic_cross, R.color.theme_red_primary);
}
return mFailedIcon;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Copyright 2015 Google Inc.
*
* 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
* 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,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/*
* Copyright 2015 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.topeka.widget;

import com.google.samples.apps.topeka.R;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2015 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.samples.apps.topeka.widget.fab;

import com.google.samples.apps.topeka.R;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.Checkable;

public class CheckableFab extends FloatingActionButton implements Checkable {

private static final int[] CHECKED = {android.R.attr.state_checked};

private boolean mIsChecked = true;


public CheckableFab(Context context) {
this(context, null);
}

public CheckableFab(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public CheckableFab(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setImageResource(R.drawable.answer_quiz_fab);
}

@Override
public int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(++extraSpace);
if (mIsChecked) {
mergeDrawableStates(drawableState, CHECKED);
}
return drawableState;
}

@Override
public void setChecked(boolean checked) {
if (mIsChecked == checked) {
return;
}
mIsChecked = checked;
refreshDrawableState();
}

@Override
public boolean isChecked() {
return mIsChecked;
}

@Override
public void toggle() {
setChecked(!mIsChecked);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ public DoneFab(Context context, AttributeSet attrs) {

public DoneFab(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setImageResource(R.drawable.ic_done);
setImageResource(R.drawable.ic_tick);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@
import com.google.samples.apps.topeka.activity.QuizActivity;
import com.google.samples.apps.topeka.model.Category;
import com.google.samples.apps.topeka.model.quiz.Quiz;
import com.google.samples.apps.topeka.widget.fab.DoneFab;
import com.google.samples.apps.topeka.widget.fab.FloatingActionButton;
import com.google.samples.apps.topeka.widget.fab.CheckableFab;

/**
* This is the base class for displaying a {@link com.google.samples.apps.topeka.model.quiz.Quiz}.
Expand Down Expand Up @@ -87,7 +86,7 @@ public Integer get(FrameLayout object) {
private final int mScaleAnimationDuration;
private boolean mAnswered;
private TextView mQuestionView;
private FloatingActionButton mSubmitAnswer;
private CheckableFab mSubmitAnswer;

/**
* Enables creation of views for quizzes.
Expand Down Expand Up @@ -178,9 +177,9 @@ private void addFloatingActionButton() {
addView(mSubmitAnswer, fabLayoutParams);
}

private FloatingActionButton getSubmitButton(Context context) {
private CheckableFab getSubmitButton(Context context) {
if (null == mSubmitAnswer) {
mSubmitAnswer = new DoneFab(context);
mSubmitAnswer = new CheckableFab(context);
mSubmitAnswer.setId(R.id.submitAnswer);
mSubmitAnswer.setVisibility(GONE);
mSubmitAnswer.setScaleY(0);
Expand Down Expand Up @@ -290,43 +289,70 @@ private void submitAnswer(final View v) {
*/
private void performScoreAnimation(final boolean answerCorrect) {

// Decide on the color and icon to use.
final int backgroundColor = getResources().getColor(answerCorrect ?
R.color.green : R.color.red);
final int imageResId = answerCorrect ? R.drawable.ic_done : R.drawable.ic_fail;
mSubmitAnswer.setChecked(answerCorrect);

// Set color, duration and interpolator for the color change. Then start the animation.
final ObjectAnimator fabColorAnimator = ObjectAnimator
.ofArgb(mSubmitAnswer, "backgroundColor", Color.WHITE, backgroundColor);
fabColorAnimator.setDuration(mColorAnimationDuration)
.setInterpolator(mFastOutSlowInInterpolator);
fabColorAnimator.start();

// Set duration and interpolator for the icon change. Then start the animation.
final ObjectAnimator iconAnimator = ObjectAnimator
.ofArgb(mSubmitAnswer, "imageResource", R.drawable.ic_done, imageResId);
iconAnimator.setDuration(mIconAnimationDuration)
.setInterpolator(mFastOutSlowInInterpolator);
iconAnimator.start();
// Decide which background color to use.
final int backgroundColor = getResources()
.getColor(answerCorrect ? R.color.green : R.color.red);
animateFabBackgroundColor(backgroundColor);
hideFab();
resizeView();
moveViewOffScreen(answerCorrect);
// Animate the foreground color to match the background color.
// This overlays all content within the current view.
animateForegroundColor(backgroundColor);
}

// Hide the FAB.
private void hideFab() {
mSubmitAnswer.animate()
.setDuration(mScaleAnimationDuration)
.setStartDelay(mIconAnimationDuration * 2)
.scaleX(0f)
.scaleY(0f)
.setInterpolator(mLinearOutSlowInInterpolator);
.setInterpolator(mLinearOutSlowInInterpolator)
.start();
}

// Prepare for take off.
private void resizeView() {
final float widthHeightRatio = (float) getHeight() / (float) getWidth();
setElevation(getResources().getDimension(R.dimen.elevation_header));

// Animate the current view off the screen.
animate().
setDuration(500)
// Animate X and Y scaling separately to allow different start delays.
animate()
.scaleY(.5f / widthHeightRatio)
.setDuration(300)
.setStartDelay(750)
.start();
animate()
.scaleX(.5f)
.scaleY(.5f / widthHeightRatio)
.setDuration(300)
.setStartDelay(800)
.start();
}

private void animateFabBackgroundColor(int backgroundColor) {
// Set color, duration and interpolator for the color change. Then start the animation.
final ObjectAnimator fabColorAnimator = ObjectAnimator
.ofArgb(mSubmitAnswer, "backgroundColor", Color.WHITE, backgroundColor);
fabColorAnimator.setDuration(mColorAnimationDuration)
.setInterpolator(mFastOutSlowInInterpolator);
fabColorAnimator.start();
}

private void animateForegroundColor(int targetColor) {
final ObjectAnimator foregroundAnimator = ObjectAnimator
.ofArgb(this, FOREGROUND_COLOR, Color.WHITE, targetColor);
foregroundAnimator
.setDuration(200)
.setInterpolator(mLinearOutSlowInInterpolator);
foregroundAnimator.setStartDelay(750);
foregroundAnimator.start();
}

private void moveViewOffScreen(final boolean answerCorrect) {
// Animate the current view off the screen.
animate()
.setDuration(200)
.setStartDelay(1200)
.setInterpolator(mLinearOutSlowInInterpolator)
.withEndAction(new Runnable() {
@Override
Expand All @@ -336,17 +362,8 @@ public void run() {
((QuizActivity) getContext()).proceed();
}
}
});

// Animate the foreground color to match the background color.
// This overlays all content within the current view.
final ObjectAnimator foregroundAnimator = ObjectAnimator
.ofArgb(this, FOREGROUND_COLOR, Color.WHITE, backgroundColor);
foregroundAnimator.setDuration(200)
.setInterpolator(mLinearOutSlowInInterpolator);
foregroundAnimator.setStartDelay(750);
foregroundAnimator.start();

})
.start();
}

private void setMinHeightInternal(View view, @DimenRes int resId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="48"
android:viewportWidth="48">
<path
android:fillColor="@android:color/black"
android:pathData="M38 12.83l-2.83-2.83-11.17 11.17-11.17-11.17-2.83 2.83 11.17 11.17-11.17 11.17 2.83 2.83 11.17-11.17 11.17 11.17 2.83-2.83-11.17-11.17z" />
</vector>
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="pathData"
android:valueFrom="@string/path_cross_line_1"
android:valueTo="@string/path_tick_line_1"
android:duration="@integer/duration"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:valueType="pathType" />
24 changes: 24 additions & 0 deletions app/src/main/res/animator-v21/cross_to_tick_line_2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2015 Google Inc.
~
~ 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.
-->
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="pathData"
android:valueFrom="@string/path_cross_line_2"
android:valueTo="@string/path_tick_line_2"
android:duration="@integer/duration"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:valueType="pathType" />
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="48"
android:viewportWidth="48">
<path
android:fillColor="@android:color/black"
android:pathData="M18 32.34l-8.34-8.34-2.83 2.83 11.17 11.17 24-24-2.83-2.83z" />
</vector>
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="rotation"
android:valueFrom="-180"
android:valueTo="0"
android:duration="@integer/duration"
android:interpolator="@android:interpolator/fast_out_slow_in" />
23 changes: 23 additions & 0 deletions app/src/main/res/animator-v21/rotate_tick_to_cross.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2015 Google Inc.
~
~ 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.
-->
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="180"
android:duration="@integer/duration"
android:interpolator="@android:interpolator/fast_out_slow_in" />
Loading

0 comments on commit cd465dd

Please sign in to comment.