Use the fragment backstack properly, and use fragment animations.

Make the MenuFragment added and removed by fragment transactions only,
instead of being initially present in the XML. This fixes a glitch where
it doesn't animate correctly the first time it's used.
This commit is contained in:
Mike Harris 2017-10-07 23:52:13 -07:00
parent c4d7814afa
commit 94ed30b055
8 changed files with 106 additions and 141 deletions

View File

@ -14,6 +14,7 @@ import android.preference.PreferenceManager;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import android.view.InputDevice; import android.view.InputDevice;
@ -24,7 +25,6 @@ import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -42,7 +42,6 @@ import org.dolphinemu.dolphinemu.ui.platform.Platform;
import org.dolphinemu.dolphinemu.utils.Animations; import org.dolphinemu.dolphinemu.utils.Animations;
import org.dolphinemu.dolphinemu.utils.Java_GCAdapter; import org.dolphinemu.dolphinemu.utils.Java_GCAdapter;
import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter; import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter;
import org.dolphinemu.dolphinemu.utils.Log;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.util.List; import java.util.List;
@ -51,13 +50,12 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
public final class EmulationActivity extends AppCompatActivity public final class EmulationActivity extends AppCompatActivity
{ {
private static final String FRAGMENT_SUBMENU_TAG = "submenu"; private static final String BACKSTACK_NAME_MENU = "menu";
private static final String BACKSTACK_NAME_SUBMENU = "submenu";
private View mDecorView; private View mDecorView;
private ImageView mImageView; private ImageView mImageView;
private EmulationFragment mEmulationFragment; private EmulationFragment mEmulationFragment;
private LinearLayout mMenuLayout;
private SharedPreferences mPreferences; private SharedPreferences mPreferences;
// So that MainActivity knows which view to invalidate before the return animation. // So that MainActivity knows which view to invalidate before the return animation.
@ -66,7 +64,6 @@ public final class EmulationActivity extends AppCompatActivity
private boolean mDeviceHasTouchScreen; private boolean mDeviceHasTouchScreen;
private boolean mSystemUiVisible; private boolean mSystemUiVisible;
private boolean mMenuVisible; private boolean mMenuVisible;
private boolean mSubMenuVisible = false;
private static boolean mIsGameCubeGame; private static boolean mIsGameCubeGame;
@ -221,7 +218,6 @@ public final class EmulationActivity extends AppCompatActivity
setContentView(R.layout.activity_emulation); setContentView(R.layout.activity_emulation);
mImageView = (ImageView) findViewById(R.id.image_screenshot); mImageView = (ImageView) findViewById(R.id.image_screenshot);
mMenuLayout = (LinearLayout) findViewById(R.id.layout_ingame_menu);
mEmulationFragment = (EmulationFragment) getSupportFragmentManager() mEmulationFragment = (EmulationFragment) getSupportFragmentManager()
.findFragmentById(R.id.fragment_emulation); .findFragmentById(R.id.fragment_emulation);
@ -280,16 +276,6 @@ public final class EmulationActivity extends AppCompatActivity
{ {
setTitle(mSelectedTitle); setTitle(mSelectedTitle);
} }
else
{
MenuFragment menuFragment = (MenuFragment) getSupportFragmentManager()
.findFragmentById(R.id.fragment_menu);
if (menuFragment != null)
{
menuFragment.setTitleText(mSelectedTitle);
}
}
mPreferences = PreferenceManager.getDefaultSharedPreferences(this); mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
@ -333,11 +319,9 @@ public final class EmulationActivity extends AppCompatActivity
{ {
if (!mDeviceHasTouchScreen) if (!mDeviceHasTouchScreen)
{ {
if (mSubMenuVisible) boolean popResult = getSupportFragmentManager().popBackStackImmediate(
{ BACKSTACK_NAME_SUBMENU, FragmentManager.POP_BACK_STACK_INCLUSIVE);
removeSubMenu(); if (!popResult)
}
else
{ {
toggleMenu(); toggleMenu();
} }
@ -347,31 +331,28 @@ public final class EmulationActivity extends AppCompatActivity
mEmulationFragment.stopEmulation(); mEmulationFragment.stopEmulation();
exitWithAnimation(); exitWithAnimation();
} }
} }
private void toggleMenu() private void toggleMenu()
{ {
if (mMenuVisible) boolean result = getSupportFragmentManager().popBackStackImmediate(
{ BACKSTACK_NAME_MENU, FragmentManager.POP_BACK_STACK_INCLUSIVE);
mMenuVisible = false; mMenuVisible = false;
Animations.fadeViewOutToLeft(mMenuLayout) if (!result) {
.withEndAction(new Runnable() // Removing the menu failed, so that means it wasn't visible. Add it.
{ Fragment fragment = MenuFragment.newInstance(mSelectedTitle);
@Override getSupportFragmentManager().beginTransaction()
public void run() .setCustomAnimations(
{ R.animator.menu_slide_in_from_left,
if (mMenuVisible) R.animator.menu_slide_out_to_left,
{ R.animator.menu_slide_in_from_left,
mMenuLayout.setVisibility(View.GONE); R.animator.menu_slide_out_to_left)
} .add(R.id.frame_menu, fragment)
} .addToBackStack(BACKSTACK_NAME_MENU)
}); .commit();
}
else
{
mMenuVisible = true; mMenuVisible = true;
Animations.fadeViewInFromLeft(mMenuLayout);
} }
} }
@ -422,7 +403,7 @@ public final class EmulationActivity extends AppCompatActivity
finishAfterTransition(); finishAfterTransition();
} }
}; };
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) public boolean onCreateOptionsMenu(Menu menu)
{ {
@ -496,14 +477,14 @@ public final class EmulationActivity extends AppCompatActivity
case MENU_ACTION_SAVE_ROOT: case MENU_ACTION_SAVE_ROOT:
if (!mDeviceHasTouchScreen) if (!mDeviceHasTouchScreen)
{ {
showMenu(SaveLoadStateFragment.SaveOrLoad.SAVE); showSubMenu(SaveLoadStateFragment.SaveOrLoad.SAVE);
} }
return; return;
case MENU_ACTION_LOAD_ROOT: case MENU_ACTION_LOAD_ROOT:
if (!mDeviceHasTouchScreen) if (!mDeviceHasTouchScreen)
{ {
showMenu(SaveLoadStateFragment.SaveOrLoad.LOAD); showSubMenu(SaveLoadStateFragment.SaveOrLoad.LOAD);
} }
return; return;
@ -817,44 +798,22 @@ public final class EmulationActivity extends AppCompatActivity
hideSystemUiAfterDelay(); hideSystemUiAfterDelay();
} }
private void showMenu(SaveLoadStateFragment.SaveOrLoad saveOrLoad) private void showSubMenu(SaveLoadStateFragment.SaveOrLoad saveOrLoad)
{ {
// Get rid of any visible submenu
getSupportFragmentManager().popBackStack(
BACKSTACK_NAME_SUBMENU, FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fragment fragment = SaveLoadStateFragment.newInstance(saveOrLoad); Fragment fragment = SaveLoadStateFragment.newInstance(saveOrLoad);
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.animator.menu_slide_in, R.animator.menu_slide_out) .setCustomAnimations(
.replace(R.id.frame_submenu, fragment, FRAGMENT_SUBMENU_TAG) R.animator.menu_slide_in_from_right,
R.animator.menu_slide_out_to_right,
R.animator.menu_slide_in_from_right,
R.animator.menu_slide_out_to_right)
.replace(R.id.frame_submenu, fragment)
.addToBackStack(BACKSTACK_NAME_SUBMENU)
.commit(); .commit();
mSubMenuVisible = true;
}
private void removeSubMenu()
{
final Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_SUBMENU_TAG);
if (fragment != null)
{
// When removing a fragment without replacement, its animation must be done
// manually beforehand.
Animations.fadeViewOutToRight(fragment.getView())
.withEndAction(new Runnable()
{
@Override
public void run()
{
if (mMenuVisible)
{
getSupportFragmentManager().beginTransaction()
.remove(fragment)
.commit();
}
}
});
}
else
{
Log.error("[EmulationActivity] Fragment not found, can't remove.");
}
mSubMenuVisible = false;
} }
public String getSelectedTitle() public String getSelectedTitle()

View File

@ -16,7 +16,7 @@ import org.dolphinemu.dolphinemu.activities.EmulationActivity;
public final class MenuFragment extends Fragment implements View.OnClickListener public final class MenuFragment extends Fragment implements View.OnClickListener
{ {
private TextView mTitleText; private static final String KEY_TITLE = "title";
private static SparseIntArray buttonsActionsMap = new SparseIntArray(); private static SparseIntArray buttonsActionsMap = new SparseIntArray();
static { static {
buttonsActionsMap.append(R.id.menu_take_screenshot, EmulationActivity.MENU_ACTION_TAKE_SCREENSHOT); buttonsActionsMap.append(R.id.menu_take_screenshot, EmulationActivity.MENU_ACTION_TAKE_SCREENSHOT);
@ -28,6 +28,17 @@ public final class MenuFragment extends Fragment implements View.OnClickListener
buttonsActionsMap.append(R.id.menu_exit, EmulationActivity.MENU_ACTION_EXIT); buttonsActionsMap.append(R.id.menu_exit, EmulationActivity.MENU_ACTION_EXIT);
} }
public static MenuFragment newInstance(String title)
{
MenuFragment fragment = new MenuFragment();
Bundle arguments = new Bundle();
arguments.putSerializable(KEY_TITLE, title);
fragment.setArguments(arguments);
return fragment;
}
@Nullable @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
@ -42,7 +53,12 @@ public final class MenuFragment extends Fragment implements View.OnClickListener
button.setOnClickListener(this); button.setOnClickListener(this);
} }
mTitleText = (TextView) rootView.findViewById(R.id.text_game_title); TextView titleText = rootView.findViewById(R.id.text_game_title);
String title = getArguments().getString(KEY_TITLE);
if (title != null)
{
titleText.setText(title);
}
return rootView; return rootView;
} }
@ -57,9 +73,4 @@ public final class MenuFragment extends Fragment implements View.OnClickListener
((EmulationActivity) getActivity()).handleMenuAction(action); ((EmulationActivity) getActivity()).handleMenuAction(action);
} }
} }
public void setTitleText(String title)
{
mTitleText.setText(title);
}
} }

View File

@ -2,55 +2,13 @@ package org.dolphinemu.dolphinemu.utils;
import android.view.View; import android.view.View;
import android.view.ViewPropertyAnimator; import android.view.ViewPropertyAnimator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
public final class Animations public final class Animations
{ {
private static final Interpolator DECELERATOR = new DecelerateInterpolator();
private static final Interpolator ACCELERATOR = new AccelerateInterpolator();
private Animations() private Animations()
{ {
} }
public static ViewPropertyAnimator fadeViewOutToRight(View view)
{
return view.animate()
.withLayer()
.setDuration(200)
.setInterpolator(ACCELERATOR)
.alpha(0.0f)
.translationX(view.getWidth());
}
public static ViewPropertyAnimator fadeViewOutToLeft(View view)
{
return view.animate()
.withLayer()
.setDuration(200)
.setInterpolator(ACCELERATOR)
.alpha(0.0f)
.translationX(-view.getWidth());
}
public static ViewPropertyAnimator fadeViewInFromLeft(View view)
{
view.setVisibility(View.VISIBLE);
view.setTranslationX(-view.getWidth());
view.setAlpha(0.0f);
return view.animate()
.withLayer()
.setDuration(300)
.setInterpolator(DECELERATOR)
.alpha(1.0f)
.translationX(0.0f);
}
public static ViewPropertyAnimator fadeViewIn(View view) public static ViewPropertyAnimator fadeViewIn(View view)
{ {
view.setVisibility(View.VISIBLE); view.setVisibility(View.VISIBLE);

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:propertyName="translationX"
android:valueType="floatType"
android:valueFrom="-1280dp"
android:valueTo="0"
android:interpolator="@android:interpolator/decelerate_quad"
android:duration="300"/>
<objectAnimator
android:propertyName="alpha"
android:valueType="floatType"
android:valueFrom="0"
android:valueTo="1"
android:interpolator="@android:interpolator/accelerate_quad"
android:duration="300"/>
</set>

View File

@ -4,7 +4,7 @@
<objectAnimator <objectAnimator
android:propertyName="translationX" android:propertyName="translationX"
android:valueType="floatType" android:valueType="floatType"
android:valueFrom="1280" android:valueFrom="1280dp"
android:valueTo="0" android:valueTo="0"
android:interpolator="@android:interpolator/decelerate_quad" android:interpolator="@android:interpolator/decelerate_quad"
android:duration="300"/> android:duration="300"/>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- This animation is used ONLY when a submenu is replaced. -->
<objectAnimator
android:propertyName="translationX"
android:valueType="floatType"
android:valueFrom="0"
android:valueTo="-1280dp"
android:interpolator="@android:interpolator/decelerate_quad"
android:duration="200"/>
<objectAnimator
android:propertyName="alpha"
android:valueType="floatType"
android:valueFrom="1"
android:valueTo="0"
android:interpolator="@android:interpolator/decelerate_quad"
android:duration="200"/>
</set>

View File

@ -6,9 +6,9 @@
android:propertyName="translationX" android:propertyName="translationX"
android:valueType="floatType" android:valueType="floatType"
android:valueFrom="0" android:valueFrom="0"
android:valueTo="1280" android:valueTo="1280dp"
android:interpolator="@android:interpolator/decelerate_quad" android:interpolator="@android:interpolator/decelerate_quad"
android:duration="300"/> android:duration="200"/>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="alpha" android:propertyName="alpha"
@ -16,6 +16,6 @@
android:valueFrom="1" android:valueFrom="1"
android:valueTo="0" android:valueTo="0"
android:interpolator="@android:interpolator/decelerate_quad" android:interpolator="@android:interpolator/decelerate_quad"
android:duration="300"/> android:duration="200"/>
</set> </set>

View File

@ -21,28 +21,24 @@
android:id="@+id/image_screenshot" android:id="@+id/image_screenshot"
android:transitionName="image_game_screenshot"/> android:transitionName="image_game_screenshot"/>
<LinearLayout <LinearLayout
android:id="@+id/layout_ingame_menu"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="horizontal" android:orientation="horizontal">
android:visibility="gone"
tools:visibility="visible"
android:baselineAligned="false">
<fragment <FrameLayout
android:id="@+id/fragment_menu" android:id="@+id/frame_menu"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight=".25"
android:name="org.dolphinemu.dolphinemu.fragments.MenuFragment"
tools:layout="@layout/fragment_ingame_menu"/> tools:layout="@layout/fragment_ingame_menu"/>
<FrameLayout <FrameLayout
android:id="@+id/frame_submenu" android:id="@+id/frame_submenu"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="3"/> android:layout_weight=".75"/>
</LinearLayout> </LinearLayout>