Make ActionBar animations work correctly

Previous ActionBar animations didn't handle configuration changes
or other situations in which the view would get detached. listeners
would stay on the view and would attempt to do something nonsensical
in the new window. This new approach removes listeners on window
detach to avoid this problem.

Issue #20125407 Settings Crashes when changing orientation of device

Change-Id: I0b3bbd0f6fc23cdb4cbd646b0d2772d72d3d795d
This commit is contained in:
Chet Haase
2015-04-09 14:31:25 -07:00
parent de77be6311
commit 575217fc3d
4 changed files with 45 additions and 92 deletions

View File

@@ -40,7 +40,6 @@ import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ListPopupWindow.ForwardingListener;
import com.android.internal.transition.ActionBarTransition;
import com.android.internal.view.ActionBarPolicy;
import com.android.internal.view.menu.ActionMenuItemView;
import com.android.internal.view.menu.BaseMenuPresenter;
@@ -99,7 +98,30 @@ public class ActionMenuPresenter extends BaseMenuPresenter
// The list of currently running animations on menu items.
private List<ItemAnimationInfo> mRunningItemAnimations = new ArrayList<ItemAnimationInfo>();
private ViewTreeObserver.OnPreDrawListener mItemAnimationPreDrawListener =
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
computeMenuItemAnimationInfo(false);
((View) mMenuView).getViewTreeObserver().removeOnPreDrawListener(this);
runItemAnimations();
return true;
}
};
private View.OnAttachStateChangeListener mAttachStateChangeListener =
new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
}
@Override
public void onViewDetachedFromWindow(View v) {
((View) mMenuView).getViewTreeObserver().removeOnPreDrawListener(
mItemAnimationPreDrawListener);
mPreLayoutItems.clear();
mPostLayoutItems.clear();
}
};
public ActionMenuPresenter(Context context) {
@@ -177,8 +199,15 @@ public class ActionMenuPresenter extends BaseMenuPresenter
@Override
public MenuView getMenuView(ViewGroup root) {
MenuView oldMenuView = mMenuView;
MenuView result = super.getMenuView(root);
((ActionMenuView) result).setPresenter(this);
if (oldMenuView != result) {
((ActionMenuView) result).setPresenter(this);
if (oldMenuView != null) {
((View) oldMenuView).removeOnAttachStateChangeListener(mAttachStateChangeListener);
}
((View) result).addOnAttachStateChangeListener(mAttachStateChangeListener);
}
return result;
}
@@ -226,11 +255,11 @@ public class ActionMenuPresenter extends BaseMenuPresenter
* into the MenuItemLayoutInfo structure to store the appropriate position values.
*/
private void computeMenuItemAnimationInfo(boolean preLayout) {
final ViewGroup menuViewParent = (ViewGroup) mMenuView;
final int count = menuViewParent.getChildCount();
final ViewGroup menuView = (ViewGroup) mMenuView;
final int count = menuView.getChildCount();
SparseArray items = preLayout ? mPreLayoutItems : mPostLayoutItems;
for (int i = 0; i < count; ++i) {
View child = menuViewParent.getChildAt(i);
View child = menuView.getChildAt(i);
final int id = child.getId();
if (id > 0 && child.getWidth() != 0 && child.getHeight() != 0) {
MenuItemLayoutInfo info = new MenuItemLayoutInfo(child, preLayout);
@@ -377,28 +406,16 @@ public class ActionMenuPresenter extends BaseMenuPresenter
* which is then fed into runItemAnimations()
*/
private void setupItemAnimations() {
final ViewGroup menuViewParent = (ViewGroup) mMenuView;
computeMenuItemAnimationInfo(true);
final ViewTreeObserver observer = menuViewParent.getViewTreeObserver();
if (observer != null) {
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
computeMenuItemAnimationInfo(false);
observer.removeOnPreDrawListener(this);
runItemAnimations();
return true;
}
});
}
((View) mMenuView).getViewTreeObserver().
addOnPreDrawListener(mItemAnimationPreDrawListener);
}
@Override
public void updateMenuView(boolean cleared) {
final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
if (menuViewParent != null) {
// setupItemAnimations();
ActionBarTransition.beginDelayedTransition(menuViewParent);
setupItemAnimations();
}
super.updateMenuView(cleared);
@@ -736,8 +753,14 @@ public class ActionMenuPresenter extends BaseMenuPresenter
}
public void setMenuView(ActionMenuView menuView) {
mMenuView = menuView;
menuView.initialize(mMenu);
if (menuView != mMenuView) {
if (mMenuView != null) {
((View) mMenuView).removeOnAttachStateChangeListener(mAttachStateChangeListener);
}
mMenuView = menuView;
menuView.initialize(mMenu);
menuView.addOnAttachStateChangeListener(mAttachStateChangeListener);
}
}
public void setOverflowTintList(ColorStateList tint) {

View File

@@ -1,58 +0,0 @@
/*
* Copyright (C) 2013 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.android.internal.transition;
import android.transition.ChangeBounds;
import android.transition.Fade;
import android.transition.ChangeText;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.view.ViewGroup;
public class ActionBarTransition {
private static boolean TRANSITIONS_ENABLED = false;
private static final int TRANSITION_DURATION = 120; // ms
private static final Transition sTransition;
static {
if (TRANSITIONS_ENABLED) {
final ChangeText tc = new ChangeText();
tc.setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN);
final TransitionSet inner = new TransitionSet();
inner.addTransition(tc).addTransition(new ChangeBounds());
final TransitionSet tg = new TransitionSet();
tg.addTransition(new Fade(Fade.OUT)).addTransition(inner).
addTransition(new Fade(Fade.IN));
tg.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
tg.setDuration(TRANSITION_DURATION);
sTransition = tg;
} else {
sTransition = null;
}
}
public static void beginDelayedTransition(ViewGroup sceneRoot) {
if (TRANSITIONS_ENABLED) {
TransitionManager.beginDelayedTransition(sceneRoot, sTransition);
}
}
}

View File

@@ -49,7 +49,6 @@ import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import com.android.internal.R;
import com.android.internal.transition.ActionBarTransition;
import com.android.internal.view.menu.ActionMenuItem;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuItemImpl;
@@ -459,9 +458,6 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar {
public void setCustomView(View view) {
final boolean showCustom = (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0;
if (showCustom) {
ActionBarTransition.beginDelayedTransition(this);
}
if (mCustomNavView != null && showCustom) {
removeView(mCustomNavView);
}
@@ -499,7 +495,6 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar {
}
private void setTitleImpl(CharSequence title) {
ActionBarTransition.beginDelayedTransition(this);
mTitle = title;
if (mTitleView != null) {
mTitleView.setText(title);
@@ -519,7 +514,6 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar {
}
public void setSubtitle(CharSequence subtitle) {
ActionBarTransition.beginDelayedTransition(this);
mSubtitle = subtitle;
if (mSubtitleView != null) {
mSubtitleView.setText(subtitle);
@@ -604,7 +598,6 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar {
mDisplayOptions = options;
if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) {
ActionBarTransition.beginDelayedTransition(this);
if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
final boolean setUp = (options & ActionBar.DISPLAY_HOME_AS_UP) != 0;
@@ -706,7 +699,6 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar {
public void setNavigationMode(int mode) {
final int oldMode = mNavigationMode;
if (mode != oldMode) {
ActionBarTransition.beginDelayedTransition(this);
switch (oldMode) {
case ActionBar.NAVIGATION_MODE_LIST:
if (mListNavLayout != null) {
@@ -836,7 +828,6 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar {
}
}
ActionBarTransition.beginDelayedTransition(this);
mUpGoerFive.addView(mTitleLayout);
if (mExpandedActionView != null ||
(TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mSubtitle))) {
@@ -1659,7 +1650,6 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar {
@Override
public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
ActionBarTransition.beginDelayedTransition(ActionBarView.this);
mExpandedActionView = item.getActionView();
mExpandedHomeLayout.setIcon(mIcon.getConstantState().newDrawable(getResources()));
@@ -1688,7 +1678,6 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar {
@Override
public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
ActionBarTransition.beginDelayedTransition(ActionBarView.this);
// Do this before detaching the actionview from the hierarchy, in case
// it needs to dismiss the soft keyboard, etc.

View File

@@ -1791,7 +1791,6 @@ com.android.internal.telephony.ITelephonyRegistry
com.android.internal.telephony.ITelephonyRegistry$Stub
com.android.internal.telephony.ITelephonyRegistry$Stub$Proxy
com.android.internal.telephony.PhoneConstants$State
com.android.internal.transition.ActionBarTransition
com.android.internal.util.ArrayUtils
com.android.internal.util.FastPrintWriter
com.android.internal.util.FastPrintWriter$1