diff --git a/app/src/main/java/com/hippo/animation/ArgbEvaluator.java b/app/src/main/java/com/hippo/animation/ArgbEvaluator.java new file mode 100644 index 000000000..b575fb129 --- /dev/null +++ b/app/src/main/java/com/hippo/animation/ArgbEvaluator.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2010 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 com.hippo.animation; + +import android.animation.TypeEvaluator; + +/** + * This evaluator can be used to perform type interpolation between integer + * values that represent ARGB colors. + */ +public class ArgbEvaluator implements TypeEvaluator { + private static final ArgbEvaluator sInstance = new ArgbEvaluator(); + + /** + * Returns an instance of ArgbEvaluator that may be used in + * {@link android.animation.ValueAnimator#setEvaluator(TypeEvaluator)}. The same instance may + * be used in multiple Animators because it holds no state. + * @return An instance of ArgbEvalutor. + */ + public static ArgbEvaluator getInstance() { + return sInstance; + } + + /** + * This function returns the calculated in-between value for a color + * given integers that represent the start and end values in the four + * bytes of the 32-bit int. Each channel is separately linearly interpolated + * and the resulting calculated values are recombined into the return value. + * + * @param fraction The fraction from the starting to the ending values + * @param startValue A 32-bit int value representing colors in the + * separate bytes of the parameter + * @param endValue A 32-bit int value representing colors in the + * separate bytes of the parameter + * @return A value that is calculated to be the linearly interpolated + * result, derived by separating the start and end values into separate + * color channels and interpolating each one separately, recombining the + * resulting values in the same way. + */ + public Object evaluate(float fraction, Object startValue, Object endValue) { + int startInt = (Integer) startValue; + int startA = (startInt >> 24) & 0xff; + int startR = (startInt >> 16) & 0xff; + int startG = (startInt >> 8) & 0xff; + int startB = startInt & 0xff; + + int endInt = (Integer) endValue; + int endA = (endInt >> 24) & 0xff; + int endR = (endInt >> 16) & 0xff; + int endG = (endInt >> 8) & 0xff; + int endB = endInt & 0xff; + + return ((startA + (int)(fraction * (endA - startA))) << 24) | + ((startR + (int)(fraction * (endR - startR))) << 16) | + ((startG + (int)(fraction * (endG - startG))) << 8) | + ((startB + (int)(fraction * (endB - startB)))); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java index bd86d8747..c352f48e4 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java @@ -62,15 +62,14 @@ public class GalleryListScene extends Scene implements SearchLayout.SearhLayoutH private PagerAdapter mPagerAdapter; - @Override - public void onCreate(Bundle savedInstanceState) { - mActivity = (ContentActivity) getStageActivity(); - mResources = mActivity.getResources(); - } - @SuppressLint("InflateParams") @Override - public View onCreateSceneView(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mActivity = (ContentActivity) getStageActivity(); + mResources = mActivity.getResources(); + LayoutInflater inflater = mActivity.getLayoutInflater(); // Search View @@ -94,9 +93,9 @@ public class GalleryListScene extends Scene implements SearchLayout.SearhLayoutH }); // Main View - View sceneView = inflater.inflate(R.layout.scene_gallery_list, null); - mAppbar = (Appbar) sceneView.findViewById(R.id.appbar); - mViewPager = (ViewPager) sceneView.findViewById(R.id.viewPager); + setContentView(R.layout.scene_gallery_list); + mAppbar = (Appbar) findViewById(R.id.appbar); + mViewPager = (ViewPager) findViewById(R.id.viewPager); mAppbar.setTitle(mResources.getString(R.string.app_name)); @@ -110,8 +109,6 @@ public class GalleryListScene extends Scene implements SearchLayout.SearhLayoutH if (fitPaddingBottom != -1) { setFitPaddingBottom(fitPaddingBottom); } - - return sceneView; } private void setFitPaddingBottom(int fitPaddingBottom) { @@ -195,5 +192,4 @@ public class GalleryListScene extends Scene implements SearchLayout.SearhLayoutH view.removeView((View) object); } } - } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/TestScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/TestScene.java index 8f58c26f9..805e77072 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/TestScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/TestScene.java @@ -17,8 +17,6 @@ package com.hippo.ehviewer.ui.scene; import android.content.res.Resources; import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; import com.hippo.ehviewer.R; import com.hippo.ehviewer.ui.ContentActivity; @@ -31,14 +29,10 @@ public class TestScene extends Scene { @Override public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.scene_test); + mActivity = (ContentActivity) getStageActivity(); mResources = mActivity.getResources(); } - - @SuppressWarnings("InflateParams") - public View onCreateSceneView(Bundle savedInstanceState) { - LayoutInflater inflater = mActivity.getLayoutInflater(); - - return inflater.inflate(R.layout.scene_test, null); - } } diff --git a/app/src/main/java/com/hippo/scene/Scene.java b/app/src/main/java/com/hippo/scene/Scene.java index 55b76113c..4e36ad693 100644 --- a/app/src/main/java/com/hippo/scene/Scene.java +++ b/app/src/main/java/com/hippo/scene/Scene.java @@ -23,7 +23,9 @@ import android.support.annotation.Nullable; import android.support.v4.app.FragmentManager; import android.util.SparseArray; import android.view.View; +import android.view.ViewGroup; import android.view.ViewParent; +import android.widget.FrameLayout; import com.hippo.util.AssertUtils; @@ -35,7 +37,9 @@ import com.hippo.util.AssertUtils; */ public abstract class Scene { - private @Nullable View mSceneView; + private @Nullable FrameLayout mSceneView; + + private int mBackgroundColor = 0xffeeeeee; // TODO Need a better to set background color private static SceneManager sSceneManager; @@ -50,7 +54,12 @@ public abstract class Scene { return stageActivity; } - @Nullable View getSceneView() { + /** + * You will get null before onCreate and after onDestroy mostly + * + * @return Null or nonull + */ + public @Nullable View getSceneView() { return mSceneView; } @@ -73,7 +82,11 @@ public abstract class Scene { void create(Bundle savedInstanceState) { onCreate(savedInstanceState); - mSceneView = onCreateSceneView(savedInstanceState); + + if (mSceneView == null) { + mSceneView = new FrameLayout(getStageActivity()); + initBackground(mSceneView); + } // Make sure scene view is attach from stage attachToStage(); @@ -104,15 +117,45 @@ public abstract class Scene { protected void onCreate(Bundle savedInstanceState) { } - /** - * It is called after {@link #onCreate(android.os.Bundle)}. - * Create view of the scene. - * - * @param savedInstanceState null for first time, non null for recrearte - * @return the view of the scene - */ - protected @Nullable View onCreateSceneView(Bundle savedInstanceState) { - return null; + public void setBackgroundColor(int bgColor) { + if (mBackgroundColor != bgColor) { + mBackgroundColor = bgColor; + View sceneView = getSceneView(); + if (sceneView != null) { + sceneView.setBackgroundColor(bgColor); + } + } + } + + public int getBackgroundColor() { + return mBackgroundColor; + } + + + + protected void setContentView(int resId) { + AssertUtils.assertNull("Only call setContentView once", mSceneView); + + StageActivity sa = getStageActivity(); + mSceneView = new FrameLayout(sa); + initBackground(mSceneView); + mSceneView.setBackgroundColor(mBackgroundColor); + sa.getLayoutInflater().inflate(resId, mSceneView); + } + + protected void setContentView(View view) { + AssertUtils.assertNull("Only call setContentView once", mSceneView); + + StageActivity sa = getStageActivity(); + mSceneView = new FrameLayout(sa); + initBackground(mSceneView); + mSceneView.addView(view, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + } + + private void initBackground(@NonNull View bg) { + bg.setBackgroundColor(mBackgroundColor); + bg.setClickable(true); } protected void onResume() { @@ -125,6 +168,23 @@ public abstract class Scene { } + /** + * Finds a view that was identified by the id attribute from the XML that + * was processed in {@link #onCreate}. + * + * @param resId the Id + * @return The view if found or null otherwise. + */ + public View findViewById(int resId) { + View sceneView = getSceneView(); + if (sceneView != null) { + return sceneView.findViewById(resId); + } else { + return null; + } + } + + /** * Is scene view attached to stage * diff --git a/app/src/main/java/com/hippo/scene/SceneManager.java b/app/src/main/java/com/hippo/scene/SceneManager.java index e1147e01e..180d95f25 100644 --- a/app/src/main/java/com/hippo/scene/SceneManager.java +++ b/app/src/main/java/com/hippo/scene/SceneManager.java @@ -85,7 +85,7 @@ class SceneManager { scene.destroy(false); if (previousState != null) { - curtain.open(previousState, scene); + curtain.close(previousState, scene); } } else { scene.destroy(true); @@ -138,7 +138,8 @@ class SceneManager { protected void onSaveInstanceState(Bundle outState) { for (Pair p : mSceneStack) { - p.first.saveInstanceState(outState); + Scene scene = p.first; + scene.saveInstanceState(outState); } } diff --git a/app/src/main/java/com/hippo/scene/TransitionCurtain.java b/app/src/main/java/com/hippo/scene/TransitionCurtain.java index c1a1a0486..8fb69f575 100644 --- a/app/src/main/java/com/hippo/scene/TransitionCurtain.java +++ b/app/src/main/java/com/hippo/scene/TransitionCurtain.java @@ -15,17 +15,21 @@ package com.hippo.scene; +import android.animation.Animator; import android.animation.ObjectAnimator; +import android.graphics.Color; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.View; import android.view.ViewTreeObserver; +import com.hippo.animation.ArgbEvaluator; import com.hippo.util.ViewUtils; public class TransitionCurtain extends Curtain { - private static long ANIMATE_TIME = 300L; + private static long ANIMATE_DELAY = 500L; + private static long ANIMATE_TIME = 800L; private ViewPair[] mViewPairArray; @@ -33,14 +37,28 @@ public class TransitionCurtain extends Curtain { mViewPairArray = viewPairArray; } + + // Make background from transport to default. + // Invisible other @Override public void open(final @NonNull Scene enter, final @NonNull Scene exit) { + // Handle background + final int bgColor = enter.getBackgroundColor(); + enter.setBackgroundColor(Color.TRANSPARENT); + + ObjectAnimator colorAnim = ObjectAnimator.ofInt(enter, "backgroundColor", bgColor); + colorAnim.setEvaluator(ArgbEvaluator.getInstance()); + colorAnim.setDuration(ANIMATE_TIME); + + colorAnim.start(); + + // Handle transit part for (ViewPair pair : mViewPairArray) { final View enterView = pair.getToView(enter); final View exitView = pair.getFromView(exit); if (enterView == null || exitView == null) { - // Can't get to view + // Can't get view continue; } @@ -53,9 +71,9 @@ public class TransitionCurtain extends Curtain { ViewUtils.removeOnGlobalLayoutListener(enterView.getViewTreeObserver(), this); int[] startloaction = new int[2]; - ViewUtils.getLocationInAncestor(exitView, startloaction, exit.getStageActivity().getStageLayout()); + ViewUtils.getLocationInAncestor(exitView, startloaction, exit.getSceneView()); int[] endloaction = new int[2]; - ViewUtils.getLocationInAncestor(enterView, endloaction, enter.getStageActivity().getStageLayout()); + ViewUtils.getLocationInAncestor(enterView, endloaction, enter.getSceneView()); int startWidth = exitView.getWidth(); int startHeight = exitView.getHeight(); int endWidth = enterView.getWidth(); @@ -63,6 +81,8 @@ public class TransitionCurtain extends Curtain { ViewUtils.setVisibility(exitView, View.INVISIBLE); ViewUtils.setVisibility(enterView, View.VISIBLE); + enterView.setPivotX(0); + enterView.setPivotY(0); // Start animation ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(enterView, "scaleX", (float) startWidth / endWidth, 1f); @@ -74,18 +94,93 @@ public class TransitionCurtain extends Curtain { ObjectAnimator yAnim = ObjectAnimator.ofFloat(enterView, "y", startloaction[1], endloaction[1]); yAnim.setDuration(ANIMATE_TIME); + yAnim.addListener(new SimpleAnimatorListener() { + @Override + public void onAnimationEnd(Animator animation) { + ViewUtils.setVisibility(exitView, View.VISIBLE); + } + + @Override + public void onAnimationCancel(Animator animation) { + ViewUtils.setVisibility(exitView, View.VISIBLE); + } + }); + scaleXAnim.start(); scaleYAnim.start(); xAnim.start(); yAnim.start(); + + // TODO show other part progressively } }); } } @Override - public void close(@NonNull Scene enter, @NonNull Scene exit) { - dispatchDetachFromeStage(exit); + public void close(@NonNull Scene enter, final @NonNull Scene exit) { + + // Handle background + ObjectAnimator colorAnim = ObjectAnimator.ofInt(exit, "backgroundColor", Color.TRANSPARENT); + colorAnim.setEvaluator(ArgbEvaluator.getInstance()); + colorAnim.setDuration(ANIMATE_TIME); + + colorAnim.start(); + + // Handle transit part + for (ViewPair pair : mViewPairArray) { + final View enterView = pair.getFromView(enter); + final View exitView = pair.getToView(exit); + if (enterView == null || exitView == null) { + // Can't get view + continue; + } + + int[] startloaction = new int[2]; + ViewUtils.getLocationInAncestor(exitView, startloaction, exit.getSceneView()); + int[] endloaction = new int[2]; + ViewUtils.getLocationInAncestor(enterView, endloaction, enter.getSceneView()); + int startWidth = exitView.getWidth(); + int startHeight = exitView.getHeight(); + int endWidth = enterView.getWidth(); + int endHeight = enterView.getHeight(); + + ViewUtils.setVisibility(exitView, View.VISIBLE); + ViewUtils.setVisibility(enterView, View.INVISIBLE); + exitView.setPivotX(0); + exitView.setPivotY(0); + + // Start animation + ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(exitView, "scaleX", (float) endWidth / startWidth); + scaleXAnim.setDuration(ANIMATE_TIME); + ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(exitView, "scaleY", (float) endHeight / startHeight); + scaleYAnim.setDuration(ANIMATE_TIME); + ObjectAnimator xAnim = ObjectAnimator.ofFloat(exitView, "x", endloaction[0]); + xAnim.setDuration(ANIMATE_TIME); + ObjectAnimator yAnim = ObjectAnimator.ofFloat(exitView, "y", endloaction[1]); + yAnim.setDuration(ANIMATE_TIME); + + yAnim.addListener(new SimpleAnimatorListener() { + @Override + public void onAnimationEnd(Animator animation) { + dispatchDetachFromeStage(exit); + ViewUtils.setVisibility(enterView, View.VISIBLE); + } + + @Override + public void onAnimationCancel(Animator animation) { + dispatchDetachFromeStage(exit); + ViewUtils.setVisibility(enterView, View.VISIBLE); + } + }); + + scaleXAnim.start(); + scaleYAnim.start(); + xAnim.start(); + yAnim.start(); + + // TODO show other part progressively + } } public static class ViewPair { @@ -132,4 +227,23 @@ public class TransitionCurtain extends Curtain { public abstract View getToView(@Nullable View sceneView); } + + public class SimpleAnimatorListener implements Animator.AnimatorListener { + + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + } } diff --git a/app/src/main/java/com/hippo/util/AssertUtils.java b/app/src/main/java/com/hippo/util/AssertUtils.java index 55518e762..51aeb382e 100644 --- a/app/src/main/java/com/hippo/util/AssertUtils.java +++ b/app/src/main/java/com/hippo/util/AssertUtils.java @@ -17,6 +17,12 @@ package com.hippo.util; public class AssertUtils { + public static void assertNull(String message, Object object) { + if (object != null) { + throw new AssertError(message); + } + } + public static void assertNotNull(String message, Object object) { if (object == null) { throw new AssertError(message); @@ -29,6 +35,13 @@ public class AssertUtils { } } + public static void assertNullEx(String message, Object object) + throws AssertException { + if (object != null) { + throw new AssertException(message); + } + } + public static void assertNotNullEx(String message, Object object) throws AssertException { if (object == null) { diff --git a/app/src/main/res/layout/fragment_list.xml b/app/src/main/res/layout/fragment_list.xml index f0613fda1..9d3f3aeca 100644 --- a/app/src/main/res/layout/fragment_list.xml +++ b/app/src/main/res/layout/fragment_list.xml @@ -30,6 +30,6 @@ android:layout_height="wrap_content" android:text="Click" android:id="@+id/test_button" - android:layout_gravity="center_horizontal|bottom"/> + android:layout_gravity="right|center_vertical"/> diff --git a/app/src/main/res/layout/scene_test.xml b/app/src/main/res/layout/scene_test.xml index ebb8a065a..d8660791e 100644 --- a/app/src/main/res/layout/scene_test.xml +++ b/app/src/main/res/layout/scene_test.xml @@ -23,7 +23,7 @@ android:id="@+id/haha" android:layout_width="120dp" android:layout_height="120dp" - android:layout_gravity="bottom|right" + android:layout_gravity="right|top" android:src="@drawable/ic_happy" android:scaleType="fitXY"/>