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.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.util.SparseIntArray;
import android.view.InputDevice;
@ -24,7 +25,6 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
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.Java_GCAdapter;
import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter;
import org.dolphinemu.dolphinemu.utils.Log;
import java.lang.annotation.Retention;
import java.util.List;
@ -51,13 +50,12 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
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 ImageView mImageView;
private EmulationFragment mEmulationFragment;
private LinearLayout mMenuLayout;
private SharedPreferences mPreferences;
// 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 mSystemUiVisible;
private boolean mMenuVisible;
private boolean mSubMenuVisible = false;
private static boolean mIsGameCubeGame;
@ -221,7 +218,6 @@ public final class EmulationActivity extends AppCompatActivity
setContentView(R.layout.activity_emulation);
mImageView = (ImageView) findViewById(R.id.image_screenshot);
mMenuLayout = (LinearLayout) findViewById(R.id.layout_ingame_menu);
mEmulationFragment = (EmulationFragment) getSupportFragmentManager()
.findFragmentById(R.id.fragment_emulation);
@ -280,16 +276,6 @@ public final class EmulationActivity extends AppCompatActivity
{
setTitle(mSelectedTitle);
}
else
{
MenuFragment menuFragment = (MenuFragment) getSupportFragmentManager()
.findFragmentById(R.id.fragment_menu);
if (menuFragment != null)
{
menuFragment.setTitleText(mSelectedTitle);
}
}
mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
@ -333,11 +319,9 @@ public final class EmulationActivity extends AppCompatActivity
{
if (!mDeviceHasTouchScreen)
{
if (mSubMenuVisible)
{
removeSubMenu();
}
else
boolean popResult = getSupportFragmentManager().popBackStackImmediate(
BACKSTACK_NAME_SUBMENU, FragmentManager.POP_BACK_STACK_INCLUSIVE);
if (!popResult)
{
toggleMenu();
}
@ -347,31 +331,28 @@ public final class EmulationActivity extends AppCompatActivity
mEmulationFragment.stopEmulation();
exitWithAnimation();
}
}
private void toggleMenu()
{
if (mMenuVisible)
{
mMenuVisible = false;
boolean result = getSupportFragmentManager().popBackStackImmediate(
BACKSTACK_NAME_MENU, FragmentManager.POP_BACK_STACK_INCLUSIVE);
mMenuVisible = false;
Animations.fadeViewOutToLeft(mMenuLayout)
.withEndAction(new Runnable()
{
@Override
public void run()
{
if (mMenuVisible)
{
mMenuLayout.setVisibility(View.GONE);
}
}
});
}
else
{
if (!result) {
// Removing the menu failed, so that means it wasn't visible. Add it.
Fragment fragment = MenuFragment.newInstance(mSelectedTitle);
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(
R.animator.menu_slide_in_from_left,
R.animator.menu_slide_out_to_left,
R.animator.menu_slide_in_from_left,
R.animator.menu_slide_out_to_left)
.add(R.id.frame_menu, fragment)
.addToBackStack(BACKSTACK_NAME_MENU)
.commit();
mMenuVisible = true;
Animations.fadeViewInFromLeft(mMenuLayout);
}
}
@ -422,7 +403,7 @@ public final class EmulationActivity extends AppCompatActivity
finishAfterTransition();
}
};
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
@ -496,14 +477,14 @@ public final class EmulationActivity extends AppCompatActivity
case MENU_ACTION_SAVE_ROOT:
if (!mDeviceHasTouchScreen)
{
showMenu(SaveLoadStateFragment.SaveOrLoad.SAVE);
showSubMenu(SaveLoadStateFragment.SaveOrLoad.SAVE);
}
return;
case MENU_ACTION_LOAD_ROOT:
if (!mDeviceHasTouchScreen)
{
showMenu(SaveLoadStateFragment.SaveOrLoad.LOAD);
showSubMenu(SaveLoadStateFragment.SaveOrLoad.LOAD);
}
return;
@ -817,44 +798,22 @@ public final class EmulationActivity extends AppCompatActivity
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);
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.animator.menu_slide_in, R.animator.menu_slide_out)
.replace(R.id.frame_submenu, fragment, FRAGMENT_SUBMENU_TAG)
.setCustomAnimations(
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();
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()

View File

@ -16,7 +16,7 @@ import org.dolphinemu.dolphinemu.activities.EmulationActivity;
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();
static {
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);
}
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
@Override
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);
}
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;
}
@ -57,9 +73,4 @@ public final class MenuFragment extends Fragment implements View.OnClickListener
((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.ViewPropertyAnimator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
public final class Animations
{
private static final Interpolator DECELERATOR = new DecelerateInterpolator();
private static final Interpolator ACCELERATOR = new AccelerateInterpolator();
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)
{
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
android:propertyName="translationX"
android:valueType="floatType"
android:valueFrom="1280"
android:valueFrom="1280dp"
android:valueTo="0"
android:interpolator="@android:interpolator/decelerate_quad"
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:valueType="floatType"
android:valueFrom="0"
android:valueTo="1280"
android:valueTo="1280dp"
android:interpolator="@android:interpolator/decelerate_quad"
android:duration="300"/>
android:duration="200"/>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="alpha"
@ -16,6 +16,6 @@
android:valueFrom="1"
android:valueTo="0"
android:interpolator="@android:interpolator/decelerate_quad"
android:duration="300"/>
android:duration="200"/>
</set>

View File

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