Bug 976335 - Handle about:home tab strip overflow on tablets. r=lucasr a=lsblakk
authorMargaret Leibovic <margaret.leibovic@gmail.com>
Wed, 23 Apr 2014 14:37:31 -0700
changeset 193165 f44e3f7a4932c794c06d57aba6fff201bbe87d65
parent 193164 49833bf2290d2d4cffc27e8aa530cfeba8e48751
child 193166 283b42b59b7f408ab532f6a0884fbcf7f6554574
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslucasr, lsblakk
bugs976335
milestone30.0a2
Bug 976335 - Handle about:home tab strip overflow on tablets. r=lucasr a=lsblakk * * * Bug 976335 - (Part 1) Turn TabMenuStrip into a HorizontalScrollView, moving LinearLayout logic to TabMenuStripLayout. r=lucasr * * * Bug 976335 - (Part 1.5) Remove m prefixes from TabMenuStripLayout. r=lucasr * * * Bug 976335 - (Part 2) Scroll tab strip to ensure selected tab is visible. r=lucasr
mobile/android/base/home/TabMenuStrip.java
mobile/android/base/home/TabMenuStripLayout.java
mobile/android/base/moz.build
--- a/mobile/android/base/home/TabMenuStrip.java
+++ b/mobile/android/base/home/TabMenuStrip.java
@@ -11,191 +11,90 @@ import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityEvent;
+import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-public class TabMenuStrip extends LinearLayout
-                          implements HomePager.Decor,
-                                     View.OnFocusChangeListener {
-    private static final String LOGTAG = "GeckoTabMenuStrip";
+/**
+ * {@code TabMenuStrip} is the view used to display {@code HomePager} tabs
+ * on tablets. See {@code TabMenuStripLayout} for details about how the
+ * tabs are created and updated.
+ */
+public class TabMenuStrip extends HorizontalScrollView
+                          implements HomePager.Decor {
 
-    private HomePager.OnTitleClickListener mOnTitleClickListener;
-    private Drawable mStrip;
-    private View mSelectedView;
+    // Offset between the selected tab title and the edge of the screen,
+    // except for the first and last tab in the tab strip.
+    private static final int TITLE_OFFSET_DIPS = 24;
 
-    // Data associated with the scrolling of the strip drawable.
-    private View toTab;
-    private View fromTab;
-    private float progress;
-
-    // This variable is used to predict the direction of scroll.
-    private float mPrevProgress;
+    private final int titleOffset;
+    private final TabMenuStripLayout layout;
 
     public TabMenuStrip(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabMenuStrip);
-        final int stripResId = a.getResourceId(R.styleable.TabMenuStrip_strip, -1);
-        a.recycle();
+        // Disable the scroll bar.
+        setHorizontalScrollBarEnabled(false);
 
-        if (stripResId != -1) {
-            mStrip = getResources().getDrawable(stripResId);
-        }
+        titleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
 
-        setWillNotDraw(false);
+        layout = new TabMenuStripLayout(context, attrs);
+        addView(layout, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
     }
 
     @Override
     public void onAddPagerView(String title) {
-        final TextView button = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.tab_menu_strip, this, false);
-        button.setText(title.toUpperCase());
-
-        addView(button);
-        button.setOnClickListener(new ViewClickListener(getChildCount() - 1));
-        button.setOnFocusChangeListener(this);
+        layout.onAddPagerView(title);
     }
 
     @Override
     public void removeAllPagerViews() {
-        removeAllViews();
+        layout.removeAllViews();
     }
 
     @Override
     public void onPageSelected(final int position) {
-        mSelectedView = getChildAt(position);
-
-        // Callback to measure and draw the strip after the view is visible.
-        ViewTreeObserver vto = mSelectedView.getViewTreeObserver();
-        if (vto.isAlive()) {
-            vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
-                @Override
-                public void onGlobalLayout() {
-                    mSelectedView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
-
-                    if (mStrip != null) {
-                        mStrip.setBounds(mSelectedView.getLeft(),
-                                         mSelectedView.getTop(),
-                                         mSelectedView.getRight(),
-                                         mSelectedView.getBottom());
-                    }
-
-                    mPrevProgress = position;
-                }
-            });
-        }
+        layout.onPageSelected(position);
     }
 
-    // Page scroll animates the drawable and its bounds from the previous to next child view.
     @Override
     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-        if (mStrip == null) {
-            return;
-        }
+        layout.onPageScrolled(position, positionOffset, positionOffsetPixels);
 
-        setScrollingData(position, positionOffset);
-
-        if (fromTab == null || toTab == null) {
+        final View selectedTitle = layout.getChildAt(position);
+        if (selectedTitle == null) {
             return;
         }
 
-        final int fromTabLeft =  fromTab.getLeft();
-        final int fromTabRight = fromTab.getRight();
-
-        final int toTabLeft =  toTab.getLeft();
-        final int toTabRight = toTab.getRight();
-
-        mStrip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)),
-                         0,
-                         (int) (fromTabRight + ((toTabRight - fromTabRight) * progress)),
-                         getHeight());
-        invalidate();
-    }
+        final int selectedTitleOffset = (int) (positionOffset * selectedTitle.getWidth());
 
-    /*
-     * position + positionOffset goes from 0 to 2 as we scroll from page 1 to 3.
-     * Normalized progress is relative to the the direction the page is being scrolled towards.
-     * For this, we maintain direction of scroll with a state, and the child view we are moving towards and away from.
-     */
-    private void setScrollingData(int position, float positionOffset) {
-        if (position >= getChildCount() - 1) {
-            return;
-        }
-
-        final float currProgress = position + positionOffset;
-
-        if (mPrevProgress > currProgress) {
-            toTab = getChildAt(position);
-            fromTab = getChildAt(position + 1);
-            progress = 1 - positionOffset;
-        } else {
-            toTab = getChildAt(position + 1);
-            fromTab = getChildAt(position);
-            progress = positionOffset;
+        int titleLeft = selectedTitle.getLeft() + selectedTitleOffset;
+        if (position > 0) {
+            titleLeft -= titleOffset;
         }
 
-        mPrevProgress = currProgress;
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        if (mStrip != null) {
-            mStrip.draw(canvas);
-        }
-    }
-
-    @Override
-    public void onFocusChange(View v, boolean hasFocus) {
-        if (v == this && hasFocus && getChildCount() > 0) {
-            mSelectedView.requestFocus();
-            return;
+        int titleRight = selectedTitle.getRight() + selectedTitleOffset;
+        if (position < layout.getChildCount() - 1) {
+            titleRight += titleOffset;
         }
 
-        if (!hasFocus) {
-            return;
-        }
-
-        int i = 0;
-        final int numTabs = getChildCount();
-
-        while (i < numTabs) {
-            View view = getChildAt(i);
-            if (view == v) {
-                view.requestFocus();
-                if (isShown()) {
-                    // A view is focused so send an event to announce the menu strip state.
-                    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
-                }
-                break;
-            }
-
-            i++;
+        final int scrollX = getScrollX();
+        if (titleLeft < scrollX) {
+            // Tab strip overflows to the left.
+            scrollTo(titleLeft, 0);
+        } else if (titleRight > scrollX + getWidth()) {
+            // Tab strip overflows to the right.
+            scrollTo(titleRight - getWidth(), 0);
         }
     }
 
     @Override
     public void setOnTitleClickListener(HomePager.OnTitleClickListener onTitleClickListener) {
-        mOnTitleClickListener = onTitleClickListener;
-    }
-
-    private class ViewClickListener implements OnClickListener {
-        private final int mIndex;
-
-        public ViewClickListener(int index) {
-            mIndex = index;
-        }
-
-        @Override
-        public void onClick(View view) {
-            if (mOnTitleClickListener != null) {
-                mOnTitleClickListener.onTitleClicked(mIndex);
-            }
-        }
+        layout.setOnTitleClickListener(onTitleClickListener);
     }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/home/TabMenuStripLayout.java
@@ -0,0 +1,194 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.home;
+
+import org.mozilla.gecko.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * {@code TabMenuStripLayout} is the view that draws the {@code HomePager}
+ * tabs that are displayed in {@code TabMenuStrip}.
+ */
+class TabMenuStripLayout extends LinearLayout
+                         implements View.OnFocusChangeListener {
+
+    private HomePager.OnTitleClickListener onTitleClickListener;
+    private Drawable strip;
+    private View selectedView;
+
+    // Data associated with the scrolling of the strip drawable.
+    private View toTab;
+    private View fromTab;
+    private float progress;
+
+    // This variable is used to predict the direction of scroll.
+    private float prevProgress;
+
+    TabMenuStripLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabMenuStrip);
+        final int stripResId = a.getResourceId(R.styleable.TabMenuStrip_strip, -1);
+        a.recycle();
+
+        if (stripResId != -1) {
+            strip = getResources().getDrawable(stripResId);
+        }
+
+        setWillNotDraw(false);
+    }
+
+    void onAddPagerView(String title) {
+        final TextView button = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.tab_menu_strip, this, false);
+        button.setText(title.toUpperCase());
+
+        addView(button);
+        button.setOnClickListener(new ViewClickListener(getChildCount() - 1));
+        button.setOnFocusChangeListener(this);
+    }
+
+    void onPageSelected(final int position) {
+        selectedView = getChildAt(position);
+
+        // Callback to measure and draw the strip after the view is visible.
+        ViewTreeObserver vto = selectedView.getViewTreeObserver();
+        if (vto.isAlive()) {
+            vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+                @Override
+                public void onGlobalLayout() {
+                    selectedView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+
+                    if (strip != null) {
+                        strip.setBounds(selectedView.getLeft(),
+                                        selectedView.getTop(),
+                                        selectedView.getRight(),
+                                        selectedView.getBottom());
+                    }
+
+                    prevProgress = position;
+                }
+            });
+        }
+    }
+
+    // Page scroll animates the drawable and its bounds from the previous to next child view.
+    void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+        if (strip == null) {
+            return;
+        }
+
+        setScrollingData(position, positionOffset);
+
+        if (fromTab == null || toTab == null) {
+            return;
+        }
+
+        final int fromTabLeft =  fromTab.getLeft();
+        final int fromTabRight = fromTab.getRight();
+
+        final int toTabLeft =  toTab.getLeft();
+        final int toTabRight = toTab.getRight();
+
+        strip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)),
+                         0,
+                         (int) (fromTabRight + ((toTabRight - fromTabRight) * progress)),
+                         getHeight());
+        invalidate();
+    }
+
+    /*
+     * position + positionOffset goes from 0 to 2 as we scroll from page 1 to 3.
+     * Normalized progress is relative to the the direction the page is being scrolled towards.
+     * For this, we maintain direction of scroll with a state, and the child view we are moving towards and away from.
+     */
+    void setScrollingData(int position, float positionOffset) {
+        if (position >= getChildCount() - 1) {
+            return;
+        }
+
+        final float currProgress = position + positionOffset;
+
+        if (prevProgress > currProgress) {
+            toTab = getChildAt(position);
+            fromTab = getChildAt(position + 1);
+            progress = 1 - positionOffset;
+        } else {
+            toTab = getChildAt(position + 1);
+            fromTab = getChildAt(position);
+            progress = positionOffset;
+        }
+
+        prevProgress = currProgress;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (strip != null) {
+            strip.draw(canvas);
+        }
+    }
+
+    @Override
+    public void onFocusChange(View v, boolean hasFocus) {
+        if (v == this && hasFocus && getChildCount() > 0) {
+            selectedView.requestFocus();
+            return;
+        }
+
+        if (!hasFocus) {
+            return;
+        }
+
+        int i = 0;
+        final int numTabs = getChildCount();
+
+        while (i < numTabs) {
+            View view = getChildAt(i);
+            if (view == v) {
+                view.requestFocus();
+                if (isShown()) {
+                    // A view is focused so send an event to announce the menu strip state.
+                    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+                }
+                break;
+            }
+
+            i++;
+        }
+    }
+
+    void setOnTitleClickListener(HomePager.OnTitleClickListener onTitleClickListener) {
+        this.onTitleClickListener = onTitleClickListener;
+    }
+
+    private class ViewClickListener implements OnClickListener {
+        private final int mIndex;
+
+        public ViewClickListener(int index) {
+            mIndex = index;
+        }
+
+        @Override
+        public void onClick(View view) {
+            if (onTitleClickListener != null) {
+                onTitleClickListener.onTitleClicked(mIndex);
+            }
+        }
+    }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -260,16 +260,17 @@ gbjar.sources += [
     'home/PinSiteDialog.java',
     'home/ReadingListPanel.java',
     'home/SearchEngine.java',
     'home/SearchEngineRow.java',
     'home/SearchLoader.java',
     'home/SimpleCursorLoader.java',
     'home/SuggestClient.java',
     'home/TabMenuStrip.java',
+    'home/TabMenuStripLayout.java',
     'home/TopSitesGridItemView.java',
     'home/TopSitesGridView.java',
     'home/TopSitesPanel.java',
     'home/TopSitesThumbnailView.java',
     'home/TwoLinePageRow.java',
     'InputMethods.java',
     'JavaAddonManager.java',
     'LightweightTheme.java',