Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f8ac6b73 authored by Adam Powell's avatar Adam Powell
Browse files

Action bar tab layout

Sync with UX on action bar tab layout behavior.

Make action bar tabs behave better with configuration changes.

LinearLayout now supports largest child measurement in unspecified
mode.

Change-Id: Id520641715a61c07e64124a0c5a70912996c98a0
parent feeba076
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -1398,6 +1398,10 @@ public class Activity extends ContextThemeWrapper
    public void onConfigurationChanged(Configuration newConfig) {
        mCalled = true;

        if (mActionBar != null) {
            mActionBar.onConfigurationChanged(newConfig);
        }

        mFragments.dispatchConfigurationChanged(newConfig);

        if (mWindow != null) {
+52 −2
Original line number Diff line number Diff line
@@ -696,7 +696,8 @@ public class LinearLayout extends ViewGroup {
            mTotalLength += mDividerHeight;
        }

        if (useLargestChild && heightMode == MeasureSpec.AT_MOST) {
        if (useLargestChild &&
                (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
            mTotalLength = 0;

            for (int i = 0; i < count; ++i) {
@@ -809,6 +810,31 @@ public class LinearLayout extends ViewGroup {
        } else {
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                           weightedMaxWidth);


            // We have no limit, so make all weighted views as tall as the largest child.
            // Children will have already been measured once.
            if (useLargestChild && widthMode == MeasureSpec.UNSPECIFIED) {
                for (int i = 0; i < count; i++) {
                    final View child = getVirtualChildAt(i);

                    if (child == null || child.getVisibility() == View.GONE) {
                        continue;
                    }

                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();

                    float childExtra = lp.weight;
                    if (childExtra > 0) {
                        child.measure(
                                MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
                                        MeasureSpec.EXACTLY),
                                MeasureSpec.makeMeasureSpec(largestChildHeight,
                                        MeasureSpec.EXACTLY));
                    }
                }
            }
        }

        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
@@ -1044,7 +1070,8 @@ public class LinearLayout extends ViewGroup {
            maxHeight = Math.max(maxHeight, ascent + descent);
        }

        if (useLargestChild && widthMode == MeasureSpec.AT_MOST) {
        if (useLargestChild &&
                (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) {
            mTotalLength = 0;

            for (int i = 0; i < count; ++i) {
@@ -1200,6 +1227,29 @@ public class LinearLayout extends ViewGroup {
            }
        } else {
            alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight);

            // We have no limit, so make all weighted views as wide as the largest child.
            // Children will have already been measured once.
            if (useLargestChild && widthMode == MeasureSpec.UNSPECIFIED) {
                for (int i = 0; i < count; i++) {
                    final View child = getVirtualChildAt(i);

                    if (child == null || child.getVisibility() == View.GONE) {
                        continue;
                    }

                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();

                    float childExtra = lp.weight;
                    if (childExtra > 0) {
                        child.measure(
                                MeasureSpec.makeMeasureSpec(largestChildWidth, MeasureSpec.EXACTLY),
                                MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),
                                        MeasureSpec.EXACTLY));
                    }
                }
            }
        }

        if (!allFillParent && heightMode != MeasureSpec.EXACTLY) {
+78 −25
Original line number Diff line number Diff line
@@ -16,25 +16,27 @@

package com.android.internal.app;

import com.android.internal.R;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuPopupHelper;
import com.android.internal.view.menu.SubMenuBuilder;
import com.android.internal.widget.AbsActionBarView;
import com.android.internal.widget.ActionBarContainer;
import com.android.internal.widget.ActionBarContextView;
import com.android.internal.widget.ActionBarView;
import com.android.internal.widget.ScrollingTabContainerView;

import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.app.ActionBar;
import android.app.Activity;
import android.app.Dialog;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.ActionMode;
@@ -43,10 +45,7 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.DecelerateInterpolator;
import android.widget.HorizontalScrollView;
import android.widget.SpinnerAdapter;

import java.lang.ref.WeakReference;
@@ -71,7 +70,7 @@ public class ActionBarImpl extends ActionBar {
    private ActionBarContextView mContextView;
    private ActionBarContainer mSplitView;
    private View mContentView;
    private ViewGroup mExternalTabView;
    private ScrollingTabContainerView mTabScrollView;

    private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();

@@ -90,16 +89,17 @@ public class ActionBarImpl extends ActionBar {
    private static final int INVALID_POSITION = -1;

    private int mContextDisplayMode;
    private boolean mHasEmbeddedTabs;
    private int mContentHeight;

    final Handler mHandler = new Handler();
    Runnable mTabSelector;

    private Animator mCurrentShowAnim;
    private Animator mCurrentModeAnim;
    private boolean mShowHideAnimationEnabled;
    boolean mWasHiddenBeforeMode;

    private static final TimeInterpolator sFadeOutInterpolator = new DecelerateInterpolator();

    final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
@@ -150,21 +150,59 @@ public class ActionBarImpl extends ActionBar {
                    "with a compatible window decor layout");
        }

        mHasEmbeddedTabs = mContext.getResources().getBoolean(
                com.android.internal.R.bool.action_bar_embed_tabs);
        mActionView.setContextView(mContextView);
        mContextDisplayMode = mActionView.isSplitActionBar() ?
                CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL;

        if (!mActionView.hasEmbeddedTabs()) {
            HorizontalScrollView tabScroller = new HorizontalScrollView(mContext);
            ViewGroup tabContainer = mActionView.createTabContainer();
            tabScroller.setHorizontalFadingEdgeEnabled(true);
            tabScroller.addView(tabContainer);
        TypedArray a = mContext.obtainStyledAttributes(null, R.styleable.ActionBar);
        mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
        a.recycle();
    }

    public void onConfigurationChanged(Configuration newConfig) {
        mHasEmbeddedTabs = mContext.getResources().getBoolean(
                com.android.internal.R.bool.action_bar_embed_tabs);

        // Switch tab layout configuration if needed
        if (!mHasEmbeddedTabs) {
            mActionView.setEmbeddedTabView(null);
            mContainerView.setTabContainer(mTabScrollView);
        } else {
            mContainerView.setTabContainer(null);
            if (mTabScrollView != null) {
                mTabScrollView.setVisibility(View.VISIBLE);
            }
            mActionView.setEmbeddedTabView(mTabScrollView);
        }

        TypedArray a = mContext.obtainStyledAttributes(null, R.styleable.ActionBar);
        mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
        a.recycle();

        if (mTabScrollView != null) {
            mTabScrollView.getLayoutParams().height = mContentHeight;
            mTabScrollView.requestLayout();
        }
    }

    private void ensureTabsExist() {
        if (mTabScrollView != null) {
            return;
        }

        ScrollingTabContainerView tabScroller = mActionView.createTabContainer();

        if (mHasEmbeddedTabs) {
            tabScroller.setVisibility(View.VISIBLE);
            mActionView.setEmbeddedTabView(tabScroller);
        } else {
            tabScroller.setVisibility(getNavigationMode() == NAVIGATION_MODE_TABS ?
                    View.VISIBLE : View.GONE);
            mActionView.setExternalTabLayout(tabContainer);
            mContainerView.setTabContainer(tabScroller);
            mExternalTabView = tabScroller;
        }
        mTabScrollView = tabScroller;
    }

    /**
@@ -269,7 +307,7 @@ public class ActionBarImpl extends ActionBar {
            selectTab(null);
        }
        mTabs.clear();
        mActionView.removeAllTabs();
        mTabScrollView.removeAllTabs();
        mSavedTabPosition = INVALID_POSITION;
    }

@@ -365,7 +403,8 @@ public class ActionBarImpl extends ActionBar {

    @Override
    public void addTab(Tab tab, boolean setSelected) {
        mActionView.addTab(tab, setSelected);
        ensureTabsExist();
        mTabScrollView.addTab(tab, setSelected);
        configureTab(tab, mTabs.size());
        if (setSelected) {
            selectTab(tab);
@@ -374,7 +413,8 @@ public class ActionBarImpl extends ActionBar {

    @Override
    public void addTab(Tab tab, int position, boolean setSelected) {
        mActionView.addTab(tab, position, setSelected);
        ensureTabsExist();
        mTabScrollView.addTab(tab, position, setSelected);
        configureTab(tab, position);
        if (setSelected) {
            selectTab(tab);
@@ -393,9 +433,14 @@ public class ActionBarImpl extends ActionBar {

    @Override
    public void removeTabAt(int position) {
        if (mTabScrollView == null) {
            // No tabs around to remove
            return;
        }

        int selectedTabPosition = mSelectedTab != null
                ? mSelectedTab.getPosition() : mSavedTabPosition;
        mActionView.removeTabAt(position);
        mTabScrollView.removeTabAt(position);
        TabImpl removedTab = mTabs.remove(position);
        if (removedTab != null) {
            removedTab.setPosition(-1);
@@ -424,9 +469,10 @@ public class ActionBarImpl extends ActionBar {
        if (mSelectedTab == tab) {
            if (mSelectedTab != null) {
                mSelectedTab.getCallback().onTabReselected(mSelectedTab, trans);
                mTabScrollView.animateToTab(tab.getPosition());
            }
        } else {
            mActionView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
            mTabScrollView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
            if (mSelectedTab != null) {
                mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans);
            }
@@ -705,7 +751,9 @@ public class ActionBarImpl extends ActionBar {
        @Override
        public Tab setCustomView(View view) {
            mCustomView = view;
            if (mPosition >= 0) mActionView.updateTab(mPosition);
            if (mPosition >= 0) {
                mTabScrollView.updateTab(mPosition);
            }
            return this;
        }

@@ -736,7 +784,9 @@ public class ActionBarImpl extends ActionBar {
        @Override
        public Tab setIcon(Drawable icon) {
            mIcon = icon;
            if (mPosition >= 0) mActionView.updateTab(mPosition);
            if (mPosition >= 0) {
                mTabScrollView.updateTab(mPosition);
            }
            return this;
        }

@@ -748,7 +798,9 @@ public class ActionBarImpl extends ActionBar {
        @Override
        public Tab setText(CharSequence text) {
            mText = text;
            if (mPosition >= 0) mActionView.updateTab(mPosition);
            if (mPosition >= 0) {
                mTabScrollView.updateTab(mPosition);
            }
            return this;
        }

@@ -818,15 +870,16 @@ public class ActionBarImpl extends ActionBar {
                mSavedTabPosition = getSelectedNavigationIndex();
                selectTab(null);
                if (!mActionView.hasEmbeddedTabs()) {
                    mExternalTabView.setVisibility(View.GONE);
                    mTabScrollView.setVisibility(View.GONE);
                }
                break;
        }
        mActionView.setNavigationMode(mode);
        switch (mode) {
            case NAVIGATION_MODE_TABS:
                ensureTabsExist();
                if (!mActionView.hasEmbeddedTabs()) {
                    mExternalTabView.setVisibility(View.VISIBLE);
                    mTabScrollView.setVisibility(View.VISIBLE);
                }
                if (mSavedTabPosition != INVALID_POSITION) {
                    setSelectedNavigationItem(mSavedTabPosition);
+29 −171
Original line number Diff line number Diff line
@@ -78,7 +78,7 @@ public class ActionBarView extends AbsActionBarView {

    private static final int DEFAULT_CUSTOM_GRAVITY = Gravity.LEFT | Gravity.CENTER_VERTICAL;
    
    private final int mContentHeight;
    private int mContentHeight;

    private int mNavigationMode;
    private int mDisplayOptions = ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_HOME_AS_UP;
@@ -95,8 +95,7 @@ public class ActionBarView extends AbsActionBarView {
    private TextView mSubtitleView;
    private Spinner mSpinner;
    private LinearLayout mListNavLayout;
    private HorizontalScrollView mTabScrollView;
    private ViewGroup mTabLayout;
    private ScrollingTabContainerView mTabScrollView;
    private View mCustomNavView;
    private ProgressBar mProgressView;
    private ProgressBar mIndeterminateProgressView;
@@ -122,6 +121,8 @@ public class ActionBarView extends AbsActionBarView {
    private SpinnerAdapter mSpinnerAdapter;
    private OnNavigationListener mCallback;

    private Runnable mTabSelector;

    private final AdapterView.OnItemSelectedListener mNavItemSelectedListener =
            new AdapterView.OnItemSelectedListener() {
        public void onItemSelected(AdapterView parent, View view, int position, long id) {
@@ -199,8 +200,6 @@ public class ActionBarView extends AbsActionBarView {
        mProgressBarPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_progressBarPadding, 0);
        mItemPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_itemPadding, 0);

        mIncludeTabs = a.getBoolean(R.styleable.ActionBar_embeddedTabs, true);

        setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT));

        final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0);
@@ -228,6 +227,12 @@ public class ActionBarView extends AbsActionBarView {
        mHomeLayout.setFocusable(true);
    }

    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        removeCallbacks(mTabSelector);
    }

    @Override
    public boolean shouldDelayChildPressedState() {
        return false;
@@ -247,6 +252,11 @@ public class ActionBarView extends AbsActionBarView {
        addView(mIndeterminateProgressView);
    }

    public void setContentHeight(int height) {
        mContentHeight = height;
        requestLayout();
    }

    public void setSplitActionBar(boolean splitActionBar) {
        if (mSplitActionBar != splitActionBar) {
            if (mMenuView != null) {
@@ -271,8 +281,9 @@ public class ActionBarView extends AbsActionBarView {
        return mIncludeTabs;
    }

    public void setExternalTabLayout(ViewGroup tabLayout) {
        mTabLayout = tabLayout;
    public void setEmbeddedTabView(ScrollingTabContainerView tabs) {
        mTabScrollView = tabs;
        mIncludeTabs = tabs != null;
    }

    public void setCallback(OnNavigationListener callback) {
@@ -489,7 +500,7 @@ public class ActionBarView extends AbsActionBarView {
                }
                break;
            case ActionBar.NAVIGATION_MODE_TABS:
                if (mTabScrollView != null) {
                if (mTabScrollView != null && mIncludeTabs) {
                    removeView(mTabScrollView);
                }
            }
@@ -513,8 +524,7 @@ public class ActionBarView extends AbsActionBarView {
                addView(mListNavLayout);
                break;
            case ActionBar.NAVIGATION_MODE_TABS:
                ensureTabsExist();
                if (mTabScrollView != null) {
                if (mTabScrollView != null && mIncludeTabs) {
                    addView(mTabScrollView);
                }
                break;
@@ -524,23 +534,16 @@ public class ActionBarView extends AbsActionBarView {
        }
    }

    private void ensureTabsExist() {
        if (!mIncludeTabs) return;

        if (mTabScrollView == null) {
            mTabScrollView = new HorizontalScrollView(getContext());
            mTabScrollView.setHorizontalFadingEdgeEnabled(true);
            mTabLayout = createTabContainer();
            mTabScrollView.addView(mTabLayout);
        }
    }

    public ViewGroup createTabContainer() {
        ViewGroup result = new LinearLayout(getContext(), null,
    public ScrollingTabContainerView createTabContainer() {
        final LinearLayout tabLayout = new LinearLayout(getContext(), null,
                com.android.internal.R.attr.actionBarTabBarStyle);
        result.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
                mContentHeight));
        return result;
        tabLayout.setMeasureWithLargestChildEnabled(true);
        tabLayout.setLayoutParams(new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT, mContentHeight));

        final ScrollingTabContainerView scroller = new ScrollingTabContainerView(mContext);
        scroller.setTabLayout(tabLayout);
        return scroller;
    }

    public void setDropdownAdapter(SpinnerAdapter adapter) {
@@ -574,51 +577,6 @@ public class ActionBarView extends AbsActionBarView {
        return mDisplayOptions;
    }

    private TabView createTabView(ActionBar.Tab tab) {
        final TabView tabView = new TabView(getContext(), tab);
        tabView.setFocusable(true);

        if (mTabClickListener == null) {
            mTabClickListener = new TabClickListener();
        }
        tabView.setOnClickListener(mTabClickListener);
        return tabView;
    }

    public void addTab(ActionBar.Tab tab, boolean setSelected) {
        ensureTabsExist();
        View tabView = createTabView(tab);
        mTabLayout.addView(tabView);
        if (setSelected) {
            tabView.setSelected(true);
        }
    }

    public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
        ensureTabsExist();
        final TabView tabView = createTabView(tab);
        mTabLayout.addView(tabView, position);
        if (setSelected) {
            tabView.setSelected(true);
        }
    }

    public void updateTab(int position) {
        ((TabView) mTabLayout.getChildAt(position)).update();
    }

    public void removeTabAt(int position) {
        if (mTabLayout != null) {
            mTabLayout.removeViewAt(position);
        }
    }

    public void removeAllTabs() {
        if (mTabLayout != null) {
            mTabLayout.removeAllViews();
        }
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        // Used by custom nav views if they don't supply layout params. Everything else
@@ -667,15 +625,6 @@ public class ActionBarView extends AbsActionBarView {
        addView(mTitleLayout);
    }

    public void setTabSelected(int position) {
        ensureTabsExist();
        final int tabCount = mTabLayout.getChildCount();
        for (int i = 0; i < tabCount; i++) {
            final View child = mTabLayout.getChildAt(i);
            child.setSelected(i == position);
        }
    }

    public void setContextView(ActionBarContextView view) {
        mContextView = view;
    }
@@ -948,97 +897,6 @@ public class ActionBarView extends AbsActionBarView {
        }
    }

    private static class TabView extends LinearLayout {
        private ActionBar.Tab mTab;
        private TextView mTextView;
        private ImageView mIconView;
        private View mCustomView;

        public TabView(Context context, ActionBar.Tab tab) {
            super(context, null, com.android.internal.R.attr.actionBarTabStyle);
            mTab = tab;

            update();

            setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
                    LayoutParams.MATCH_PARENT, 1));
        }

        public void update() {
            final ActionBar.Tab tab = mTab;
            final View custom = tab.getCustomView();
            if (custom != null) {
                addView(custom);
                mCustomView = custom;
                if (mTextView != null) mTextView.setVisibility(GONE);
                if (mIconView != null) {
                    mIconView.setVisibility(GONE);
                    mIconView.setImageDrawable(null);
                }
            } else {
                if (mCustomView != null) {
                    removeView(mCustomView);
                    mCustomView = null;
                }

                final Drawable icon = tab.getIcon();
                final CharSequence text = tab.getText();

                if (icon != null) {
                    if (mIconView == null) {
                        ImageView iconView = new ImageView(getContext());
                        LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
                                LayoutParams.WRAP_CONTENT);
                        lp.gravity = Gravity.CENTER_VERTICAL;
                        iconView.setLayoutParams(lp);
                        addView(iconView, 0);
                        mIconView = iconView;
                    }
                    mIconView.setImageDrawable(icon);
                    mIconView.setVisibility(VISIBLE);
                } else if (mIconView != null) {
                    mIconView.setVisibility(GONE);
                    mIconView.setImageDrawable(null);
                }

                if (text != null) {
                    if (mTextView == null) {
                        TextView textView = new TextView(getContext(), null,
                                com.android.internal.R.attr.actionBarTabTextStyle);
                        textView.setSingleLine();
                        textView.setEllipsize(TruncateAt.END);
                        LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
                                LayoutParams.WRAP_CONTENT);
                        lp.gravity = Gravity.CENTER_VERTICAL;
                        textView.setLayoutParams(lp);
                        addView(textView);
                        mTextView = textView;
                    }
                    mTextView.setText(text);
                    mTextView.setVisibility(VISIBLE);
                } else {
                    mTextView.setVisibility(GONE);
                }
            }
        }

        public ActionBar.Tab getTab() {
            return mTab;
        }
    }

    private class TabClickListener implements OnClickListener {
        public void onClick(View view) {
            TabView tabView = (TabView) view;
            tabView.getTab().select();
            final int tabCount = mTabLayout.getChildCount();
            for (int i = 0; i < tabCount; i++) {
                final View child = mTabLayout.getChildAt(i);
                child.setSelected(child == view);
            }
        }
    }

    private static class HomeView extends FrameLayout {
        private View mUpView;
        private View mIconView;
+261 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading