Bug 1317446 - 3. Add TabStripDividerItem for TabStripView dividers and spacing. r?sebastian draft
authorTom Klein <twointofive@gmail.com>
Sun, 16 Oct 2016 21:03:50 -0500
changeset 450803 1f054194e5e5958e0e1e8c201b94a596d9c563d7
parent 450802 9205455af77232610eb09dc6a717bec54f83bc2a
child 539826 7890596fad0de3750e187c6674e2b81fac88215e
push id38945
push userbmo:twointofive@gmail.com
push dateSun, 18 Dec 2016 16:58:43 +0000
reviewerssebastian
bugs1317446
milestone52.0a1
Bug 1317446 - 3. Add TabStripDividerItem for TabStripView dividers and spacing. r?sebastian MozReview-Commit-ID: 5OC58tEGQrM
mobile/android/base/java/org/mozilla/gecko/tabs/TabStripDividerItem.java
mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java
mobile/android/base/moz.build
mobile/android/base/resources/values/colors.xml
mobile/android/base/resources/values/dimens.xml
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripDividerItem.java
@@ -0,0 +1,90 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.tabs;
+
+import org.mozilla.gecko.R;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+class TabStripDividerItem extends RecyclerView.ItemDecoration {
+    private final int margin;
+    private final int dividerWidth;
+    private final int dividerHeight;
+    private final int dividerPaddingBottom;
+    private final Paint dividerPaint;
+
+    TabStripDividerItem(Context context) {
+        margin = (int) context.getResources().getDimension(R.dimen.tablet_tab_strip_item_margin);
+        dividerWidth = (int) context.getResources().getDimension(R.dimen.tablet_tab_strip_divider_width);
+        dividerHeight = (int) context.getResources().getDimension(R.dimen.tablet_tab_strip_divider_height);
+        dividerPaddingBottom = (int) context.getResources().getDimension(R.dimen.tablet_tab_strip_divider_padding_bottom);
+
+        dividerPaint = new Paint();
+        dividerPaint.setColor(ContextCompat.getColor(context, R.color.tablet_tab_strip_divider_color));
+        dividerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+    }
+
+    /**
+     * Return whether a divider should be drawn on the left side of the tab represented by
+     * {@code view}.
+     */
+    private static boolean drawLeftDividerForView(View view, RecyclerView parent) {
+        final int position = parent.getChildAdapterPosition(view);
+        // No left divider if this is tab 0 or this tab is currently pressed.
+        if (position == 0 || view.isPressed()) {
+            return false;
+        }
+
+        final int selectedPosition = ((TabStripView) parent).getPositionForSelectedTab();
+        // No left divider if this tab or the previous tab is the current selected tab.
+        if (selectedPosition != RecyclerView.NO_POSITION &&
+                (position == selectedPosition || position == selectedPosition + 1)) {
+            return false;
+        }
+
+        final RecyclerView.ViewHolder holder = parent.findViewHolderForAdapterPosition(position - 1);
+        // No left divider if the previous tab is currently pressed.
+        if (holder != null && holder.itemView.isPressed()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+        final int position = parent.getChildAdapterPosition(view);
+        final int leftOffset = position == 0 ? 0 : margin;
+        final int rightOffset = position == parent.getAdapter().getItemCount() - 1 ? 0 : margin;
+
+        outRect.set(leftOffset, 0, rightOffset, 0);
+    }
+
+    @Override
+    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        final int childCount = parent.getChildCount();
+        if (childCount == 0) {
+            return;
+        }
+
+        for (int i = 0; i < childCount; i++) {
+            final View child = parent.getChildAt(i);
+            if (drawLeftDividerForView(child, parent)) {
+                final float left = child.getLeft() + child.getTranslationX() + Math.abs(margin);
+                final float top = child.getTop() + child.getTranslationY();
+                final float dividerTop = top + child.getHeight() - dividerHeight - dividerPaddingBottom;
+                final float dividerBottom = dividerTop + dividerHeight;
+                c.drawRect(left - dividerWidth, dividerTop, left, dividerBottom, dividerPaint);
+            }
+        }
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java
@@ -46,26 +46,28 @@ public class TabStripView extends Recycl
 
         fadingEdgePaint = new Paint();
         final Resources resources = getResources();
         fadingEdgeSize =
                 resources.getDimensionPixelOffset(R.dimen.tablet_tab_strip_fading_edge_size);
 
         animatorListener = new TabAnimatorListener();
 
+        setChildrenDrawingOrderEnabled(true);
+
         adapter = new TabStripAdapter(context);
         setAdapter(adapter);
 
         final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
         layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
         setLayoutManager(layoutManager);
 
         setItemAnimator(new TabStripItemAnimator(ANIM_TIME_MS));
 
-        // TODO add item decoration.
+        addItemDecoration(new TabStripDividerItem(context));
     }
 
     /* package */ void refreshTabs() {
         // Store a different copy of the tabs, so that we don't have
         // to worry about accidentally updating it on the wrong thread.
         final List<Tab> tabs = new ArrayList<>();
 
         for (final Tab tab : Tabs.getInstance().getTabsInOrder()) {
@@ -119,18 +121,22 @@ public class TabStripView extends Recycl
         }
 
     }
 
     /* package */ void updateTab(Tab tab) {
         adapter.notifyTabChanged(tab);
     }
 
+    public int getPositionForSelectedTab() {
+        return adapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
+    }
+
     private void updateSelectedPosition() {
-        final int selected = adapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
+        final int selected = getPositionForSelectedTab();
         if (selected != -1) {
             scrollToPosition(selected);
         }
     }
 
     public void refresh() {
         // This gets called after a rotation.  Without the delay the scroll can fail to scroll far
         // enough if the selected position is the last or next to last position (and there are
@@ -145,16 +151,55 @@ public class TabStripView extends Recycl
 
     @Override
     public void onChildAttachedToWindow(View child) {
         // Make sure we didn't miss any resets after animations etc.
         child.setTranslationX(0);
         child.setTranslationY(0);
     }
 
+    /**
+     * Return the position of the currently selected tab relative to the tabs currently visible in
+     * the tabs list, or -1 if the currently selected tab isn't visible in the tabs list.
+     */
+    private int getRelativeSelectedPosition(int visibleTabsCount) {
+        final int selectedPosition = getPositionForSelectedTab();
+        final LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
+        final int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();
+        final int relativeSelectedPosition = selectedPosition - firstVisiblePosition;
+        if (relativeSelectedPosition < 0 || relativeSelectedPosition > visibleTabsCount - 1) {
+            return -1;
+        }
+
+        return relativeSelectedPosition;
+    }
+
+    @Override
+    protected int getChildDrawingOrder(int childCount, int i) {
+        final int relativeSelectedPosition = getRelativeSelectedPosition(childCount);
+        if (relativeSelectedPosition == -1) {
+            // The selected tab isn't visible, so we don't need to adjust drawing order.
+            return i;
+        }
+
+        // Explanation of the input parameters: there are childCount tabs visible, and right now
+        // we're returning which of those tabs to draw i'th, for some i between 0 and
+        // childCount - 1.
+        if (i == childCount - 1) {
+            // Draw the selected tab last.
+            return relativeSelectedPosition;
+        } else if (i >= relativeSelectedPosition) {
+            // Draw the tabs after the selected tab one iteration earlier than normal.
+            return i + 1;
+        } else {
+            // Draw the tabs before the selected tab in their normal order.
+            return i;
+        }
+    }
+
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
 
         if (w == oldw) {
             return;
         }
 
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -829,16 +829,17 @@ else:
     max_sdk_version = 999
 
 # Only bother to include new tablet code if we're building for tablet-capable
 # OS releases.
 if max_sdk_version >= 11:
     gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
         'tabs/TabStrip.java',
         'tabs/TabStripAdapter.java',
+        'tabs/TabStripDividerItem.java',
         'tabs/TabStripItemAnimator.java',
         'tabs/TabStripItemView.java',
         'tabs/TabStripView.java'
     ]]
 
 gbjar.extra_jars += [
     OBJDIR + '/../javaaddons/javaaddons-1.0.jar',
     'gecko-R.jar',
--- a/mobile/android/base/resources/values/colors.xml
+++ b/mobile/android/base/resources/values/colors.xml
@@ -140,9 +140,11 @@
 
   <color name="action_bar_bg_color">@color/toolbar_grey</color>
 
   <color name="activity_stream_divider">#FFD2D2D2</color>
   <color name="activity_stream_subtitle">#FF919191</color>
   <color name="activity_stream_timestamp">#FFD3D3D3</color>
   <color name="activity_stream_icon">#FF919191</color>
 
+  <color name="tablet_tab_strip_divider_color">#555555</color>
+
 </resources>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -35,16 +35,20 @@
 
     <!-- This is the system default for the vertical padding for the divider of the TabWidget.
          Used to mimic the divider padding on the tablet tabs panel back button. -->
     <dimen name="tab_panel_divider_vertical_padding">12dp</dimen>
 
     <dimen name="tablet_tab_strip_height">48dp</dimen>
     <dimen name="tablet_tab_strip_item_width">208dp</dimen>
     <dimen name="tablet_tab_strip_fading_edge_size">15dp</dimen>
+    <dimen name="tablet_tab_strip_item_margin">-14dp</dimen>
+    <dimen name="tablet_tab_strip_divider_width">1dp</dimen>
+    <dimen name="tablet_tab_strip_divider_height">30dp</dimen>
+    <dimen name="tablet_tab_strip_divider_padding_bottom">6dp</dimen>
     <dimen name="tablet_browser_toolbar_menu_item_width">56dp</dimen>
     <!-- Padding combines with an 18dp image to form the menu item width and height. -->
     <dimen name="tablet_browser_toolbar_menu_item_padding_horizontal">19dp</dimen>
     <dimen name="tablet_browser_toolbar_menu_item_inset_vertical">5dp</dimen>
     <dimen name="tablet_browser_toolbar_menu_item_inset_horizontal">3dp</dimen>
     <dimen name="tablet_tab_strip_button_inset">5dp</dimen>
 
     <!-- Dimensions used by Favicons and FaviconView -->