Bug 1317446 - 1. Make TabStripView a RecyclerView. r?sebastian draft
authorTom Klein <twointofive@gmail.com>
Mon, 14 Nov 2016 15:02:02 -0600
changeset 450801 f8fc7acc3863eb36c7fef1016e81f232c49d1536
parent 437787 d284cdb3ad6e0609a53a275df6efcff4255c30ec
child 450802 9205455af77232610eb09dc6a717bec54f83bc2a
push id38945
push userbmo:twointofive@gmail.com
push dateSun, 18 Dec 2016 16:58:43 +0000
reviewerssebastian
bugs1317446
milestone52.0a1
Bug 1317446 - 1. Make TabStripView a RecyclerView. r?sebastian MozReview-Commit-ID: EGzDZBpAV3N
mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java
mobile/android/base/java/org/mozilla/gecko/tabs/TabStripAdapter.java
mobile/android/base/java/org/mozilla/gecko/tabs/TabStripItemView.java
mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java
mobile/android/base/resources/drawable/tab_strip_divider.xml
mobile/android/base/resources/values/dimens.xml
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/TabStripComponent.java
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java
@@ -109,17 +109,18 @@ public class TabStrip extends ThemedLine
         @Override
         public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
             switch (msg) {
                 case RESTORED:
                     tabStripView.restoreTabs();
                     break;
 
                 case ADDED:
-                    tabStripView.addTab(tab);
+                    final int tabIndex = Integer.parseInt(data);
+                    tabStripView.addTab(tab, tabIndex);
                     if (tabChangedListener != null) {
                         tabChangedListener.onTabChanged();
                     }
                     break;
 
                 case CLOSED:
                     tabStripView.removeTab(tab);
                     if (tabChangedListener != null) {
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripAdapter.java
@@ -1,98 +1,107 @@
 /* -*- 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 android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.BaseAdapter;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.Tab;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
+class TabStripAdapter extends RecyclerView.Adapter<TabStripAdapter.TabStripViewHolder> {
+    private static final String LOGTAG = "Gecko" + TabStripAdapter.class.getSimpleName();
 
-class TabStripAdapter extends BaseAdapter {
-    private static final String LOGTAG = "GeckoTabStripAdapter";
+    private @NonNull List<Tab> tabs;
+    private final LayoutInflater inflater;
 
-    private final Context context;
-    private List<Tab> tabs;
-
-    public TabStripAdapter(Context context) {
-        this.context = context;
+    static class TabStripViewHolder extends RecyclerView.ViewHolder {
+        TabStripViewHolder(View itemView) {
+            super(itemView);
+        }
     }
 
-    @Override
-    public Tab getItem(int position) {
-        return (tabs != null &&
-                position >= 0 &&
-                position < tabs.size() ? tabs.get(position) : null);
+    TabStripAdapter(Context context) {
+        inflater = LayoutInflater.from(context);
+        tabs = new ArrayList<>(0);
     }
 
-    @Override
-    public long getItemId(int position) {
-        final Tab tab = getItem(position);
-        return (tab != null ? tab.getId() : -1);
+    public void refresh(@NonNull List<Tab> tabs) {
+        this.tabs = tabs;
+        notifyDataSetChanged();
     }
 
     @Override
-    public boolean hasStableIds() {
-        return true;
+    public int getItemCount() {
+        return tabs.size();
     }
 
-    @Override
-    public View getView(int position, View convertView, ViewGroup parent) {
-        final TabStripItemView item;
-        if (convertView == null) {
-            item = (TabStripItemView)
-                    LayoutInflater.from(context).inflate(R.layout.tab_strip_item, parent, false);
-        } else {
-            item = (TabStripItemView) convertView;
-        }
-
-        final Tab tab = tabs.get(position);
-        item.updateFromTab(tab);
-
-        return item;
-    }
-
-    @Override
-    public int getCount() {
-        return (tabs != null ? tabs.size() : 0);
-    }
-
-    int getPositionForTab(Tab tab) {
-        if (tabs == null || tab == null) {
+    /* package */ int getPositionForTab(Tab tab) {
+        if (tab == null) {
             return -1;
         }
 
         return tabs.indexOf(tab);
     }
 
-    void removeTab(Tab tab) {
-        if (tabs == null) {
+    /* package */ void addTab(Tab tab, int position) {
+        if (position >= 0 && position <= tabs.size()) {
+            tabs.add(position, tab);
+            notifyItemInserted(position);
+        } else {
+            // Add to the end.
+            tabs.add(tab);
+            notifyItemInserted(tabs.size() - 1);
+            // index == -1 is a valid way to add to the end, the other cases are errors.
+            if (position != -1) {
+                Log.e(LOGTAG, "Tab was inserted at an invalid position: " + position);
+            }
+        }
+    }
+
+    /* package */ void removeTab(Tab tab) {
+        final int position = getPositionForTab(tab);
+        if (position == -1) {
             return;
         }
+        tabs.remove(position);
+        notifyItemRemoved(position);
+    }
 
-        tabs.remove(tab);
+    /* package */ void notifyTabChanged(Tab tab) {
+        final int position =  getPositionForTab(tab);
+        if (position == -1) {
+            return;
+        }
+        notifyItemChanged(position);
+    }
+
+    /* package */ void clear() {
+        tabs = new ArrayList<>(0);
         notifyDataSetChanged();
     }
 
-    void refresh(List<Tab> tabs) {
-        // The list of tabs is guaranteed to be non-null.
-        // See TabStripView.refreshTabs().
-        this.tabs = tabs;
-        notifyDataSetChanged();
+    @Override
+    public TabStripViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        final TabStripItemView view = (TabStripItemView) inflater.inflate(R.layout.tab_strip_item, parent, false);
+
+        return new TabStripViewHolder(view);
     }
 
-    void clear() {
-        tabs = null;
-        notifyDataSetInvalidated();
+    @Override
+    public void onBindViewHolder(TabStripViewHolder viewHolder, int position) {
+        final Tab tab = tabs.get(position);
+        final TabStripItemView itemView = (TabStripItemView) viewHolder.itemView;
+        itemView.updateFromTab(tab);
     }
-}
\ No newline at end of file
+}
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripItemView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripItemView.java
@@ -187,16 +187,17 @@ public class TabStripItemView extends Th
 
     void updateFromTab(Tab tab) {
         if (tab == null) {
             return;
         }
 
         id = tab.getId();
 
+        setChecked(Tabs.getInstance().isSelectedTab(tab));
         updateTitle(tab);
         updateFavicon(tab.getFavicon());
         setPrivateMode(tab.isPrivate());
     }
 
     private void updateTitle(Tab tab) {
         final String title;
 
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java
@@ -1,219 +1,213 @@
 /* -*- 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 org.mozilla.gecko.Tab;
+import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.LinearGradient;
 import android.graphics.Paint;
-import android.graphics.Rect;
 import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
-import android.view.animation.DecelerateInterpolator;
 import android.view.View;
-import android.view.ViewTreeObserver.OnPreDrawListener;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
+import android.view.ViewTreeObserver;
+import android.view.animation.DecelerateInterpolator;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.widget.TwoWayView;
-
-public class TabStripView extends TwoWayView {
-    private static final String LOGTAG = "GeckoTabStrip";
-
+public class TabStripView extends RecyclerView {
     private static final int ANIM_TIME_MS = 200;
-    private static final DecelerateInterpolator ANIM_INTERPOLATOR =
-            new DecelerateInterpolator();
+    private static final DecelerateInterpolator ANIM_INTERPOLATOR = new DecelerateInterpolator();
 
     private final TabStripAdapter adapter;
-    private final Drawable divider;
+    private boolean isPrivate;
 
     private final TabAnimatorListener animatorListener;
 
-    private boolean isRestoringTabs;
-
-    // Filled by calls to ShapeDrawable.getPadding();
-    // saved to prevent allocation in draw().
-    private final Rect dividerPadding = new Rect();
-
-    private boolean isPrivate;
-
     private final Paint fadingEdgePaint;
     private final int fadingEdgeSize;
 
     public TabStripView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        setOrientation(Orientation.HORIZONTAL);
-        setChoiceMode(ChoiceMode.SINGLE);
-        setItemsCanFocus(true);
-        setChildrenDrawingOrderEnabled(true);
-        setWillNotDraw(false);
-
+        fadingEdgePaint = new Paint();
         final Resources resources = getResources();
-
-        divider = resources.getDrawable(R.drawable.tab_strip_divider);
-        divider.getPadding(dividerPadding);
-
-        final int itemMargin =
-                resources.getDimensionPixelSize(R.dimen.tablet_tab_strip_item_margin);
-        setItemMargin(itemMargin);
+        fadingEdgeSize =
+                resources.getDimensionPixelOffset(R.dimen.tablet_tab_strip_fading_edge_size);
 
         animatorListener = new TabAnimatorListener();
 
-        fadingEdgePaint = new Paint();
-        fadingEdgeSize =
-                resources.getDimensionPixelOffset(R.dimen.tablet_tab_strip_fading_edge_size);
-
         adapter = new TabStripAdapter(context);
         setAdapter(adapter);
-    }
 
-    private View getViewForTab(Tab tab) {
-        final int position = adapter.getPositionForTab(tab);
-        return getChildAt(position - getFirstVisiblePosition());
-    }
+        final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
+        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
+        setLayoutManager(layoutManager);
 
-    private int getPositionForSelectedTab() {
-        return adapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
+        // TODO add item animator.
+
+        // TODO add item decoration.
     }
 
-    private void updateSelectedStyle(int selected) {
-        setItemChecked(selected, true);
+    /* 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()) {
+            if (tab.isPrivate() == isPrivate) {
+                tabs.add(tab);
+            }
+        }
+
+        adapter.refresh(tabs);
+        updateSelectedPosition();
     }
 
-    private void updateSelectedPosition(boolean ensureVisible) {
-        final int selected = getPositionForSelectedTab();
-        if (selected != -1) {
-            updateSelectedStyle(selected);
+    /* package */ void clearTabs() {
+        adapter.clear();
+    }
 
-            if (ensureVisible) {
-                ensurePositionIsVisible(selected, true);
-            }
+    /* package */ void restoreTabs() {
+        refreshTabs();
+        animateRestoredTabs();
+    }
+
+    /* package */ void addTab(Tab tab, int position) {
+        adapter.addTab(tab, position);
+        position = position == -1 ? adapter.getItemCount() - 1 : position;
+        if (position == 0 || position == adapter.getItemCount() - 1) {
+            // A new first or last tab gets added off screen, so scroll to make it visible.
+            scrollToPosition(position);
         }
     }
 
-    private void animateRemoveTab(Tab removedTab) {
-        final int removedPosition = adapter.getPositionForTab(removedTab);
+    /* package */ void removeTab(Tab tab) {
+        adapter.removeTab(tab);
+    }
+
+    /* package */ void selectTab(Tab tab) {
+        if (tab.isPrivate() != isPrivate) {
+            isPrivate = tab.isPrivate();
+            refreshTabs();
+            return;
+        }
+        final int position = adapter.getPositionForTab(tab);
+        if (position == -1) {
+            return;
+        }
+        // scrollToPosition sometimes needlessly scrolls even when position is already in view, so
+        // don't scrollToPosition unless necessary.
+        final LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
+        if (position < layoutManager.findFirstCompletelyVisibleItemPosition() ||
+                position > layoutManager.findLastCompletelyVisibleItemPosition()) {
+            scrollToPosition(position);
+        }
+
+    }
+
+    /* package */ void updateTab(Tab tab) {
+        adapter.notifyTabChanged(tab);
+    }
 
-        final View removedView = getViewForTab(removedTab);
+    private void updateSelectedPosition() {
+        final int selected = adapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
+        if (selected != -1) {
+            scrollToPosition(selected);
+        }
+    }
 
-        // The removed position might not have a matching child view
-        // when it's not within the visible range of positions in the strip.
-        if (removedView == null) {
+    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
+        // enough open tabs so that the last two tabs aren't automatically always in view).
+        ThreadUtils.postDelayedToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                updateSelectedPosition();
+            }
+        }, 50);
+    }
+
+    @Override
+    public void onChildAttachedToWindow(View child) {
+        // Make sure we didn't miss any resets after animations etc.
+        child.setTranslationX(0);
+        child.setTranslationY(0);
+        child.setAlpha(1);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        if (w == oldw) {
             return;
         }
 
-        // We don't animate the removed child view (it just disappears)
-        // but we still need its size of animate all affected children
-        // within the visible viewport.
-        final int removedSize = removedView.getWidth() + getItemMargin();
+        fadingEdgePaint.setShader(new LinearGradient(w - fadingEdgeSize, 0, w, 0,
+                new int[] { 0x0, 0x11292C29, 0xDD292C29 },
+                new float[] { 0, 0.4f, 1.0f }, Shader.TileMode.CLAMP));
+    }
+
+    private float getFadingEdgeStrength() {
+        final int childCount = getChildCount();
+        if (childCount == 0) {
+            return 0.0f;
+        } else {
+            final LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
+            if (layoutManager.findLastVisibleItemPosition() < adapter.getItemCount() - 1) {
+                return 1.0f;
+            }
+
+            final int right = getChildAt(getChildCount() - 1).getRight();
+            final int paddingRight = getPaddingRight();
+            final int width = getWidth();
 
-        getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+            final float strength = (right > width - paddingRight ?
+                    (float) (right - width + paddingRight) / fadingEdgeSize : 0.0f);
+
+            return Math.max(0.0f, Math.min(strength, 1.0f));
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        final float strength = getFadingEdgeStrength();
+        if (strength > 0.0f) {
+            final int r = getRight();
+            canvas.drawRect(r - fadingEdgeSize, getTop(), r, getBottom(), fadingEdgePaint);
+            fadingEdgePaint.setAlpha((int) (strength * 255));
+        }
+    }
+
+    private void animateRestoredTabs() {
+        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
             @Override
             public boolean onPreDraw() {
                 getViewTreeObserver().removeOnPreDrawListener(this);
 
-                final int firstPosition = getFirstVisiblePosition();
-                final List<Animator> childAnimators = new ArrayList<Animator>();
-
-                final int childCount = getChildCount();
-                for (int i = removedPosition - firstPosition; i < childCount; i++) {
-                    final View child = getChildAt(i);
-
-                    final ObjectAnimator animator =
-                            ObjectAnimator.ofFloat(child, "translationX", removedSize, 0);
-                    childAnimators.add(animator);
-                }
-
-                final AnimatorSet animatorSet = new AnimatorSet();
-                animatorSet.playTogether(childAnimators);
-                animatorSet.setDuration(ANIM_TIME_MS);
-                animatorSet.setInterpolator(ANIM_INTERPOLATOR);
-                animatorSet.addListener(animatorListener);
-
-                animatorSet.start();
-
-                return true;
-            }
-        });
-    }
-
-    private void animateNewTab(Tab newTab) {
-        final int newPosition = adapter.getPositionForTab(newTab);
-        if (newPosition < 0) {
-            return;
-        }
-
-        getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
-            @Override
-            public boolean onPreDraw() {
-                getViewTreeObserver().removeOnPreDrawListener(this);
-
-                final int firstPosition = getFirstVisiblePosition();
-
-                final View newChild = getChildAt(newPosition - firstPosition);
-                if (newChild == null) {
-                    return true;
-                }
-
-                final List<Animator> childAnimators = new ArrayList<Animator>();
-                childAnimators.add(
-                        ObjectAnimator.ofFloat(newChild, "translationY", newChild.getHeight(), 0));
-
-                // This will momentaneously add a gap on the right side
-                // because TwoWayView doesn't provide APIs to control
-                // view recycling programatically to handle these transitory
-                // states in the container during animations.
-
-                final int tabSize = newChild.getWidth();
-                final int newIndex = newPosition - firstPosition;
-                final int childCount = getChildCount();
-                for (int i = newIndex + 1; i < childCount; i++) {
-                    final View child = getChildAt(i);
-
-                    childAnimators.add(
-                        ObjectAnimator.ofFloat(child, "translationX", -tabSize, 0));
-                }
-
-                final AnimatorSet animatorSet = new AnimatorSet();
-                animatorSet.playTogether(childAnimators);
-                animatorSet.setDuration(ANIM_TIME_MS);
-                animatorSet.setInterpolator(ANIM_INTERPOLATOR);
-                animatorSet.addListener(animatorListener);
-
-                animatorSet.start();
-
-                return true;
-            }
-        });
-    }
-
-    private void animateRestoredTabs() {
-        getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
-            @Override
-            public boolean onPreDraw() {
-                getViewTreeObserver().removeOnPreDrawListener(this);
-
-                final List<Animator> childAnimators = new ArrayList<Animator>();
+                final List<Animator> childAnimators = new ArrayList<>();
 
                 final int tabHeight = getHeight() - getPaddingTop() - getPaddingBottom();
                 final int childCount = getChildCount();
                 for (int i = 0; i < childCount; i++) {
                     final View child = getChildAt(i);
 
                     childAnimators.add(
                         ObjectAnimator.ofFloat(child, "translationY", tabHeight, 0));
@@ -227,203 +221,17 @@ public class TabStripView extends TwoWay
 
                 animatorSet.start();
 
                 return true;
             }
         });
     }
 
-    /**
-     * Ensures the tab at the given position is visible. If we are not restoring tabs and
-     * shouldAnimate == true, the tab will animate to be visible, if it is not already visible.
-     */
-    private void ensurePositionIsVisible(final int position, final boolean shouldAnimate) {
-        // We just want to move the strip to the right position
-        // when restoring tabs on startup.
-        if (isRestoringTabs || !shouldAnimate) {
-            setSelection(position);
-            return;
-        }
-
-        getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
-            @Override
-            public boolean onPreDraw() {
-                getViewTreeObserver().removeOnPreDrawListener(this);
-                smoothScrollToPosition(position);
-                return true;
-            }
-        });
-    }
-
-    private int getCheckedIndex(int childCount) {
-        final int checkedIndex = getCheckedItemPosition() - getFirstVisiblePosition();
-        if (checkedIndex < 0 || checkedIndex > childCount - 1) {
-            return INVALID_POSITION;
-        }
-
-        return checkedIndex;
-    }
-
-    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<Tab>();
-
-        for (Tab tab : Tabs.getInstance().getTabsInOrder()) {
-            if (tab.isPrivate() == isPrivate) {
-                tabs.add(tab);
-            }
-        }
-
-        adapter.refresh(tabs);
-        updateSelectedPosition(true);
-    }
-
-    void clearTabs() {
-        adapter.clear();
-    }
-
-    void restoreTabs() {
-        isRestoringTabs = true;
-        refreshTabs();
-        animateRestoredTabs();
-        isRestoringTabs = false;
-    }
-
-    void addTab(Tab tab) {
-        // Refresh the list to make sure the new tab is
-        // added in the right position.
-        refreshTabs();
-        animateNewTab(tab);
-    }
-
-    void removeTab(Tab tab) {
-        animateRemoveTab(tab);
-        adapter.removeTab(tab);
-        updateSelectedPosition(false);
-    }
-
-    void selectTab(Tab tab) {
-        if (tab.isPrivate() != isPrivate) {
-            isPrivate = tab.isPrivate();
-            refreshTabs();
-        } else {
-            updateSelectedPosition(true);
-        }
-    }
-
-    void updateTab(Tab tab) {
-        final TabStripItemView item = (TabStripItemView) getViewForTab(tab);
-        if (item != null) {
-            item.updateFromTab(tab);
-        }
-    }
-
-    private float getFadingEdgeStrength() {
-        final int childCount = getChildCount();
-        if (childCount == 0) {
-            return 0.0f;
-        } else {
-            if (getFirstVisiblePosition() + childCount - 1 < adapter.getCount() - 1) {
-                return 1.0f;
-            }
-
-            final int right = getChildAt(childCount - 1).getRight();
-            final int paddingRight = getPaddingRight();
-            final int width = getWidth();
-
-            final float strength = (right > width - paddingRight ?
-                    (float) (right - width + paddingRight) / fadingEdgeSize : 0.0f);
-
-            return Math.max(0.0f, Math.min(strength, 1.0f));
-        }
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-
-        fadingEdgePaint.setShader(new LinearGradient(w - fadingEdgeSize, 0, w, 0,
-                new int[] { 0x0, 0x11292C29, 0xDD292C29 },
-                new float[] { 0, 0.4f, 1.0f }, Shader.TileMode.CLAMP));
-    }
-
-    @Override
-    protected int getChildDrawingOrder(int childCount, int i) {
-        final int checkedIndex = getCheckedIndex(childCount);
-        if (checkedIndex == INVALID_POSITION) {
-            return i;
-        }
-
-        // Always draw the currently selected tab on top of all
-        // other child views so that its curve is fully visible.
-        if (i == childCount - 1) {
-            return checkedIndex;
-        } else if (checkedIndex <= i) {
-            return i + 1;
-        } else {
-            return i;
-        }
-    }
-
-    private void drawDividers(Canvas canvas) {
-        final int bottom = getHeight() - getPaddingBottom() - dividerPadding.bottom;
-        final int top = bottom - divider.getIntrinsicHeight();
-
-        final int dividerWidth = divider.getIntrinsicWidth();
-        final int itemMargin = getItemMargin();
-
-        final int childCount = getChildCount();
-        final int checkedIndex = getCheckedIndex(childCount);
-
-        for (int i = 1; i < childCount; i++) {
-            final View child = getChildAt(i);
-
-            final boolean pressed = (child.isPressed() || getChildAt(i - 1).isPressed());
-            final boolean checked = (i == checkedIndex || i == checkedIndex + 1);
-
-            // Don't draw dividers for around checked or pressed items
-            // so that they are not drawn on top of the tab curves.
-            if (pressed || checked) {
-                continue;
-            }
-
-            final int left = child.getLeft() - (itemMargin / 2) - dividerWidth;
-            final int right = left + dividerWidth;
-
-            divider.setBounds(left, top, right, bottom);
-            divider.draw(canvas);
-        }
-    }
-
-    private void drawFadingEdge(Canvas canvas) {
-        final float strength = getFadingEdgeStrength();
-        if (strength > 0.0f) {
-            final int r = getRight();
-            canvas.drawRect(r - fadingEdgeSize, getTop(), r, getBottom(), fadingEdgePaint);
-            fadingEdgePaint.setAlpha((int) (strength * 255));
-        }
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        super.draw(canvas);
-        drawDividers(canvas);
-        drawFadingEdge(canvas);
-    }
-
-    public void refresh() {
-        final int selectedPosition = getPositionForSelectedTab();
-        if (selectedPosition != -1) {
-            ensurePositionIsVisible(selectedPosition, false);
-        }
-    }
-
-    private class TabAnimatorListener implements AnimatorListener {
+    private class TabAnimatorListener implements Animator.AnimatorListener {
         private void setLayerType(int layerType) {
             final int childCount = getChildCount();
             for (int i = 0; i < childCount; i++) {
                 getChildAt(i).setLayerType(layerType, null);
             }
         }
 
         @Override
@@ -439,11 +247,10 @@ public class TabStripView extends TwoWay
 
         @Override
         public void onAnimationRepeat(Animator animation) {
         }
 
         @Override
         public void onAnimationCancel(Animator animation) {
         }
-
     }
-}
\ No newline at end of file
+}
deleted file mode 100644
--- a/mobile/android/base/resources/drawable/tab_strip_divider.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-
-    <solid android:color="#555555"/>
-
-    <size android:width="1dp"
-          android:height="30dp"/>
-
-    <!-- We draw this ourselves in TabStripView.draw() and to avoid implementing more
-         than we have to, only bottom padding is taken into account. -->
-    <padding android:bottom="6dp"/>
-
-</shape>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -34,17 +34,16 @@
     <dimen name="tablet_nav_button_width_plus_half">63dp</dimen>
 
     <!-- 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_item_margin">-28dp</dimen>
     <dimen name="tablet_tab_strip_fading_edge_size">15dp</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>
 
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/TabStripComponent.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/components/TabStripComponent.java
@@ -1,18 +1,18 @@
 package org.mozilla.gecko.tests.components;
 
 import android.view.View;
+import android.support.v7.widget.RecyclerView;
 
 import com.robotium.solo.Condition;
 
 import org.mozilla.gecko.tests.UITestContext;
 import org.mozilla.gecko.tests.helpers.DeviceHelper;
 import org.mozilla.gecko.tests.helpers.WaitHelper;
-import org.mozilla.gecko.widget.TwoWayView;
 
 import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
 
 /**
  * A class representing any interactions that take place on the tablet tab strip.
  */
 public class TabStripComponent extends BaseComponent {
     // Using a text id because the layout and therefore the id might be stripped from the (non-tablet) build
@@ -27,30 +27,34 @@ public class TabStripComponent extends B
         DeviceHelper.assertIsTablet();
 
         View tabView = waitForTabView(index);
         fAssertNotNull(String.format("Tab at index %d is not null", index), tabView);
 
         mSolo.clickOnView(tabView);
     }
 
+    /**
+     * Note: this currently only supports the case where the tab strip visible tabs start at tab 0
+     * and the tab at {@code index} is visible in the tab strip.
+     */
     private View waitForTabView(final int index) {
-        final TwoWayView tabStrip = getTabStripView();
+        final RecyclerView tabStrip = getTabStripView();
         final View[] tabView = new View[1];
 
         WaitHelper.waitFor(String.format("Tab at index %d to be visible", index), new Condition() {
             @Override
             public boolean isSatisfied() {
                 return (tabView[0] = tabStrip.getChildAt(index)) != null;
             }
         });
 
         return tabView[0];
     }
 
-    private TwoWayView getTabStripView() {
-        TwoWayView tabStrip = (TwoWayView) mSolo.getView("tab_strip");
+    private RecyclerView getTabStripView() {
+        RecyclerView tabStrip = (RecyclerView) mSolo.getView("tab_strip");
 
         fAssertNotNull("Tab strip is not null", tabStrip);
 
         return tabStrip;
     }
 }