Bug 1116415 - 11. Adjust ItemDecorations to fill in extra horizontal space. r?sebastian draft
authorTom Klein <twointofive@gmail.com>
Wed, 21 Sep 2016 22:51:27 -0500
changeset 424677 17966f55c27550e30f2ec1aab5bc6bc849240436
parent 424676 0c267676cb0824d916f398155b0d5b7dec6f346c
child 533728 db939304dedbcc94a68e805dda67678707944f78
push id32215
push userbmo:twointofive@gmail.com
push dateThu, 13 Oct 2016 05:22:21 +0000
reviewerssebastian
bugs1116415
milestone52.0a1
Bug 1116415 - 11. Adjust ItemDecorations to fill in extra horizontal space. r?sebastian The GridLayoutView gives each view an equal share of the width available, but simply giving each item the same spacing ignores the fact that there's also already layout padding on the outer edges. GridSpacingDecoration takes that padding into account. MozReview-Commit-ID: L3fgjacMu2d
mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java
mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
mobile/android/base/java/org/mozilla/gecko/widget/GridSpacingDecoration.java
mobile/android/base/moz.build
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java
@@ -2,26 +2,28 @@
 /* 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 org.mozilla.gecko.Tab;
-import org.mozilla.gecko.widget.SpacingDecoration;
+import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.widget.GridSpacingDecoration;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.support.v7.widget.DefaultItemAnimator;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.helper.ItemTouchHelper;
 import android.util.AttributeSet;
 
 public class TabsGridLayout extends TabsLayout {
+    private final GridSpacingDecoration spacingDecoration;
 
     public TabsGridLayout(Context context, AttributeSet attrs) {
         super(context, attrs, R.layout.tabs_layout_item_view);
 
         final Resources resources = context.getResources();
 
         setLayoutManager(new GridLayoutManager(context, 1));
         setAutoFit(true);
@@ -29,26 +31,16 @@ public class TabsGridLayout extends Tabs
         final int itemWidth = resources.getDimensionPixelSize(R.dimen.tab_panel_item_width);
         final int horizontalItemPadding = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_item_hpadding);
         final int verticalItemPadding = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_item_vpadding);
         final int viewPaddingHorizontal = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_hpadding);
         final int viewPaddingVertical = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_vpadding);
 
         setDesiredColumnWidth(itemWidth + 2 * horizontalItemPadding);
 
-        // TODO We'll need to be more specific about how much space the ItemDecoration should fill
-        // in - otherwise we're left with empty item space that can be clicked even though it
-        // appears to the user that there should be no content there.
-        // (The issue is that GridLayoutManager left aligns its content in a given column, so we
-        // need to make content full width, and then (until we support setting thumbnail widths on
-        // the fly in the tabs layout) to keep the portion of the content outside the fixed-size
-        // thumbnail/title region unclickable, we need to fill in that region with ItemDecoration,
-        // which is what remains to be done.)
-        addItemDecoration(new SpacingDecoration(horizontalItemPadding, verticalItemPadding));
-
         setPadding(viewPaddingHorizontal, viewPaddingVertical, viewPaddingHorizontal, viewPaddingVertical);
         setClipToPadding(false);
         setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY);
 
         final DefaultItemAnimator animator = new DefaultItemAnimator();
         // On close we only animate the moves, not the remove.
         animator.setRemoveDuration(0);
         // A fade in/out each time the title/thumbnail/etc. gets updated isn't helpful, so disable
@@ -60,16 +52,30 @@ public class TabsGridLayout extends Tabs
         final TabsTouchHelperCallback callback = new TabsTouchHelperCallback(this) {
             @Override
             protected float alphaForItemSwipeDx(float dX, int distanceToAlphaMin) {
                 return 1f - 2f * Math.abs(dX) / distanceToAlphaMin;
             }
         };
         final ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
         touchHelper.attachToRecyclerView(this);
+
+        spacingDecoration = new GridSpacingDecoration(itemWidth, verticalItemPadding);
+        addItemDecoration(spacingDecoration);
+    }
+
+    @Override
+    protected void onSpanCountChanged() {
+        // spanCount can change (in onMeasure) at a point where some ItemDecorations have been
+        // computed and some have not, and Android doesn't recompute ItemDecorations after a
+        // setSpanCount call, so we need to remove and then add back our spacingDecoration (whose
+        // computations depend on spanCount) in order to get a full layout recompute.
+        removeItemDecoration(spacingDecoration);
+        addItemDecoration(spacingDecoration);
+        updateSelectedPosition();
     }
 
     @Override
     public void closeAll() {
         autoHidePanel();
 
         closeAllTabs();
     }
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
@@ -144,17 +144,17 @@ public abstract class TabsLayout extends
         final int tabId = item.getTabId();
         final Tabs tabs = Tabs.getInstance();
         tabs.selectTab(tabId);
         autoHidePanel();
         tabs.notifyListeners(tabs.getTab(tabId), Tabs.TabEvents.OPENED_FROM_TABS_TRAY);
     }
 
     // Updates the selected position in the list so that it will be scrolled to the right place.
-    private void updateSelectedPosition() {
+    protected void updateSelectedPosition() {
         final int selected = getSelectedAdapterPosition();
         if (selected != NO_POSITION) {
             scrollToPosition(selected);
         }
     }
 
     private void refreshTabsData() {
         // Store a different copy of the tabs, so that we don't have to worry about
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/widget/GridSpacingDecoration.java
@@ -0,0 +1,68 @@
+/* -*- 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.widget;
+
+import android.graphics.Rect;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+// An ItemDecoration for a GridLayoutManager to adjust fixed width items within their columns so as
+// to maintain as much space between items as possible.  The computations take into account and use
+// any horizontal RecyclerView padding as part of the spacing of first and last items in a row.
+// This decoration assumes the RecyclerView's left and right padding are equal.
+public class GridSpacingDecoration extends RecyclerView.ItemDecoration {
+    private final int verticalPadding;
+    private final int itemWidth;
+
+    public GridSpacingDecoration(int itemWidth, int verticalPadding) {
+        this.verticalPadding = verticalPadding;
+        this.itemWidth = itemWidth;
+    }
+
+    @Override
+    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+        final int position = parent.getChildAdapterPosition(view);
+        if (position == RecyclerView.NO_POSITION) {
+            return;
+        }
+
+        final GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
+        final int spanCount = layoutManager.getSpanCount();
+        final int layoutWidth = parent.getWidth();
+        // We currently assume left and right padding are equal.
+        final int layoutHorizontalPadding = layoutManager.getPaddingLeft();
+        final int innerLayoutWidth = layoutWidth - 2 * layoutHorizontalPadding;
+
+        if (spanCount == 1) {
+            final int offset = (innerLayoutWidth - itemWidth) / 2;
+            outRect.set(offset, verticalPadding, offset, verticalPadding);
+            return;
+        }
+
+        // The formulas here compute the start and end of each item within its column so as to
+        // evenly space items within the inner layout (i.e. the space between the layout padding)
+        // while still using the layout padding as part of the outer spacing of the outer items to
+        // the extent possible.
+        final int rowPosition = position % spanCount;
+        // The ideal spacing has equal spacing (on each side) around every item...
+        final int idealSpacing = (layoutWidth - spanCount * itemWidth) / (spanCount + 1);
+        // ...but we're confined to having at least the layout padding's amount of spacing on the
+        // outer edges of the outer items, so use that as much as possible, and then compute
+        // outerOffset to be how much more (if any) is needed for the outer edge of the outer items.
+        final int outerOffset = Math.max(0, idealSpacing - layoutHorizontalPadding);
+        // Offset for the start of this view from the inner edge of the left padding:
+        final float innerLayoutOffset =
+                rowPosition * ((float) (innerLayoutWidth - 2 * outerOffset - itemWidth) / (float) (spanCount - 1)) + outerOffset;
+        // Left offset of the start of this view within its column:
+        final int columnLeftOffset = (int) (innerLayoutOffset - (float) (rowPosition * innerLayoutWidth) / (float) spanCount);
+        // Right offset of the end of this view within its column:
+        final int columnRightOffset =
+                (int) ((float) ((rowPosition + 1) * innerLayoutWidth) / (float) spanCount - (innerLayoutOffset + itemWidth));
+
+        outRect.set(columnLeftOffset, verticalPadding, columnRightOffset, verticalPadding);
+    }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -738,16 +738,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'widget/FadedMultiColorTextView.java',
     'widget/FadedSingleColorTextView.java',
     'widget/FadedTextView.java',
     'widget/FaviconView.java',
     'widget/FilledCardView.java',
     'widget/FlowLayout.java',
     'widget/GeckoActionProvider.java',
     'widget/GeckoPopupMenu.java',
+    'widget/GridSpacingDecoration.java',
     'widget/HistoryDividerItemDecoration.java',
     'widget/IconTabWidget.java',
     'widget/LoginDoorHanger.java',
     'widget/RecyclerViewClickSupport.java',
     'widget/ResizablePathDrawable.java',
     'widget/RoundedCornerLayout.java',
     'widget/SiteLogins.java',
     'widget/SpacingDecoration.java',