From 058fbeda9b36a0389c83f2cd7015dd485f8050ae Mon Sep 17 00:00:00 2001
From: Ben Weiss
Date: Tue, 3 Nov 2015 16:41:23 +0000
Subject: [PATCH] Many transition. Such slide. Much target. Wow.
* implement enter and exit transitions and more animations
* remove unused proguard references
* Introduce ViewUtils with a couple of Properties
* migrate Category display to RecyclerView
* replace GridView in category selection with RecyclerView
* Espresso now requires espresso-contrib
* enables use of notifyItemChanged
* more control over updated items
---
app/.gitignore | 1 +
app/build.gradle | 6 +-
app/proguard-rules.pro | 17 --
.../CategorySelectionActivityTest.java | 19 +-
app/src/main/AndroidManifest.xml | 1 +
.../main/java/android/util/IntProperty.java | 46 ----
.../activity/CategorySelectionActivity.java | 31 ++-
.../apps/topeka/activity/QuizActivity.java | 237 ++++++++++++------
.../apps/topeka/activity/SignInActivity.java | 15 +-
.../apps/topeka/adapter/CategoryAdapter.java | 108 +++++---
.../topeka/adapter/CategoryViewHolder.java | 34 ---
.../apps/topeka/adapter/ScoreAdapter.java | 2 +-
.../fragment/CategorySelectionFragment.java | 54 ++--
.../apps/topeka/fragment/QuizFragment.java | 10 +
.../apps/topeka/helper/AnswerHelper.java | 1 -
.../apps/topeka/helper/ApiLevelHelper.java | 4 +
.../apps/topeka/helper/TransitionHelper.java | 9 +-
.../samples/apps/topeka/helper/ViewUtils.java | 157 ++++++++++++
.../apps/topeka/widget/OffsetDecoration.java | 39 +++
.../topeka/widget/TextResizeTransition.java | 106 ++++++++
.../widget/TextSharedElementCallback.java | 73 ++++++
.../apps/topeka/widget/quiz/AbsQuizView.java | 36 +--
.../res/animator-v21/cross_to_tick_line_1.xml | 2 +-
.../res/animator-v21/cross_to_tick_line_2.xml | 2 +-
.../res/animator-v21/rotate_cross_to_tick.xml | 2 +-
.../res/animator-v21/rotate_tick_to_cross.xml | 2 +-
.../res/animator-v21/tick_to_cross_line_1.xml | 2 +-
.../res/animator-v21/tick_to_cross_line_2.xml | 2 +-
...tor_categories.xml => selector_subtle.xml} | 0
...tor_categories.xml => selector_subtle.xml} | 0
.../main/res/layout-land/fragment_sign_in.xml | 2 +
.../layout/activity_category_selection.xml | 4 +-
app/src/main/res/layout/activity_quiz.xml | 66 ++---
app/src/main/res/layout/activity_sign_in.xml | 6 +-
.../main/res/layout/fragment_categories.xml | 20 +-
app/src/main/res/layout/fragment_quiz.xml | 16 +-
app/src/main/res/layout/fragment_sign_in.xml | 9 +-
app/src/main/res/layout/item_category.xml | 18 +-
app/src/main/res/layout/sign_in_avatars.xml | 13 +-
app/src/main/res/layout/sign_in_username.xml | 57 +++--
.../main/res/transition/category_enter.xml | 19 +-
app/src/main/res/transition/category_exit.xml | 30 +++
...iz_enter.xml => category_shared_enter.xml} | 5 +-
.../main/res/transition/quiz_shared_enter.xml | 28 +++
app/src/main/res/transition/signin_enter.xml | 22 ++
app/src/main/res/transition/signin_exit.xml | 28 +++
app/src/main/res/values-v21/styles.xml | 18 +-
app/src/main/res/values/dimens.xml | 2 +
app/src/main/res/values/ids.xml | 3 +
app/src/main/res/values/styles.xml | 6 +-
app/src/main/res/values/tick_cross.xml | 2 +-
app/src/main/res/values/transition_names.xml | 1 -
app/src/main/res/values/transitions.xml | 20 ++
53 files changed, 1021 insertions(+), 392 deletions(-)
create mode 100644 app/.gitignore
delete mode 100644 app/proguard-rules.pro
delete mode 100644 app/src/main/java/android/util/IntProperty.java
delete mode 100644 app/src/main/java/com/google/samples/apps/topeka/adapter/CategoryViewHolder.java
create mode 100644 app/src/main/java/com/google/samples/apps/topeka/helper/ViewUtils.java
create mode 100644 app/src/main/java/com/google/samples/apps/topeka/widget/OffsetDecoration.java
create mode 100644 app/src/main/java/com/google/samples/apps/topeka/widget/TextResizeTransition.java
create mode 100644 app/src/main/java/com/google/samples/apps/topeka/widget/TextSharedElementCallback.java
rename app/src/main/res/drawable-v21/{selector_categories.xml => selector_subtle.xml} (100%)
rename app/src/main/res/drawable/{selector_categories.xml => selector_subtle.xml} (100%)
create mode 100644 app/src/main/res/transition/category_exit.xml
rename app/src/main/res/transition/{quiz_enter.xml => category_shared_enter.xml} (88%)
create mode 100644 app/src/main/res/transition/quiz_shared_enter.xml
create mode 100644 app/src/main/res/transition/signin_enter.xml
create mode 100644 app/src/main/res/transition/signin_exit.xml
create mode 100644 app/src/main/res/values/transitions.xml
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
index d611944d..d4ddb68c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -32,7 +32,6 @@ android {
buildTypes {
release {
minifyEnabled true
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
@@ -52,6 +51,11 @@ dependencies {
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.1') {
exclude module: 'espresso-idling-resource'
}
+ androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.1') {
+ exclude module: 'espresso-core'
+ exclude module: 'recyclerview-v7'
+ exclude module: 'support-v4'
+ }
androidTestCompile 'com.android.support.test:rules:0.4'
androidTestCompile 'com.android.support.test:runner:0.4'
androidTestCompile 'org.hamcrest:hamcrest-core:1.3'
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
deleted file mode 100644
index bb65c6fe..00000000
--- a/app/proguard-rules.pro
+++ /dev/null
@@ -1,17 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
diff --git a/app/src/androidTest/java/com/google/samples/apps/topeka/activity/CategorySelectionActivityTest.java b/app/src/androidTest/java/com/google/samples/apps/topeka/activity/CategorySelectionActivityTest.java
index e7395f04..248d914d 100644
--- a/app/src/androidTest/java/com/google/samples/apps/topeka/activity/CategorySelectionActivityTest.java
+++ b/app/src/androidTest/java/com/google/samples/apps/topeka/activity/CategorySelectionActivityTest.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.contrib.RecyclerViewActions;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
@@ -37,18 +38,15 @@
import java.util.List;
-import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.action.ViewActions.scrollTo;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static junit.framework.Assert.assertFalse;
-import static org.hamcrest.Matchers.allOf;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.is;
@RunWith(AndroidJUnit4.class)
@LargeTest
@@ -80,11 +78,14 @@ public void loadCategories() {
}
@Test
- public void allCategories_areDisplayed() {
- for (Category category : mCategories) {
- onData(allOf(is(instanceOf(Category.class)), is(category)))
- .inAdapterView(withId(R.id.categories))
- .check(matches(isDisplayed()));
+ public void allCategories_areDisplayed() throws InterruptedException {
+ String categoryName;
+ for (int i = 0; i < mCategories.size(); i++) {
+ categoryName = mCategories.get(i).getName();
+ onView(withId(R.id.categories))
+ .perform(RecyclerViewActions.actionOnItemAtPosition(i, scrollTo()));
+ onView(withText(categoryName)).check(matches(isDisplayed()));
+
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4225553e..1c5ce141 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -25,6 +25,7 @@
android:theme="@style/Topeka">
diff --git a/app/src/main/java/android/util/IntProperty.java b/app/src/main/java/android/util/IntProperty.java
deleted file mode 100644
index 064d58ed..00000000
--- a/app/src/main/java/android/util/IntProperty.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-/**
- * An implementation of {@link android.util.Property} to be used specifically with fields of type
- * int
. This type-specific subclass enables performance benefit by allowing
- * calls to a {@link #set(Object, Integer) set()} function that takes the primitive
- * int
type and avoids autoboxing and other overhead associated with the
- * Integer
class.
- *
- * @param The class on which the Property is declared.
- */
-public abstract class IntProperty extends Property {
-
- public IntProperty(String name) {
- super(Integer.class, name);
- }
-
- /**
- * A type-specific override of the {@link #set(Object, Integer)} that is faster when dealing
- * with fields of type int
.
- */
- public abstract void setValue(T object, int value);
-
- @Override
- final public void set(T object, Integer value) {
- //noinspection UnnecessaryUnboxing
- setValue(object, value.intValue());
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/google/samples/apps/topeka/activity/CategorySelectionActivity.java b/app/src/main/java/com/google/samples/apps/topeka/activity/CategorySelectionActivity.java
index c680c4cf..38b73a15 100644
--- a/app/src/main/java/com/google/samples/apps/topeka/activity/CategorySelectionActivity.java
+++ b/app/src/main/java/com/google/samples/apps/topeka/activity/CategorySelectionActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc.
+ * Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,12 +19,16 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
+import android.transition.TransitionInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -32,6 +36,7 @@
import com.google.samples.apps.topeka.R;
import com.google.samples.apps.topeka.fragment.CategorySelectionFragment;
+import com.google.samples.apps.topeka.helper.ApiLevelHelper;
import com.google.samples.apps.topeka.helper.PreferencesHelper;
import com.google.samples.apps.topeka.model.Player;
import com.google.samples.apps.topeka.persistence.TopekaDatabaseHelper;
@@ -73,6 +78,7 @@ protected void onCreate(Bundle savedInstanceState) {
} else {
setProgressBarVisibility(View.GONE);
}
+ supportPostponeEnterTransition();
}
@Override
@@ -100,6 +106,14 @@ public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.category_container);
+ if (fragment != null) {
+ fragment.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
@@ -114,7 +128,11 @@ public boolean onOptionsItemSelected(MenuItem item) {
private void signOut() {
PreferencesHelper.signOut(this);
TopekaDatabaseHelper.reset(this);
- SignInActivity.start(this, false, null);
+ if (ApiLevelHelper.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
+ getWindow().setExitTransition(TransitionInflater.from(this)
+ .inflateTransition(R.transition.category_enter));
+ }
+ SignInActivity.start(this, false);
ActivityCompat.finishAfterTransition(this);
}
@@ -124,8 +142,13 @@ private String getDisplayName(Player player) {
}
private void attachCategoryGridFragment() {
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.quiz_container, CategorySelectionFragment.newInstance())
+ FragmentManager supportFragmentManager = getSupportFragmentManager();
+ Fragment fragment = supportFragmentManager.findFragmentById(R.id.category_container);
+ if (!(fragment instanceof CategorySelectionFragment)) {
+ fragment = CategorySelectionFragment.newInstance();
+ }
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.category_container, fragment)
.commit();
setProgressBarVisibility(View.GONE);
}
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 f1f09310..6d961af6 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
@@ -18,10 +18,14 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
@@ -32,21 +36,28 @@
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
+import android.support.v4.view.animation.FastOutLinearInInterpolator;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.Window;
import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
import android.widget.ImageView;
+import android.widget.TextView;
import com.google.samples.apps.topeka.R;
import com.google.samples.apps.topeka.fragment.QuizFragment;
import com.google.samples.apps.topeka.helper.ApiLevelHelper;
+import com.google.samples.apps.topeka.helper.ViewUtils;
import com.google.samples.apps.topeka.model.Category;
+import com.google.samples.apps.topeka.model.JsonAttributes;
import com.google.samples.apps.topeka.persistence.TopekaDatabaseHelper;
+import com.google.samples.apps.topeka.widget.TextSharedElementCallback;
+
+import java.util.List;
import static com.google.samples.apps.topeka.adapter.CategoryAdapter.DRAWABLE;
@@ -55,20 +66,21 @@ public class QuizActivity extends AppCompatActivity {
private static final String TAG = "QuizActivity";
private static final String IMAGE_CATEGORY = "image_category_";
private static final String STATE_IS_PLAYING = "isPlaying";
- private static final int UNDEFINED = -1;
private static final String FRAGMENT_TAG = "Quiz";
private Interpolator mInterpolator;
- private String mCategoryId;
+ private Category mCategory;
private QuizFragment mQuizFragment;
- private Toolbar mToolbar;
private FloatingActionButton mQuizFab;
private boolean mSavedStateIsPlaying;
private ImageView mIcon;
private Animator mCircularReveal;
+ private ObjectAnimator mColorChange;
private CountingIdlingResource mCountingIdlingResource;
+ private View mToolbarBack;
+
- private View.OnClickListener mOnClickListener = new View.OnClickListener() {
+ View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(final View v) {
switch (v.getId()) {
@@ -81,13 +93,9 @@ public void onClick(final View v) {
case R.id.quiz_done:
ActivityCompat.finishAfterTransition(QuizActivity.this);
break;
- case UNDEFINED:
- final CharSequence contentDescription = v.getContentDescription();
- if (contentDescription != null && contentDescription
- .equals(getString(R.string.up))) {
- onBackPressed();
- break;
- }
+ case R.id.back:
+ onBackPressed();
+ break;
default:
throw new UnsupportedOperationException(
"OnClick has not been implemented for " + getResources().
@@ -105,13 +113,45 @@ public static Intent getStartIntent(Context context, Category category) {
@Override
protected void onCreate(Bundle savedInstanceState) {
mCountingIdlingResource = new CountingIdlingResource("Quiz");
- mCategoryId = getIntent().getStringExtra(Category.TAG);
+ String categoryId = getIntent().getStringExtra(Category.TAG);
mInterpolator = new FastOutSlowInInterpolator();
if (null != savedInstanceState) {
mSavedStateIsPlaying = savedInstanceState.getBoolean(STATE_IS_PLAYING);
}
super.onCreate(savedInstanceState);
- populate(mCategoryId);
+ populate(categoryId);
+ int categoryNameTextSize = getResources()
+ .getDimensionPixelSize(R.dimen.category_item_text_size);
+ int paddingStart = getResources().getDimensionPixelSize(R.dimen.spacing_double);
+ final int startDelay = getResources().getInteger(R.integer.toolbar_transition_duration);
+ ActivityCompat.setEnterSharedElementCallback(this,
+ new TextSharedElementCallback(categoryNameTextSize, paddingStart) {
+ @Override
+ public void onSharedElementStart(List sharedElementNames,
+ List sharedElements,
+ List sharedElementSnapshots) {
+ super.onSharedElementStart(sharedElementNames,
+ sharedElements,
+ sharedElementSnapshots);
+ mToolbarBack.setScaleX(0f);
+ mToolbarBack.setScaleY(0f);
+ }
+
+ @Override
+ public void onSharedElementEnd(List sharedElementNames,
+ List sharedElements,
+ List sharedElementSnapshots) {
+ super.onSharedElementEnd(sharedElementNames,
+ sharedElements,
+ sharedElementSnapshots);
+ // Make sure to perform this animation after the transition has ended.
+ ViewCompat.animate(mToolbarBack)
+ .setStartDelay(startDelay)
+ .scaleX(1f)
+ .scaleY(1f)
+ .alpha(1f);
+ }
+ });
}
@Override
@@ -119,7 +159,13 @@ protected void onResume() {
if (mSavedStateIsPlaying) {
mQuizFragment = (QuizFragment) getSupportFragmentManager().findFragmentByTag(
FRAGMENT_TAG);
+ if (!mQuizFragment.hasSolvedStateListener()) {
+ mQuizFragment.setSolvedStateListener(getSolvedStateListener());
+ }
findViewById(R.id.quiz_fragment_container).setVisibility(View.VISIBLE);
+ mQuizFab.hide();
+ } else {
+ initQuizFragment();
}
super.onResume();
}
@@ -138,6 +184,13 @@ public void onBackPressed() {
return;
}
+ ViewCompat.animate(mToolbarBack)
+ .scaleX(0f)
+ .scaleY(0f)
+ .alpha(0f)
+ .setDuration(100)
+ .start();
+
// Scale the icon and fab to 0 size before calling onBackPressed if it exists.
ViewCompat.animate(mIcon)
.scaleX(.7f)
@@ -168,15 +221,18 @@ && isDestroyed())) {
private void startQuizFromClickOn(final View clickedView) {
initQuizFragment();
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.quiz_fragment_container, mQuizFragment, FRAGMENT_TAG).commit();
- final View fragmentContainer = findViewById(R.id.quiz_fragment_container);
- revealFragmentContainer(clickedView, fragmentContainer);
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(R.id.quiz_fragment_container, mQuizFragment, FRAGMENT_TAG)
+ .commit();
+ final FrameLayout container = (FrameLayout) findViewById(R.id.quiz_fragment_container);
+ revealFragmentContainer(clickedView, container);
// the toolbar should not have more elevation than the content while playing
setToolbarElevation(false);
}
- private void revealFragmentContainer(final View clickedView, final View fragmentContainer) {
+ private void revealFragmentContainer(final View clickedView,
+ final FrameLayout fragmentContainer) {
if (ApiLevelHelper.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
revealFragmentContainerLollipop(clickedView, fragmentContainer);
} else {
@@ -188,30 +244,38 @@ private void revealFragmentContainer(final View clickedView, final View fragment
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void revealFragmentContainerLollipop(final View clickedView,
- final View fragmentContainer) {
+ final FrameLayout fragmentContainer) {
prepareCircularReveal(clickedView, fragmentContainer);
+
ViewCompat.animate(clickedView)
.scaleX(0)
.scaleY(0)
+ .alpha(0)
.setInterpolator(mInterpolator)
.setListener(new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationEnd(View view) {
fragmentContainer.setVisibility(View.VISIBLE);
- mCircularReveal.start();
clickedView.setVisibility(View.GONE);
}
})
.start();
+
+ fragmentContainer.setVisibility(View.VISIBLE);
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.play(mCircularReveal).with(mColorChange);
+ animatorSet.start();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
- private void prepareCircularReveal(View startView, View targetView) {
+ private void prepareCircularReveal(View startView, FrameLayout targetView) {
int centerX = (startView.getLeft() + startView.getRight()) / 2;
- int centerY = (startView.getTop() + startView.getBottom()) / 2;
- float finalRadius = (float) Math.hypot((double) centerX, (double) centerY);
+ // Subtract the start view's height to adjust for relative coordinates on screen.
+ int centerY = (startView.getTop() + startView.getBottom()) / 2 - startView.getHeight();
+ float endRadius = (float) Math.hypot((double) centerX, (double) centerY);
mCircularReveal = ViewAnimationUtils.createCircularReveal(
- targetView, centerX, centerY, 0, finalRadius);
+ targetView, centerX, centerY, startView.getWidth(), endRadius);
+ mCircularReveal.setInterpolator(new FastOutLinearInInterpolator());
mCircularReveal.addListener(new AnimatorListenerAdapter() {
@Override
@@ -220,60 +284,79 @@ public void onAnimationEnd(Animator animation) {
mCircularReveal.removeListener(this);
}
});
+ // Adding a color animation from the FAB's color to transparent creates a dissolve like
+ // effect to the circular reveal.
+ int accentColor = ContextCompat.getColor(this, mCategory.getTheme().getAccentColor());
+ mColorChange = ObjectAnimator.ofInt(targetView,
+ ViewUtils.FOREGROUND_COLOR, accentColor, Color.TRANSPARENT);
+ mColorChange.setEvaluator(new ArgbEvaluator());
+ mColorChange.setInterpolator(mInterpolator);
}
public void setToolbarElevation(boolean shouldElevate) {
if (ApiLevelHelper.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
- mToolbar.setElevation(shouldElevate ?
+ mToolbarBack.setElevation(shouldElevate ?
getResources().getDimension(R.dimen.elevation_header) : 0);
}
}
private void initQuizFragment() {
- mQuizFragment = QuizFragment.newInstance(mCategoryId,
- new QuizFragment.SolvedStateListener() {
- @Override
- public void onCategorySolved() {
- setToolbarElevation(true);
- displayDoneFab();
- }
+ if (mQuizFragment != null) {
+ return;
+ }
+ mQuizFragment = QuizFragment.newInstance(mCategory.getId(), getSolvedStateListener());
+ // the toolbar should not have more elevation than the content while playing
+ setToolbarElevation(false);
+ }
- private void displayDoneFab() {
- /* We're re-using the already existing fab and give it some
- * new values. This has to run delayed due to the queued animation
- * to hide the fab initially.
- */
- if (null != mCircularReveal && mCircularReveal.isRunning()) {
- mCircularReveal.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- showQuizFabWithDoneIcon();
- mCircularReveal.removeListener(this);
- }
- });
- } else {
+ @NonNull
+ private QuizFragment.SolvedStateListener getSolvedStateListener() {
+ return new QuizFragment.SolvedStateListener() {
+ @Override
+ public void onCategorySolved() {
+ setResultSolved();
+ setToolbarElevation(true);
+ displayDoneFab();
+ }
+
+ private void displayDoneFab() {
+ /* We're re-using the already existing fab and give it some
+ * new values. This has to run delayed due to the queued animation
+ * to hide the fab initially.
+ */
+ if (null != mCircularReveal && mCircularReveal.isRunning()) {
+ mCircularReveal.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
showQuizFabWithDoneIcon();
+ mCircularReveal.removeListener(this);
}
- }
+ });
+ } else {
+ showQuizFabWithDoneIcon();
+ }
+ }
- private void showQuizFabWithDoneIcon() {
- mQuizFab.setImageResource(R.drawable.ic_tick);
- mQuizFab.setId(R.id.quiz_done);
- mQuizFab.setVisibility(View.VISIBLE);
- mQuizFab.setScaleX(0f);
- mQuizFab.setScaleY(0f);
- ViewCompat.animate(mQuizFab)
- .scaleX(1)
- .scaleY(1)
- .setInterpolator(mInterpolator)
- .setListener(null)
- .start();
- }
- });
- if (ApiLevelHelper.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
- // the toolbar should not have more elevation than the content while playing
- setToolbarElevation(false);
- }
+ private void showQuizFabWithDoneIcon() {
+ mQuizFab.setImageResource(R.drawable.ic_tick);
+ mQuizFab.setId(R.id.quiz_done);
+ mQuizFab.setVisibility(View.VISIBLE);
+ mQuizFab.setScaleX(0f);
+ mQuizFab.setScaleY(0f);
+ ViewCompat.animate(mQuizFab)
+ .scaleX(1)
+ .scaleY(1)
+ .setInterpolator(mInterpolator)
+ .setListener(null)
+ .start();
+ }
+ };
+ }
+
+ private void setResultSolved() {
+ Intent categoryIntent = new Intent();
+ categoryIntent.putExtra(JsonAttributes.ID, mCategory.getId());
+ setResult(R.id.solved, categoryIntent);
}
/**
@@ -294,6 +377,7 @@ private void submitAnswer() {
mCountingIdlingResource.decrement();
if (!mQuizFragment.showNextPage()) {
mQuizFragment.showSummary();
+ setResultSolved();
return;
}
setToolbarElevation(false);
@@ -304,15 +388,15 @@ private void populate(String categoryId) {
Log.w(TAG, "Didn't find a category. Finishing");
finish();
}
- Category category = TopekaDatabaseHelper.getCategoryWith(this, categoryId);
- setTheme(category.getTheme().getStyleId());
+ mCategory = TopekaDatabaseHelper.getCategoryWith(this, categoryId);
+ setTheme(mCategory.getTheme().getStyleId());
if (ApiLevelHelper.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
Window window = getWindow();
window.setStatusBarColor(ContextCompat.getColor(this,
- category.getTheme().getPrimaryDarkColor()));
+ mCategory.getTheme().getPrimaryDarkColor()));
}
- initLayout(category.getId());
- initToolbar(category);
+ initLayout(mCategory.getId());
+ initToolbar(mCategory);
}
private void initLayout(String categoryId) {
@@ -341,11 +425,12 @@ private void initLayout(String categoryId) {
}
private void initToolbar(Category category) {
- mToolbar = (Toolbar) findViewById(R.id.toolbar_activity_quiz);
- mToolbar.setBackgroundColor(
- ContextCompat.getColor(this, category.getTheme().getPrimaryColor()));
- mToolbar.setTitle(category.getName());
- mToolbar.setNavigationOnClickListener(mOnClickListener);
+ mToolbarBack = findViewById(R.id.back);
+ mToolbarBack.setOnClickListener(mOnClickListener);
+ TextView titleView = (TextView) findViewById(R.id.category_title);
+ titleView.setText(category.getName());
+ titleView.setTextColor(ContextCompat.getColor(this,
+ category.getTheme().getTextPrimaryColor()));
if (mSavedStateIsPlaying) {
// the toolbar should not have more elevation than the content while playing
setToolbarElevation(false);
diff --git a/app/src/main/java/com/google/samples/apps/topeka/activity/SignInActivity.java b/app/src/main/java/com/google/samples/apps/topeka/activity/SignInActivity.java
index f40eb885..e1203cd5 100644
--- a/app/src/main/java/com/google/samples/apps/topeka/activity/SignInActivity.java
+++ b/app/src/main/java/com/google/samples/apps/topeka/activity/SignInActivity.java
@@ -17,9 +17,10 @@
package com.google.samples.apps.topeka.activity;
import android.app.Activity;
-import android.app.ActivityOptions;
import android.content.Intent;
import android.os.Bundle;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.app.AppCompatActivity;
import com.google.samples.apps.topeka.R;
@@ -30,16 +31,12 @@ public class SignInActivity extends AppCompatActivity {
private static final String EXTRA_EDIT = "EDIT";
- public static void start(Activity activity, Boolean edit, ActivityOptions options) {
+ public static void start(Activity activity, Boolean edit) {
Intent starter = new Intent(activity, SignInActivity.class);
starter.putExtra(EXTRA_EDIT, edit);
- if (options == null) {
- activity.startActivity(starter);
- activity.overridePendingTransition(android.R.anim.slide_in_left,
- android.R.anim.slide_out_right);
- } else {
- activity.startActivity(starter, options.toBundle());
- }
+ ActivityCompat.startActivity(activity,
+ starter,
+ ActivityOptionsCompat.makeSceneTransitionAnimation(activity).toBundle());
}
@Override
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 68c56af1..e27831dc 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
@@ -23,12 +23,12 @@
import android.support.annotation.ColorRes;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.BaseAdapter;
import android.widget.ImageView;
-import android.widget.LinearLayout;
+import android.widget.TextView;
import com.google.samples.apps.topeka.R;
import com.google.samples.apps.topeka.model.Category;
@@ -37,10 +37,7 @@
import java.util.List;
-/**
- * An adapter that allows display of {@link Category} data.
- */
-public class CategoryAdapter extends BaseAdapter {
+public class CategoryAdapter extends RecyclerView.Adapter {
public static final String DRAWABLE = "drawable";
private static final String ICON_CATEGORY = "icon_category_";
@@ -50,54 +47,78 @@ public class CategoryAdapter extends BaseAdapter {
private final Activity mActivity;
private List mCategories;
+ private OnItemClickListener mOnItemClickListener;
+
+ public interface OnItemClickListener {
+ void onClick(View view, int position);
+ }
+
public CategoryAdapter(Activity activity) {
- mResources = activity.getResources();
mActivity = activity;
+ mResources = mActivity.getResources();
mPackageName = mActivity.getPackageName();
mLayoutInflater = LayoutInflater.from(activity.getApplicationContext());
updateCategories(activity);
}
@Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (null == convertView) {
- convertView = mLayoutInflater.inflate(R.layout.item_category, parent, false);
- convertView.setTag(new CategoryViewHolder((LinearLayout) convertView));
- }
- CategoryViewHolder holder = (CategoryViewHolder) convertView.getTag();
- Category category = getItem(position);
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new ViewHolder(mLayoutInflater
+ .inflate(R.layout.item_category, parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, final int position) {
+ Category category = mCategories.get(position);
Theme theme = category.getTheme();
setCategoryIcon(category, holder.icon);
- convertView.setBackgroundColor(getColor(theme.getWindowBackgroundColor()));
+ holder.itemView.setBackgroundColor(getColor(theme.getWindowBackgroundColor()));
holder.title.setText(category.getName());
holder.title.setTextColor(getColor(theme.getTextPrimaryColor()));
holder.title.setBackgroundColor(getColor(theme.getPrimaryColor()));
- return convertView;
+ holder.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mOnItemClickListener.onClick(v, position);
+ }
+ });
}
@Override
- public int getCount() {
- return mCategories.size();
+ public long getItemId(int position) {
+ return mCategories.get(position).getId().hashCode();
}
@Override
+ public int getItemCount() {
+ return mCategories.size();
+ }
+
public Category getItem(int position) {
return mCategories.get(position);
}
- @Override
- public long getItemId(int position) {
- return mCategories.get(position).getId().hashCode();
+ /**
+ * @see android.support.v7.widget.RecyclerView.Adapter#notifyItemChanged(int)
+ * @param id Id of changed category.
+ */
+ public final void notifyItemChanged(String id) {
+ updateCategories(mActivity);
+ notifyItemChanged(getItemPositionById(id));
}
- @Override
- public boolean hasStableIds() {
- return true;
+ private int getItemPositionById(String id) {
+ for (int i = 0; i < mCategories.size(); i++) {
+ if (mCategories.get(i).getId().equals(id)) {
+ return i;
+ }
+
+ }
+ return -1;
}
- @Override
- public boolean areAllItemsEnabled() {
- return false;
+ public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
+ mOnItemClickListener = onItemClickListener;
}
private void setCategoryIcon(Category category, ImageView icon) {
@@ -112,12 +133,6 @@ private void setCategoryIcon(Category category, ImageView icon) {
}
}
- @Override
- public void notifyDataSetChanged() {
- super.notifyDataSetChanged();
- updateCategories(mActivity);
- }
-
private void updateCategories(Activity activity) {
mCategories = TopekaDatabaseHelper.getCategories(activity, true);
}
@@ -144,9 +159,9 @@ private LayerDrawable loadSolvedIcon(Category category, int categoryImageResourc
* @return The tinted resource
*/
private Drawable loadTintedCategoryDrawable(Category category, int categoryImageResource) {
- final Drawable categoryIcon = ContextCompat.getDrawable(mActivity, categoryImageResource);
- DrawableCompat.setTint(categoryIcon, getColor(category.getTheme().getPrimaryColor()));
- return categoryIcon;
+ final Drawable categoryIcon = ContextCompat
+ .getDrawable(mActivity, categoryImageResource).mutate();
+ return wrapAndTint(categoryIcon, category.getTheme().getPrimaryColor());
}
/**
@@ -156,8 +171,13 @@ private Drawable loadTintedCategoryDrawable(Category category, int categoryImage
*/
private Drawable loadTintedDoneDrawable() {
final Drawable done = ContextCompat.getDrawable(mActivity, R.drawable.ic_tick);
- DrawableCompat.setTint(done, getColor(android.R.color.white));
- return done;
+ return wrapAndTint(done, android.R.color.white);
+ }
+
+ private Drawable wrapAndTint(Drawable done, @ColorRes int color) {
+ Drawable compatDrawable = DrawableCompat.wrap(done);
+ DrawableCompat.setTint(compatDrawable, getColor(color));
+ return compatDrawable;
}
/**
@@ -169,4 +189,16 @@ private Drawable loadTintedDoneDrawable() {
private int getColor(@ColorRes int colorRes) {
return ContextCompat.getColor(mActivity, colorRes);
}
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+
+ final ImageView icon;
+ final TextView title;
+
+ public ViewHolder(View container) {
+ super(container);
+ icon = (ImageView) container.findViewById(R.id.category_icon);
+ title = (TextView) container.findViewById(R.id.category_title);
+ }
+ }
}
diff --git a/app/src/main/java/com/google/samples/apps/topeka/adapter/CategoryViewHolder.java b/app/src/main/java/com/google/samples/apps/topeka/adapter/CategoryViewHolder.java
deleted file mode 100644
index 34b28bdd..00000000
--- a/app/src/main/java/com/google/samples/apps/topeka/adapter/CategoryViewHolder.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.adapter;
-
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.google.samples.apps.topeka.R;
-
-public class CategoryViewHolder {
-
- protected TextView title;
- protected ImageView icon;
-
- public CategoryViewHolder(LinearLayout container) {
- icon = (ImageView) container.findViewById(R.id.category_icon);
- title = (TextView) container.findViewById(R.id.category_title);
- }
-}
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 1d661f45..5ea60572 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
@@ -126,7 +126,7 @@ private Drawable loadAndTint(Context context, @DrawableRes int drawableId,
throw new IllegalArgumentException("The drawable with id " + drawableId
+ " does not exist");
}
- DrawableCompat.setTint(imageDrawable, tintColor);
+ DrawableCompat.setTint(DrawableCompat.wrap(imageDrawable), tintColor);
return imageDrawable;
}
diff --git a/app/src/main/java/com/google/samples/apps/topeka/fragment/CategorySelectionFragment.java b/app/src/main/java/com/google/samples/apps/topeka/fragment/CategorySelectionFragment.java
index 61d0058c..d7988df9 100644
--- a/app/src/main/java/com/google/samples/apps/topeka/fragment/CategorySelectionFragment.java
+++ b/app/src/main/java/com/google/samples/apps/topeka/fragment/CategorySelectionFragment.java
@@ -17,26 +17,28 @@
package com.google.samples.apps.topeka.fragment;
import android.app.Activity;
+import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.app.Fragment;
import android.support.v4.util.Pair;
+import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.GridView;
import com.google.samples.apps.topeka.R;
import com.google.samples.apps.topeka.activity.QuizActivity;
import com.google.samples.apps.topeka.adapter.CategoryAdapter;
import com.google.samples.apps.topeka.helper.TransitionHelper;
import com.google.samples.apps.topeka.model.Category;
+import com.google.samples.apps.topeka.model.JsonAttributes;
+import com.google.samples.apps.topeka.widget.OffsetDecoration;
public class CategorySelectionFragment extends Fragment {
- private CategoryAdapter mCategoryAdapter;
+ private CategoryAdapter mAdapter;
public static CategorySelectionFragment newInstance() {
return new CategorySelectionFragment();
@@ -50,29 +52,42 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
- setUpQuizGrid((GridView) view.findViewById(R.id.categories));
+ setUpQuizGrid((RecyclerView) view.findViewById(R.id.categories));
super.onViewCreated(view, savedInstanceState);
}
- private void setUpQuizGrid(GridView categoriesView) {
- categoriesView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView> parent, View view, int position, long id) {
- Activity activity = getActivity();
- startQuizActivityWithTransition(activity, view.findViewById(R.id.category_title),
- mCategoryAdapter.getItem(position));
- }
- });
- mCategoryAdapter = new CategoryAdapter(getActivity());
- categoriesView.setAdapter(mCategoryAdapter);
+ private void setUpQuizGrid(RecyclerView categoriesView) {
+ final int spacing = getContext().getResources()
+ .getDimensionPixelSize(R.dimen.spacing_nano);
+ categoriesView.addItemDecoration(new OffsetDecoration(spacing));
+ mAdapter = new CategoryAdapter(getActivity());
+ mAdapter.setOnItemClickListener(
+ new CategoryAdapter.OnItemClickListener() {
+ @Override
+ public void onClick(View v, int position) {
+ Activity activity = getActivity();
+ startQuizActivityWithTransition(activity,
+ v.findViewById(R.id.category_title),
+ mAdapter.getItem(position));
+ }
+ });
+ categoriesView.setAdapter(mAdapter);
}
@Override
public void onResume() {
- mCategoryAdapter.notifyDataSetChanged();
+ getActivity().supportStartPostponedEnterTransition();
super.onResume();
}
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == R.id.request_category && resultCode == R.id.solved) {
+ mAdapter.notifyItemChanged(data.getStringExtra(JsonAttributes.ID));
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
private void startQuizActivityWithTransition(Activity activity, View toolbar,
Category category) {
@@ -83,8 +98,11 @@ private void startQuizActivityWithTransition(Activity activity, View toolbar,
// Start the activity with the participants, animating from one to the other.
final Bundle transitionBundle = sceneTransitionAnimation.toBundle();
- ActivityCompat.startActivity(getActivity(),
- QuizActivity.getStartIntent(activity, category), transitionBundle);
+ Intent startIntent = QuizActivity.getStartIntent(activity, category);
+ ActivityCompat.startActivityForResult(activity,
+ startIntent,
+ R.id.request_category,
+ transitionBundle);
}
}
diff --git a/app/src/main/java/com/google/samples/apps/topeka/fragment/QuizFragment.java b/app/src/main/java/com/google/samples/apps/topeka/fragment/QuizFragment.java
index e29ebc8d..24d5c963 100644
--- a/app/src/main/java/com/google/samples/apps/topeka/fragment/QuizFragment.java
+++ b/app/src/main/java/com/google/samples/apps/topeka/fragment/QuizFragment.java
@@ -243,6 +243,16 @@ public void showSummary() {
mQuizView.setVisibility(View.GONE);
}
+ public boolean hasSolvedStateListener() {
+ return mSolvedStateListener != null;
+ }
+ public void setSolvedStateListener(SolvedStateListener solvedStateListener) {
+ mSolvedStateListener = solvedStateListener;
+ if (mCategory.isSolved() && null != mSolvedStateListener) {
+ mSolvedStateListener.onCategorySolved();
+ }
+ }
+
private ScoreAdapter getScoreAdapter() {
if (null == mScoreAdapter) {
mScoreAdapter = new ScoreAdapter(mCategory);
diff --git a/app/src/main/java/com/google/samples/apps/topeka/helper/AnswerHelper.java b/app/src/main/java/com/google/samples/apps/topeka/helper/AnswerHelper.java
index 87a5f2c7..5fdbb8cf 100644
--- a/app/src/main/java/com/google/samples/apps/topeka/helper/AnswerHelper.java
+++ b/app/src/main/java/com/google/samples/apps/topeka/helper/AnswerHelper.java
@@ -31,7 +31,6 @@ private AnswerHelper() {
//no instance
}
-
/**
* Converts an array of answers to a readable answer.
*
diff --git a/app/src/main/java/com/google/samples/apps/topeka/helper/ApiLevelHelper.java b/app/src/main/java/com/google/samples/apps/topeka/helper/ApiLevelHelper.java
index 7f855541..b1ff5410 100644
--- a/app/src/main/java/com/google/samples/apps/topeka/helper/ApiLevelHelper.java
+++ b/app/src/main/java/com/google/samples/apps/topeka/helper/ApiLevelHelper.java
@@ -23,6 +23,10 @@
*/
public class ApiLevelHelper {
+ private ApiLevelHelper() {
+ //no instance
+ }
+
/**
* Checks if the current api level is at least the provided value.
*
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 0f34f45a..4353d1e3 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
@@ -16,7 +16,9 @@
package com.google.samples.apps.topeka.helper;
+import android.annotation.TargetApi;
import android.app.Activity;
+import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.Pair;
@@ -31,6 +33,9 @@
*/
public class TransitionHelper {
+ private TransitionHelper() {
+ //no instance
+ }
/**
* Create the transition participants required during a activity transition while
* avoiding glitches with the system UI.
@@ -40,7 +45,8 @@ public class TransitionHelper {
* participant.
* @return All transition participants.
*/
- public static Pair[] createSafeTransitionParticipants(@NonNull Activity activity,
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static Pair[] createSafeTransitionParticipants(@NonNull Activity activity,
boolean includeStatusBar,
@Nullable Pair... otherParticipants) {
// Avoid system UI glitches as described here:
@@ -64,6 +70,7 @@ public static Pair[] createSafeTransitionParticipants(@NonNull Activity activity
return participants.toArray(new Pair[participants.size()]);
}
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static void addNonNullViewToTransitionParticipants(View view, List participants) {
if (view == null) {
return;
diff --git a/app/src/main/java/com/google/samples/apps/topeka/helper/ViewUtils.java b/app/src/main/java/com/google/samples/apps/topeka/helper/ViewUtils.java
new file mode 100644
index 00000000..5d6904b7
--- /dev/null
+++ b/app/src/main/java/com/google/samples/apps/topeka/helper/ViewUtils.java
@@ -0,0 +1,157 @@
+/*
+ * 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.helper;
+
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.v4.view.ViewCompat;
+import android.transition.ChangeBounds;
+import android.util.Property;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+public class ViewUtils {
+
+ private ViewUtils() {
+ //no instance
+ }
+
+ public static final Property FOREGROUND_COLOR =
+ new IntProperty("foregroundColor") {
+
+ @Override
+ public void setValue(FrameLayout layout, int value) {
+ if (layout.getForeground() instanceof ColorDrawable) {
+ ((ColorDrawable) layout.getForeground().mutate()).setColor(value);
+ } else {
+ layout.setForeground(new ColorDrawable(value));
+ }
+ }
+
+ @Override
+ public Integer get(FrameLayout layout) {
+ if (layout.getForeground() instanceof ColorDrawable) {
+ return ((ColorDrawable) layout.getForeground()).getColor();
+ } else {
+ return Color.TRANSPARENT;
+ }
+ }
+ };
+
+ public static final Property BACKGROUND_COLOR =
+ new IntProperty("backgroundColor") {
+
+ @Override
+ public void setValue(View view, int value) {
+ view.setBackgroundColor(value);
+ }
+
+ @Override
+ public Integer get(View view) {
+ Drawable d = view.getBackground();
+ if (d instanceof ColorDrawable) {
+ return ((ColorDrawable) d).getColor();
+ }
+ return Color.TRANSPARENT;
+ }
+ };
+
+ /**
+ * Allows changes to the text size in transitions and animations.
+ * Using this with something else than {@link ChangeBounds}
+ * can result in a severe performance penalty due to layout passes.
+ */
+ public static final Property PROPERTY_TEXT_SIZE =
+ new FloatProperty("textSize") {
+ @Override
+ public Float get(TextView view) {
+ return view.getTextSize();
+ }
+
+ @Override
+ public void setValue(TextView view, float textSize) {
+ view.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ }
+ };
+
+ /**
+ * Allows making changes to the start padding of a view.
+ * Using this with something else than {@link ChangeBounds}
+ * can result in a severe performance penalty due to layout passes.
+ */
+ public static final Property PROPERTY_TEXT_PADDING_START =
+ new IntProperty("paddingStart") {
+ @Override
+ public Integer get(TextView view) {
+ return ViewCompat.getPaddingStart(view);
+ }
+
+ @Override
+ public void setValue(TextView view, int paddingStart) {
+ ViewCompat.setPaddingRelative(view, paddingStart, view.getPaddingTop(),
+ ViewCompat.getPaddingEnd(view), view.getPaddingBottom());
+ }
+ };
+
+ public static abstract class IntProperty extends Property {
+
+ public IntProperty(String name) {
+ super(Integer.class, name);
+ }
+
+ /**
+ * A type-specific override of the {@link #set(Object, Integer)} that is faster when
+ * dealing
+ * with fields of type int
.
+ */
+ public abstract void setValue(T object, int value);
+
+ @Override
+ final public void set(T object, Integer value) {
+ //noinspection UnnecessaryUnboxing
+ setValue(object, value.intValue());
+ }
+ }
+
+ public static abstract class FloatProperty extends Property {
+
+ public FloatProperty(String name) {
+ super(Float.class, name);
+ }
+
+ /**
+ * A type-specific override of the {@link #set(Object, Float)} that is faster when dealing
+ * with fields of type int
.
+ */
+ public abstract void setValue(T object, float value);
+
+ @Override
+ final public void set(T object, Float value) {
+ //noinspection UnnecessaryUnboxing
+ setValue(object, value.floatValue());
+ }
+ }
+
+ public static void setPaddingStart(TextView target, int paddingStart) {
+ ViewCompat.setPaddingRelative(target, paddingStart, target.getPaddingTop(),
+ ViewCompat.getPaddingEnd(target), target.getPaddingBottom());
+ }
+
+}
diff --git a/app/src/main/java/com/google/samples/apps/topeka/widget/OffsetDecoration.java b/app/src/main/java/com/google/samples/apps/topeka/widget/OffsetDecoration.java
new file mode 100644
index 00000000..473f8b72
--- /dev/null
+++ b/app/src/main/java/com/google/samples/apps/topeka/widget/OffsetDecoration.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * 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 android.graphics.Rect;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+public class OffsetDecoration extends RecyclerView.ItemDecoration {
+
+ private final int mOffset;
+
+ public OffsetDecoration(int offset) {
+ mOffset = offset;
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view,
+ RecyclerView parent, RecyclerView.State state) {
+ outRect.left = mOffset;
+ outRect.right = mOffset;
+ outRect.bottom = mOffset;
+ outRect.top = mOffset;
+ }
+}
diff --git a/app/src/main/java/com/google/samples/apps/topeka/widget/TextResizeTransition.java b/app/src/main/java/com/google/samples/apps/topeka/widget/TextResizeTransition.java
new file mode 100644
index 00000000..601b5428
--- /dev/null
+++ b/app/src/main/java/com/google/samples/apps/topeka/widget/TextResizeTransition.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * 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.helper.ViewUtils;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.support.v4.view.ViewCompat;
+import android.transition.Transition;
+import android.transition.TransitionValues;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * A transition that resizes text of a TextView.
+ */
+@TargetApi(Build.VERSION_CODES.KITKAT)
+public class TextResizeTransition extends Transition {
+
+ private static final String PROPERTY_NAME_TEXT_RESIZE =
+ "com.google.samples.apps.topeka.widget:TextResizeTransition:textSize";
+ private static final String PROPERTY_NAME_PADDING_RESIZE =
+ "com.google.samples.apps.topeka.widget:TextResizeTransition:paddingStart";
+
+ private static final String[] TRANSITION_PROPERTIES = {PROPERTY_NAME_TEXT_RESIZE,
+ PROPERTY_NAME_PADDING_RESIZE };
+
+ public TextResizeTransition(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ private void captureValues(TransitionValues transitionValues) {
+ if (!(transitionValues.view instanceof TextView)) {
+ throw new UnsupportedOperationException("Doesn't work on "
+ + transitionValues.view.getClass().getName());
+ }
+ TextView view = (TextView) transitionValues.view;
+ transitionValues.values.put(PROPERTY_NAME_TEXT_RESIZE, view.getTextSize());
+ transitionValues.values.put(PROPERTY_NAME_PADDING_RESIZE,
+ ViewCompat.getPaddingStart(view));
+ }
+
+ @Override
+ public String[] getTransitionProperties() {
+ return TRANSITION_PROPERTIES;
+ }
+
+ @Override
+ public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return null;
+ }
+
+ float initialTextSize = (float) startValues.values.get(PROPERTY_NAME_TEXT_RESIZE);
+ float targetTextSize = (float) endValues.values.get(PROPERTY_NAME_TEXT_RESIZE);
+ TextView targetView = (TextView) endValues.view;
+ targetView.setTextSize(TypedValue.COMPLEX_UNIT_PX, initialTextSize);
+
+ int initialPaddingStart = (int) startValues.values.get(PROPERTY_NAME_PADDING_RESIZE);
+ int targetPaddingStart = (int) endValues.values.get(PROPERTY_NAME_PADDING_RESIZE);
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(
+ ObjectAnimator.ofFloat(targetView,
+ ViewUtils.PROPERTY_TEXT_SIZE,
+ initialTextSize,
+ targetTextSize),
+ ObjectAnimator.ofInt(targetView,
+ ViewUtils.PROPERTY_TEXT_PADDING_START,
+ initialPaddingStart,
+ targetPaddingStart));
+ return animatorSet;
+ }
+}
diff --git a/app/src/main/java/com/google/samples/apps/topeka/widget/TextSharedElementCallback.java b/app/src/main/java/com/google/samples/apps/topeka/widget/TextSharedElementCallback.java
new file mode 100644
index 00000000..407ce411
--- /dev/null
+++ b/app/src/main/java/com/google/samples/apps/topeka/widget/TextSharedElementCallback.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * 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.helper.ViewUtils;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.support.v4.app.SharedElementCallback;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * This callback allows a shared TextView to resize text and start padding during transition.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class TextSharedElementCallback extends SharedElementCallback {
+
+ private final int mInitialPaddingStart;
+ private float mInitialTextSize;
+ private float mTargetViewTextSize;
+ private int mTargetViewPaddingStart;
+
+ public TextSharedElementCallback(float initialTextSize, int initialPaddingStart) {
+ mInitialTextSize = initialTextSize;
+ mInitialPaddingStart = initialPaddingStart;
+ }
+
+ @Override
+ public void onSharedElementStart(List sharedElementNames, List sharedElements,
+ List sharedElementSnapshots) {
+ TextView targetView = (TextView) sharedElements.get(0);
+ mTargetViewTextSize = targetView.getTextSize();
+ mTargetViewPaddingStart = targetView.getPaddingStart();
+ // Setup the TextView's start values.
+ targetView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mInitialTextSize);
+ ViewUtils.setPaddingStart(targetView, mInitialPaddingStart);
+ }
+
+ @Override
+ public void onSharedElementEnd(List sharedElementNames, List sharedElements,
+ List sharedElementSnapshots) {
+ TextView initialView = (TextView) sharedElements.get(0);
+
+ // Setup the TextView's end values.
+ initialView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTargetViewTextSize);
+ ViewUtils.setPaddingStart(initialView, mTargetViewPaddingStart);
+
+ // Re-measure the TextView (since the text size has changed).
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ initialView.measure(widthSpec, heightSpec);
+ initialView.requestLayout();
+ }
+
+}
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 22f4c9b0..716a8aee 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
@@ -21,7 +21,6 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -30,7 +29,6 @@
import android.support.v4.content.ContextCompat;
import android.support.v4.view.MarginLayoutParamsCompat;
import android.support.v4.view.animation.LinearOutSlowInInterpolator;
-import android.util.IntProperty;
import android.util.Property;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -45,6 +43,7 @@
import com.google.samples.apps.topeka.R;
import com.google.samples.apps.topeka.activity.QuizActivity;
import com.google.samples.apps.topeka.helper.ApiLevelHelper;
+import com.google.samples.apps.topeka.helper.ViewUtils;
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.CheckableFab;
@@ -61,7 +60,7 @@
*
*
* @param The type of {@link com.google.samples.apps.topeka.model.quiz.Quiz} you want to
- * display.
+ * display.
*/
public abstract class AbsQuizView extends FrameLayout {
@@ -81,34 +80,12 @@ public abstract class AbsQuizView extends FrameLayout {
private Runnable mMoveOffScreenRunnable;
private InputMethodManager mInputMethodManager;
- private static final Property FOREGROUND_COLOR =
- new IntProperty("foregroundColor") {
-
- @Override
- public void setValue(AbsQuizView layout, int value) {
- if (layout.getForeground() instanceof ColorDrawable) {
- ((ColorDrawable) layout.getForeground().mutate()).setColor(value);
- } else {
- layout.setForeground(new ColorDrawable(value));
- }
- }
-
- @Override
- public Integer get(AbsQuizView layout) {
- if (layout.getForeground() instanceof ColorDrawable) {
- return ((ColorDrawable) layout.getForeground()).getColor();
- } else {
- return Color.TRANSPARENT;
- }
- }
- };
-
/**
* Enables creation of views for quizzes.
*
- * @param context The context for this view.
+ * @param context The context for this view.
* @param category The {@link Category} this view is running in.
- * @param quiz The actual {@link Quiz} that is going to be displayed.
+ * @param quiz The actual {@link Quiz} that is going to be displayed.
*/
public AbsQuizView(Context context, Category category, Q quiz) {
super(context);
@@ -207,6 +184,7 @@ public void onClick(View v) {
if (mInputMethodManager.isAcceptingText()) {
mInputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
+ mSubmitAnswer.setEnabled(false);
}
});
}
@@ -334,7 +312,7 @@ private void resizeView() {
// Animate X and Y scaling separately to allow different start delays.
// object animators for x and y with different durations and then run them independently
resizeViewProperty(View.SCALE_X, .5f, 200);
- resizeViewProperty(View.SCALE_Y, .5f / widthHeightRatio, 250);
+ resizeViewProperty(View.SCALE_Y, .5f / widthHeightRatio, 300);
}
private void resizeViewProperty(Property property,
@@ -358,7 +336,7 @@ protected void onDetachedFromWindow() {
}
private void animateForegroundColor(@ColorInt final int targetColor) {
- ObjectAnimator animator = ObjectAnimator.ofInt(this, FOREGROUND_COLOR,
+ ObjectAnimator animator = ObjectAnimator.ofInt(this, ViewUtils.FOREGROUND_COLOR,
Color.TRANSPARENT, targetColor);
animator.setEvaluator(new ArgbEvaluator());
animator.setStartDelay(FOREGROUND_COLOR_CHANGE_DELAY);
diff --git a/app/src/main/res/animator-v21/cross_to_tick_line_1.xml b/app/src/main/res/animator-v21/cross_to_tick_line_1.xml
index adf92a88..ae06ab94 100644
--- a/app/src/main/res/animator-v21/cross_to_tick_line_1.xml
+++ b/app/src/main/res/animator-v21/cross_to_tick_line_1.xml
@@ -19,6 +19,6 @@
android:propertyName="pathData"
android:valueFrom="@string/path_cross_line_1"
android:valueTo="@string/path_tick_line_1"
- android:duration="@integer/duration"
+ android:duration="@integer/tick_cross_duration"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:valueType="pathType" />
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
index deb4a1d5..190036c5 100644
--- 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
@@ -19,6 +19,6 @@
android:propertyName="pathData"
android:valueFrom="@string/path_cross_line_2"
android:valueTo="@string/path_tick_line_2"
- android:duration="@integer/duration"
+ android:duration="@integer/tick_cross_duration"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:valueType="pathType" />
diff --git a/app/src/main/res/animator-v21/rotate_cross_to_tick.xml b/app/src/main/res/animator-v21/rotate_cross_to_tick.xml
index d73e2994..72f5c471 100644
--- a/app/src/main/res/animator-v21/rotate_cross_to_tick.xml
+++ b/app/src/main/res/animator-v21/rotate_cross_to_tick.xml
@@ -19,5 +19,5 @@
android:propertyName="rotation"
android:valueFrom="-180"
android:valueTo="0"
- android:duration="@integer/duration"
+ android:duration="@integer/tick_cross_duration"
android:interpolator="@android:interpolator/fast_out_slow_in" />
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
index 437d5fa4..6904f936 100644
--- a/app/src/main/res/animator-v21/rotate_tick_to_cross.xml
+++ b/app/src/main/res/animator-v21/rotate_tick_to_cross.xml
@@ -19,5 +19,5 @@
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="180"
- android:duration="@integer/duration"
+ android:duration="@integer/tick_cross_duration"
android:interpolator="@android:interpolator/fast_out_slow_in" />
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
index 96118cd3..96b25cd1 100644
--- 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
@@ -19,6 +19,6 @@
android:propertyName="pathData"
android:valueFrom="@string/path_tick_line_1"
android:valueTo="@string/path_cross_line_1"
- android:duration="@integer/duration"
+ android:duration="@integer/tick_cross_duration"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:valueType="pathType" />
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
index 10cf1924..35094d7a 100644
--- 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
@@ -19,6 +19,6 @@
android:propertyName="pathData"
android:valueFrom="@string/path_tick_line_2"
android:valueTo="@string/path_cross_line_2"
- android:duration="@integer/duration"
+ android:duration="@integer/tick_cross_duration"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:valueType="pathType" />
diff --git a/app/src/main/res/drawable-v21/selector_categories.xml b/app/src/main/res/drawable-v21/selector_subtle.xml
similarity index 100%
rename from app/src/main/res/drawable-v21/selector_categories.xml
rename to app/src/main/res/drawable-v21/selector_subtle.xml
diff --git a/app/src/main/res/drawable/selector_categories.xml b/app/src/main/res/drawable/selector_subtle.xml
similarity index 100%
rename from app/src/main/res/drawable/selector_categories.xml
rename to app/src/main/res/drawable/selector_subtle.xml
diff --git a/app/src/main/res/layout-land/fragment_sign_in.xml b/app/src/main/res/layout-land/fragment_sign_in.xml
index 6eb47355..0797fcce 100644
--- a/app/src/main/res/layout-land/fragment_sign_in.xml
+++ b/app/src/main/res/layout-land/fragment_sign_in.xml
@@ -15,8 +15,10 @@
-->
diff --git a/app/src/main/res/layout/activity_category_selection.xml b/app/src/main/res/layout/activity_category_selection.xml
index c5806842..85cc1ed4 100644
--- a/app/src/main/res/layout/activity_category_selection.xml
+++ b/app/src/main/res/layout/activity_category_selection.xml
@@ -18,6 +18,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:background="@color/topeka_blank"
+ android:transitionGroup="false"
android:orientation="vertical">
-
-
+
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/quiz_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
-
-
+
-
-
+ android:layout_height="wrap_content">
+
+
+
+ android:layout_gravity="center">
+ android:scaleY="0.7"/>
+ android:visibility="invisible"/>
+ android:transitionName="@string/transition_avatar"/>
diff --git a/app/src/main/res/layout/activity_sign_in.xml b/app/src/main/res/layout/activity_sign_in.xml
index 58c1230c..87d5372e 100644
--- a/app/src/main/res/layout/activity_sign_in.xml
+++ b/app/src/main/res/layout/activity_sign_in.xml
@@ -14,10 +14,12 @@
~ limitations under the License.
-->
-
diff --git a/app/src/main/res/layout/fragment_categories.xml b/app/src/main/res/layout/fragment_categories.xml
index 584bb927..2f30e4d5 100644
--- a/app/src/main/res/layout/fragment_categories.xml
+++ b/app/src/main/res/layout/fragment_categories.xml
@@ -14,20 +14,16 @@
~ limitations under the License.
-->
-
+ android:scrollbars="vertical"
+ app:layoutManager="android.support.v7.widget.GridLayoutManager"
+ android:transitionGroup="false"
+ app:spanCount="2" />
diff --git a/app/src/main/res/layout/fragment_quiz.xml b/app/src/main/res/layout/fragment_quiz.xml
index 168c8ccb..f02d54aa 100644
--- a/app/src/main/res/layout/fragment_quiz.xml
+++ b/app/src/main/res/layout/fragment_quiz.xml
@@ -17,7 +17,8 @@
+ android:orientation="vertical"
+ android:background="?android:attr/windowBackground">
+ android:visibility="gone"/>
+ android:layout_weight="1"/>
+ android:progressTint="?android:colorAccent"/>
+ android:layout_height="wrap_content"
+ android:textColor="?android:attr/textColorPrimary"/>
diff --git a/app/src/main/res/layout/fragment_sign_in.xml b/app/src/main/res/layout/fragment_sign_in.xml
index 4489179f..0b17107e 100644
--- a/app/src/main/res/layout/fragment_sign_in.xml
+++ b/app/src/main/res/layout/fragment_sign_in.xml
@@ -15,6 +15,7 @@
-->
@@ -34,20 +35,18 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
- android:orientation="vertical"
- android:paddingEnd="@dimen/spacing_double"
- android:paddingStart="@dimen/spacing_double">
+ android:orientation="vertical">
diff --git a/app/src/main/res/layout/item_category.xml b/app/src/main/res/layout/item_category.xml
index 9f8f753a..8efd8df0 100644
--- a/app/src/main/res/layout/item_category.xml
+++ b/app/src/main/res/layout/item_category.xml
@@ -14,23 +14,27 @@
~ limitations under the License.
-->
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/sign_in_avatars.xml b/app/src/main/res/layout/sign_in_avatars.xml
index 27f1dc39..b8aa0112 100644
--- a/app/src/main/res/layout/sign_in_avatars.xml
+++ b/app/src/main/res/layout/sign_in_avatars.xml
@@ -24,18 +24,23 @@
+ android:text="@string/choose_avatar"
+ android:paddingEnd="@dimen/spacing_double"
+ android:paddingStart="@dimen/spacing_double"
+ android:clipToPadding="false" />
+ android:verticalSpacing="@dimen/spacing_double"
+ android:paddingEnd="@dimen/spacing_double"
+ android:paddingStart="@dimen/spacing_double"
+ android:clipToPadding="false" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/sign_in_username.xml b/app/src/main/res/layout/sign_in_username.xml
index 41a7a875..a46adb45 100644
--- a/app/src/main/res/layout/sign_in_username.xml
+++ b/app/src/main/res/layout/sign_in_username.xml
@@ -24,35 +24,54 @@
+ android:text="@string/sign_in"
+ android:paddingEnd="@dimen/spacing_double"
+ android:paddingStart="@dimen/spacing_double"
+ android:clipToPadding="false" />
-
+ android:paddingEnd="@dimen/spacing_double"
+ android:paddingBottom="@dimen/spacing_micro"
+ android:clipToPadding="false"
+ android:transitionGroup="true">
+
+
-
+ android:paddingEnd="@dimen/spacing_double"
+ android:paddingBottom="@dimen/spacing_micro"
+ android:clipToPadding="false"
+ android:transitionGroup="true">
+
+
\ No newline at end of file
diff --git a/app/src/main/res/transition/category_enter.xml b/app/src/main/res/transition/category_enter.xml
index dbc1a273..fd4e24ca 100644
--- a/app/src/main/res/transition/category_enter.xml
+++ b/app/src/main/res/transition/category_enter.xml
@@ -15,9 +15,16 @@
-->
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/transition/category_exit.xml b/app/src/main/res/transition/category_exit.xml
new file mode 100644
index 00000000..a67ba032
--- /dev/null
+++ b/app/src/main/res/transition/category_exit.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/transition/quiz_enter.xml b/app/src/main/res/transition/category_shared_enter.xml
similarity index 88%
rename from app/src/main/res/transition/quiz_enter.xml
rename to app/src/main/res/transition/category_shared_enter.xml
index 52eb8c02..42e7f1b7 100644
--- a/app/src/main/res/transition/quiz_enter.xml
+++ b/app/src/main/res/transition/category_shared_enter.xml
@@ -15,10 +15,7 @@
-->
-
-
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/transition/quiz_shared_enter.xml b/app/src/main/res/transition/quiz_shared_enter.xml
new file mode 100644
index 00000000..e488c2d3
--- /dev/null
+++ b/app/src/main/res/transition/quiz_shared_enter.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/transition/signin_enter.xml b/app/src/main/res/transition/signin_enter.xml
new file mode 100644
index 00000000..078c31ea
--- /dev/null
+++ b/app/src/main/res/transition/signin_enter.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/transition/signin_exit.xml b/app/src/main/res/transition/signin_exit.xml
new file mode 100644
index 00000000..b446049c
--- /dev/null
+++ b/app/src/main/res/transition/signin_exit.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
index c0f2d5a0..13dcf8f4 100644
--- a/app/src/main/res/values-v21/styles.xml
+++ b/app/src/main/res/values-v21/styles.xml
@@ -1,4 +1,3 @@
-
+ 2dp
4dp
8dp
16dp
@@ -32,4 +33,5 @@
4dp
18sp
+ 14sp
diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml
index f8bc9f76..b4b8ac2f 100644
--- a/app/src/main/res/values/ids.xml
+++ b/app/src/main/res/values/ids.xml
@@ -20,4 +20,7 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 2416129d..7ee26920 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -28,7 +28,6 @@
- @color/topeka_primary_dark
- @color/text_dark
- @style/Topeka.CompoundButton.Radio
- - true
- @style/Topeka.CompoundButton
- @color/light_grey
- @color/light_grey
@@ -36,6 +35,7 @@
+
+
+
diff --git a/app/src/main/res/values/tick_cross.xml b/app/src/main/res/values/tick_cross.xml
index 785e81ad..1b49cba0 100644
--- a/app/src/main/res/values/tick_cross.xml
+++ b/app/src/main/res/values/tick_cross.xml
@@ -36,6 +36,6 @@
@android:color/black
- 450
+ 450
diff --git a/app/src/main/res/values/transition_names.xml b/app/src/main/res/values/transition_names.xml
index e028cb70..a4cdf229 100644
--- a/app/src/main/res/values/transition_names.xml
+++ b/app/src/main/res/values/transition_names.xml
@@ -16,6 +16,5 @@
AvatarTransition
- BackgroundTransition
ToolbarTransition
\ No newline at end of file
diff --git a/app/src/main/res/values/transitions.xml b/app/src/main/res/values/transitions.xml
new file mode 100644
index 00000000..1e70ce7e
--- /dev/null
+++ b/app/src/main/res/values/transitions.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ - 350
+
\ No newline at end of file