diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index c9efb0c790..9fcdbe8cd1 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -277,7 +277,7 @@ public final class NativeLibrary public static void endEmulationActivity() { Log.v("DolphinEmu", "Ending EmulationActivity."); - mEmulationActivity.finish(); + mEmulationActivity.exitWithAnimation(); } public static void setEmulationActivity(EmulationActivity emulationActivity) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java index d1e6f47273..6b27c08c4a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java @@ -12,6 +12,12 @@ import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.squareup.picasso.Callback; +import com.squareup.picasso.Picasso; import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.R; @@ -22,10 +28,15 @@ import java.util.List; public final class EmulationActivity extends AppCompatActivity { private View mDecorView; + private ImageView mImageView; + private FrameLayout mFrameEmulation; private boolean mDeviceHasTouchScreen; private boolean mSystemUiVisible; + // So that MainActivity knows which view to invalidate before the return animation. + private int mPosition; + /** * Handlers are a way to pass a message to an Activity telling it to do something * on the UI thread. This Handler responds to any message, even blank ones, by @@ -39,12 +50,18 @@ public final class EmulationActivity extends AppCompatActivity hideSystemUI(); } }; + private String mScreenPath; + private FrameLayout mFrameContent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // Picasso will take a while to load these big-ass screenshots. So don't run + // the animation until we say so. + postponeEnterTransition(); + mDeviceHasTouchScreen = getPackageManager().hasSystemFeature("android.hardware.touchscreen"); // Get a handle to the Window containing the UI. @@ -79,9 +96,57 @@ public final class EmulationActivity extends AppCompatActivity setContentView(R.layout.activity_emulation); + mImageView = (ImageView) findViewById(R.id.image_screenshot); + mFrameContent = (FrameLayout) findViewById(R.id.frame_content); + mFrameEmulation = (FrameLayout) findViewById(R.id.frame_emulation_fragment); + Intent gameToEmulate = getIntent(); String path = gameToEmulate.getStringExtra("SelectedGame"); String title = gameToEmulate.getStringExtra("SelectedTitle"); + mScreenPath = gameToEmulate.getStringExtra("ScreenPath"); + mPosition = gameToEmulate.getIntExtra("GridPosition", -1); + + Picasso.with(this) + .load(mScreenPath) + .noFade() + .noPlaceholder() + .into(mImageView, new Callback() + { + @Override + public void onSuccess() + { + scheduleStartPostponedTransition(mImageView); + } + + @Override + public void onError() + { + // Still have to do this, or else the app will crash. + scheduleStartPostponedTransition(mImageView); + } + }); + + mImageView.animate() + .withLayer() + .setStartDelay(2000) + .setDuration(500) + .alpha(0.0f) + .withStartAction(new Runnable() + { + @Override + public void run() + { + mFrameEmulation.setVisibility(View.VISIBLE); + } + }) + .withEndAction(new Runnable() + { + @Override + public void run() + { + mImageView.setVisibility(View.GONE); + } + }); setTitle(title); @@ -90,7 +155,7 @@ public final class EmulationActivity extends AppCompatActivity // Add fragment to the activity - this triggers all its lifecycle callbacks. getFragmentManager().beginTransaction() - .add(R.id.frame_content, emulationFragment, EmulationFragment.FRAGMENT_TAG) + .add(R.id.frame_emulation_fragment, emulationFragment, EmulationFragment.FRAGMENT_TAG) .commit(); } @@ -155,6 +220,59 @@ public final class EmulationActivity extends AppCompatActivity } } + public void exitWithAnimation() + { + runOnUiThread(new Runnable() + { + @Override + public void run() + { + Picasso.with(EmulationActivity.this) + .invalidate(mScreenPath); + + Picasso.with(EmulationActivity.this) + .load(mScreenPath) + .noFade() + .noPlaceholder() + .into(mImageView, new Callback() + { + @Override + public void onSuccess() + { + showScreenshot(); + } + + @Override + public void onError() + { + finish(); + } + }); + } + }); + } + + private void showScreenshot() + { + mImageView.setVisibility(View.VISIBLE); + mImageView.animate() + .withLayer() + .setDuration(500) + .alpha(1.0f) + .withEndAction(afterShowingScreenshot); + } + + private Runnable afterShowingScreenshot = new Runnable() + { + @Override + public void run() + { + mFrameContent.removeView(mFrameEmulation); + setResult(mPosition); + finishAfterTransition(); + } + }; + @Override public boolean onCreateOptionsMenu(Menu menu) { @@ -324,4 +442,20 @@ public final class EmulationActivity extends AppCompatActivity hideSystemUiAfterDelay(); } + + + private void scheduleStartPostponedTransition(final View sharedElement) + { + sharedElement.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() + { + @Override + public boolean onPreDraw() + { + sharedElement.getViewTreeObserver().removeOnPreDrawListener(this); + startPostponedEnterTransition(); + return true; + } + }); + } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/MainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/MainActivity.java index 7812e6ead5..33cd0b0c15 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/MainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/MainActivity.java @@ -36,6 +36,7 @@ import org.dolphinemu.dolphinemu.services.AssetCopyService; public final class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks { private static final int REQUEST_ADD_DIRECTORY = 1; + public static final int REQUEST_EMULATE_GAME = 2; /** * It is important to keep track of loader ID separately from platform ID (see Game.java) @@ -115,15 +116,29 @@ public final class MainActivity extends AppCompatActivity implements LoaderManag @Override protected void onActivityResult(int requestCode, int resultCode, Intent result) { - // If the user picked a file, as opposed to just backing out. - if (resultCode == RESULT_OK) + switch (requestCode) { - // Sanity check to make sure the Activity that just returned was the AddDirectoryActivity; - // other activities might use this callback in the future (don't forget to change Javadoc!) - if (requestCode == REQUEST_ADD_DIRECTORY) - { - refreshFragment(); - } + case REQUEST_ADD_DIRECTORY: + // If the user picked a file, as opposed to just backing out. + if (resultCode == RESULT_OK) + { + // Sanity check to make sure the Activity that just returned was the AddDirectoryActivity; + // other activities might use this callback in the future (don't forget to change Javadoc!) + if (requestCode == REQUEST_ADD_DIRECTORY) + { + refreshFragment(); + } + } + break; + + case REQUEST_EMULATE_GAME: + // Invalidate Picasso image so that the new screenshot is animated in. + PlatformGamesFragment fragment = getPlatformFragment(mViewPager.getCurrentItem()); + + if (fragment != null) + { + fragment.refreshScreenshotAtPosition(resultCode); + } } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java index a9a7be930e..e74bd6f96d 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java @@ -1,9 +1,11 @@ package org.dolphinemu.dolphinemu.adapters; import android.app.Activity; +import android.app.ActivityOptions; import android.content.Intent; import android.database.Cursor; import android.database.DataSetObserver; +import android.graphics.Bitmap; import android.graphics.Rect; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -15,6 +17,7 @@ import com.squareup.picasso.Picasso; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.activities.EmulationActivity; +import org.dolphinemu.dolphinemu.activities.MainActivity; import org.dolphinemu.dolphinemu.dialogs.GameDetailsDialog; import org.dolphinemu.dolphinemu.model.GameDatabase; import org.dolphinemu.dolphinemu.viewholders.GameViewHolder; @@ -80,14 +83,15 @@ public final class GameAdapter extends RecyclerView.Adapter impl if (mCursor.moveToPosition(position)) { String screenPath = mCursor.getString(GameDatabase.GAME_COLUMN_SCREENSHOT_PATH); - Picasso.with(holder.imageScreenshot.getContext()) - .invalidate(screenPath); // Fill in the view contents. Picasso.with(holder.imageScreenshot.getContext()) .load(screenPath) .fit() .centerCrop() + .noFade() + .noPlaceholder() + .config(Bitmap.Config.RGB_565) .error(R.drawable.no_banner) .into(holder.imageScreenshot); @@ -112,8 +116,6 @@ public final class GameAdapter extends RecyclerView.Adapter impl { Log.e("DolphinEmu", "Can't bind view; dataset is not valid."); } - - } /** @@ -220,8 +222,17 @@ public final class GameAdapter extends RecyclerView.Adapter impl intent.putExtra("SelectedGame", holder.path); intent.putExtra("SelectedTitle", holder.title); + intent.putExtra("ScreenPath", holder.screenshotPath); + intent.putExtra("GridPosition", holder.getAdapterPosition()); - view.getContext().startActivity(intent); + ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation( + (Activity) view.getContext(), + holder.imageScreenshot, + "image_game_screenshot"); + + ((Activity) view.getContext()).startActivityForResult(intent, + MainActivity.REQUEST_EMULATE_GAME, + options.toBundle()); } /** diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/PlatformGamesFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/PlatformGamesFragment.java index 4957090ed3..c1d1b1c8e7 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/PlatformGamesFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/PlatformGamesFragment.java @@ -1,12 +1,12 @@ package org.dolphinemu.dolphinemu.fragments; -import android.app.Activity; import android.app.Fragment; import android.app.LoaderManager; import android.content.Loader; import android.database.Cursor; import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -57,6 +57,15 @@ public class PlatformGamesFragment extends Fragment RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getActivity(), getResources().getInteger(R.integer.game_grid_columns)); recyclerView.setLayoutManager(layoutManager); + recyclerView.setItemAnimator(new DefaultItemAnimator() + { + @Override + public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) + { + dispatchChangeFinished(newHolder, false); + return true; + } + }); recyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(8)); @@ -70,10 +79,9 @@ public class PlatformGamesFragment extends Fragment return rootView; } - @Override - public void onAttach(Activity activity) + public void refreshScreenshotAtPosition(int position) { - super.onAttach(activity); + mAdapter.notifyItemChanged(position); } public void refresh() diff --git a/Source/Android/app/src/main/res/layout/activity_emulation.xml b/Source/Android/app/src/main/res/layout/activity_emulation.xml index 63ea99ba83..9e4e84e151 100644 --- a/Source/Android/app/src/main/res/layout/activity_emulation.xml +++ b/Source/Android/app/src/main/res/layout/activity_emulation.xml @@ -1,5 +1,19 @@ + android:id="@+id/frame_content" + > + + + + + \ No newline at end of file diff --git a/Source/Android/app/src/main/res/layout/card_game.xml b/Source/Android/app/src/main/res/layout/card_game.xml index b18bc9e7ee..9b172b6871 100644 --- a/Source/Android/app/src/main/res/layout/card_game.xml +++ b/Source/Android/app/src/main/res/layout/card_game.xml @@ -20,7 +20,7 @@ android:id="@+id/image_game_screen" android:layout_width="match_parent" android:layout_height="0dp" - android:transitionName="image_game_screen" + android:transitionName="image_game_screenshot" android:layout_weight="1" tools:src="@drawable/placeholder_screenshot" tools:scaleType="centerCrop"/> diff --git a/Source/Android/app/src/main/res/transition/change_image_transform.xml b/Source/Android/app/src/main/res/transition/change_image_transform.xml new file mode 100644 index 0000000000..3ee92bd7c4 --- /dev/null +++ b/Source/Android/app/src/main/res/transition/change_image_transform.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Source/Android/app/src/main/res/values/styles.xml b/Source/Android/app/src/main/res/values/styles.xml index 98cf984807..dcfe4a77b7 100644 --- a/Source/Android/app/src/main/res/values/styles.xml +++ b/Source/Android/app/src/main/res/values/styles.xml @@ -7,6 +7,11 @@ @color/dolphin_blue @color/dolphin_blue_dark + + + true + true + true @@ -62,10 +67,17 @@ @color/dolphin_accent_wiiware - @@ -81,7 +93,6 @@ @color/dolphin_accent_wiiware -