diff --git a/app/src/main/java/com/google/samples/apps/topeka/activity/QuizActivity.java b/app/src/main/java/com/google/samples/apps/topeka/activity/QuizActivity.java index 8026f5f2..cabd24eb 100644 --- a/app/src/main/java/com/google/samples/apps/topeka/activity/QuizActivity.java +++ b/app/src/main/java/com/google/samples/apps/topeka/activity/QuizActivity.java @@ -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); diff --git a/app/src/main/java/com/google/samples/apps/topeka/adapter/CategoryAdapter.java b/app/src/main/java/com/google/samples/apps/topeka/adapter/CategoryAdapter.java index 79879c4a..bb5a4962 100644 --- a/app/src/main/java/com/google/samples/apps/topeka/adapter/CategoryAdapter.java +++ b/app/src/main/java/com/google/samples/apps/topeka/adapter/CategoryAdapter.java @@ -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; } diff --git a/app/src/main/java/com/google/samples/apps/topeka/adapter/ScoreAdapter.java b/app/src/main/java/com/google/samples/apps/topeka/adapter/ScoreAdapter.java index 56b1944f..78c97065 100644 --- a/app/src/main/java/com/google/samples/apps/topeka/adapter/ScoreAdapter.java +++ b/app/src/main/java/com/google/samples/apps/topeka/adapter/ScoreAdapter.java @@ -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; } diff --git a/app/src/main/java/com/google/samples/apps/topeka/helper/TransitionHelper.java b/app/src/main/java/com/google/samples/apps/topeka/helper/TransitionHelper.java index 641fc437..1a998936 100644 --- a/app/src/main/java/com/google/samples/apps/topeka/helper/TransitionHelper.java +++ b/app/src/main/java/com/google/samples/apps/topeka/helper/TransitionHelper.java @@ -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, diff --git a/app/src/main/java/com/google/samples/apps/topeka/widget/AvatarView.java b/app/src/main/java/com/google/samples/apps/topeka/widget/AvatarView.java index 8720687a..fdf3d131 100644 --- a/app/src/main/java/com/google/samples/apps/topeka/widget/AvatarView.java +++ b/app/src/main/java/com/google/samples/apps/topeka/widget/AvatarView.java @@ -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; diff --git a/app/src/main/java/com/google/samples/apps/topeka/widget/fab/CheckableFab.java b/app/src/main/java/com/google/samples/apps/topeka/widget/fab/CheckableFab.java new file mode 100644 index 00000000..41186766 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/topeka/widget/fab/CheckableFab.java @@ -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); + } +} diff --git a/app/src/main/java/com/google/samples/apps/topeka/widget/fab/DoneFab.java b/app/src/main/java/com/google/samples/apps/topeka/widget/fab/DoneFab.java index 55b59b3d..23bc619e 100644 --- a/app/src/main/java/com/google/samples/apps/topeka/widget/fab/DoneFab.java +++ b/app/src/main/java/com/google/samples/apps/topeka/widget/fab/DoneFab.java @@ -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); } } diff --git a/app/src/main/java/com/google/samples/apps/topeka/widget/quiz/AbsQuizView.java b/app/src/main/java/com/google/samples/apps/topeka/widget/quiz/AbsQuizView.java index 216c72c8..67883e11 100644 --- a/app/src/main/java/com/google/samples/apps/topeka/widget/quiz/AbsQuizView.java +++ b/app/src/main/java/com/google/samples/apps/topeka/widget/quiz/AbsQuizView.java @@ -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}. @@ -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. @@ -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); @@ -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 @@ -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) { diff --git a/app/src/main/res/drawable/ic_fail.xml b/app/src/main/res/animator-v21/cross_to_tick_line_1.xml similarity index 60% rename from app/src/main/res/drawable/ic_fail.xml rename to app/src/main/res/animator-v21/cross_to_tick_line_1.xml index 8e49d5e8..34877d85 100644 --- a/app/src/main/res/drawable/ic_fail.xml +++ b/app/src/main/res/animator-v21/cross_to_tick_line_1.xml @@ -14,12 +14,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - - + diff --git a/app/src/main/res/animator-v21/cross_to_tick_line_2.xml b/app/src/main/res/animator-v21/cross_to_tick_line_2.xml new file mode 100644 index 00000000..f69b1654 --- /dev/null +++ b/app/src/main/res/animator-v21/cross_to_tick_line_2.xml @@ -0,0 +1,24 @@ + + + diff --git a/app/src/main/res/drawable/ic_done.xml b/app/src/main/res/animator-v21/rotate_cross_to_tick.xml similarity index 65% rename from app/src/main/res/drawable/ic_done.xml rename to app/src/main/res/animator-v21/rotate_cross_to_tick.xml index e9fff018..77d2b040 100644 --- a/app/src/main/res/drawable/ic_done.xml +++ b/app/src/main/res/animator-v21/rotate_cross_to_tick.xml @@ -14,12 +14,10 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - - \ No newline at end of file + diff --git a/app/src/main/res/animator-v21/rotate_tick_to_cross.xml b/app/src/main/res/animator-v21/rotate_tick_to_cross.xml new file mode 100644 index 00000000..9d9e98b8 --- /dev/null +++ b/app/src/main/res/animator-v21/rotate_tick_to_cross.xml @@ -0,0 +1,23 @@ + + + diff --git a/app/src/main/res/animator-v21/tick_to_cross_line_1.xml b/app/src/main/res/animator-v21/tick_to_cross_line_1.xml new file mode 100644 index 00000000..a798e7cd --- /dev/null +++ b/app/src/main/res/animator-v21/tick_to_cross_line_1.xml @@ -0,0 +1,24 @@ + + + diff --git a/app/src/main/res/animator-v21/tick_to_cross_line_2.xml b/app/src/main/res/animator-v21/tick_to_cross_line_2.xml new file mode 100644 index 00000000..e38e7aa0 --- /dev/null +++ b/app/src/main/res/animator-v21/tick_to_cross_line_2.xml @@ -0,0 +1,24 @@ + + + diff --git a/app/src/main/res/drawable-v21/answer_quiz_fab.xml b/app/src/main/res/drawable-v21/answer_quiz_fab.xml new file mode 100644 index 00000000..6c436332 --- /dev/null +++ b/app/src/main/res/drawable-v21/answer_quiz_fab.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/avd_cross_to_tick.xml b/app/src/main/res/drawable-v21/avd_cross_to_tick.xml new file mode 100644 index 00000000..7375e2a1 --- /dev/null +++ b/app/src/main/res/drawable-v21/avd_cross_to_tick.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-v21/avd_tick_to_cross.xml b/app/src/main/res/drawable-v21/avd_tick_to_cross.xml new file mode 100644 index 00000000..71b9280c --- /dev/null +++ b/app/src/main/res/drawable-v21/avd_tick_to_cross.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/answer_quiz_fab.xml b/app/src/main/res/drawable/answer_quiz_fab.xml new file mode 100644 index 00000000..d20f76f0 --- /dev/null +++ b/app/src/main/res/drawable/answer_quiz_fab.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_cross.xml b/app/src/main/res/drawable/ic_cross.xml new file mode 100644 index 00000000..a5f0b16c --- /dev/null +++ b/app/src/main/res/drawable/ic_cross.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_tick.xml b/app/src/main/res/drawable/ic_tick.xml new file mode 100644 index 00000000..30d2ef97 --- /dev/null +++ b/app/src/main/res/drawable/ic_tick.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_scorecard.xml b/app/src/main/res/layout/item_scorecard.xml index 6aa837fd..79ba8a0b 100644 --- a/app/src/main/res/layout/item_scorecard.xml +++ b/app/src/main/res/layout/item_scorecard.xml @@ -33,7 +33,7 @@ android:layout_marginEnd="@dimen/spacing_double" android:layout_marginStart="@dimen/spacing_double" android:contentDescription="@null" - android:src="@drawable/ic_done" /> + android:src="@drawable/ic_tick" /> + + + + + 24 + 24 + 12 + 12 + 2 + M19.6,7 L10.4,16.2 + M4.8,13.4 L9,17.6 + M17.6,6.4 L6.4,17.6 + M6.4,6.4 L17.6,17.6 + + + groupTickCross + line_1 + line_2 + + + @android:color/black + + + 450 + +