Tidy up TabLayout + ViewPager integration

Also added the ability to add/remove OnPageChangeListeners
to ViewPager.

BUG: 20897298
Change-Id: I51ec2117a1f49aab15f7ed1a30960330fa00c317
diff --git a/design/api/current.txt b/design/api/current.txt
index 3cfe257..412410e 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -239,8 +239,6 @@
     method public void addTab(android.support.design.widget.TabLayout.Tab, int);
     method public void addTab(android.support.design.widget.TabLayout.Tab, boolean);
     method public void addTab(android.support.design.widget.TabLayout.Tab, int, boolean);
-    method public void addTabsFromPagerAdapter(android.support.v4.view.PagerAdapter);
-    method public android.support.v4.view.ViewPager.OnPageChangeListener createOnPageChangeListener();
     method public android.support.design.widget.TabLayout.Tab getTabAt(int);
     method public int getTabCount();
     method public int getTabGravity();
@@ -256,6 +254,8 @@
     method public void setTabMode(int);
     method public void setTabTextColors(android.content.res.ColorStateList);
     method public void setTabTextColors(int, int);
+    method public void setTabsFromPagerAdapter(android.support.v4.view.PagerAdapter);
+    method public void setupWithViewPager(android.support.v4.view.ViewPager);
     field public static final int GRAVITY_CENTER = 1; // 0x1
     field public static final int GRAVITY_FILL = 0; // 0x0
     field public static final int MODE_FIXED = 1; // 0x1
@@ -287,6 +287,20 @@
     field public static final int INVALID_POSITION = -1; // 0xffffffff
   }
 
+  public static class TabLayout.TabLayoutOnPageChangeListener implements android.support.v4.view.ViewPager.OnPageChangeListener {
+    ctor public TabLayout.TabLayoutOnPageChangeListener(android.support.design.widget.TabLayout);
+    method public void onPageScrollStateChanged(int);
+    method public void onPageScrolled(int, float, int);
+    method public void onPageSelected(int);
+  }
+
+  public static class TabLayout.ViewPagerOnTabSelectedListener implements android.support.design.widget.TabLayout.OnTabSelectedListener {
+    ctor public TabLayout.ViewPagerOnTabSelectedListener(android.support.v4.view.ViewPager);
+    method public void onTabReselected(android.support.design.widget.TabLayout.Tab);
+    method public void onTabSelected(android.support.design.widget.TabLayout.Tab);
+    method public void onTabUnselected(android.support.design.widget.TabLayout.Tab);
+  }
+
   public class TextInputLayout extends android.widget.LinearLayout {
     ctor public TextInputLayout(android.content.Context);
     ctor public TextInputLayout(android.content.Context, android.util.AttributeSet);
diff --git a/design/src/android/support/design/widget/TabLayout.java b/design/src/android/support/design/widget/TabLayout.java
index 05cb354..e475040 100755
--- a/design/src/android/support/design/widget/TabLayout.java
+++ b/design/src/android/support/design/widget/TabLayout.java
@@ -51,6 +51,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Iterator;
 
@@ -70,14 +71,14 @@
  * notified when any tab's selection state has been changed.
  * <p>
  * If you're using a {@link android.support.v4.view.ViewPager} together
- * with this layout, you can use {@link #addTabsFromPagerAdapter(PagerAdapter)} which will populate
- * the tabs using the {@link PagerAdapter}'s page titles. You should also use a {@link
- * ViewPager.OnPageChangeListener} to forward the scroll and selection changes to this layout.
- * You can use the one returned {@link #createOnPageChangeListener()} for easy implementation:
+ * with this layout, you can use {@link #setTabsFromPagerAdapter(PagerAdapter)} which will populate
+ * the tabs using the given {@link PagerAdapter}'s page titles. You should also use a
+ * {@link TabLayoutOnPageChangeListener} to forward the scroll and selection changes to this
+ * layout like so:
  * <pre>
  * ViewPager viewPager = ...;
  * TabLayout tabLayout = ...;
- * viewPager.setOnPageChangeListener(tabLayout.createOnPageChangeListener());
+ * viewPager.addOnPageChangeListener(new TabLayoutOnPageChangeListener(tabLayout));
  * </pre>
  *
  * @see <a href="http://www.google.com/design/spec/components/tabs.html">Tabs</a>
@@ -270,7 +271,7 @@
 
     /**
      * Set the scroll position of the tabs. This is useful for when the tabs are being displayed as
-     * part of a scrolling container such as {@link ViewPager}.
+     * part of a scrolling container such as {@link android.support.v4.view.ViewPager}.
      * <p>
      * Calling this method does not update the selected tab, it is only used for drawing purposes.
      *
@@ -297,51 +298,6 @@
     }
 
     /**
-     * Add new {@link Tab}s populated from a {@link PagerAdapter}. Each tab will have it's text set
-     * to the value returned from {@link PagerAdapter#getPageTitle(int)}.
-     *
-     * @param adapter the adapter to populate from
-     */
-    public void addTabsFromPagerAdapter(PagerAdapter adapter) {
-        for (int i = 0, count = adapter.getCount(); i < count; i++) {
-            addTab(newTab().setText(adapter.getPageTitle(i)));
-        }
-    }
-
-    /**
-     * Create a {@link ViewPager.OnPageChangeListener} which implements the
-     * necessary calls back to this layout so that the tabs position is kept in sync.
-     * <p>
-     * If you need to have a custom {@link ViewPager.OnPageChangeListener} for your own
-     * purposes, you can still use the instance returned from this method, but making sure to call
-     * through to all of the methods.
-     */
-    public ViewPager.OnPageChangeListener createOnPageChangeListener() {
-        return new ViewPager.OnPageChangeListener() {
-            private int mScrollState;
-
-            @Override
-            public void onPageScrollStateChanged(int state) {
-                mScrollState = state;
-            }
-
-            @Override
-            public void onPageScrolled(int position, float positionOffset,
-                    int positionOffsetPixels) {
-                // Update the scroll position, only update the text selection if we're being
-                // dragged
-                setScrollPosition(position, positionOffset,
-                        mScrollState == ViewPager.SCROLL_STATE_DRAGGING);
-            }
-
-            @Override
-            public void onPageSelected(int position) {
-                getTabAt(position).select();
-            }
-        };
-    }
-
-    /**
      * Add a tab to this layout. The tab will be added at the end of the list.
      * If this is the first tab to be added it will become the selected tab.
      *
@@ -400,7 +356,8 @@
     }
 
     /**
-     * Set the {@link android.support.design.widget.TabLayout.OnTabSelectedListener} that will handle switching to and from tabs.
+     * Set the {@link android.support.design.widget.TabLayout.OnTabSelectedListener} that will
+     * handle switching to and from tabs.
      *
      * @param onTabSelectedListener Listener to handle tab selection events
      */
@@ -496,7 +453,7 @@
      * <li>{@link #MODE_SCROLLABLE}: Scrollable tabs display a subset of tabs at any given moment,
      * and can contain longer tab labels and a larger number of tabs. They are best used for
      * browsing contexts in touch interfaces when users don’t need to directly compare the tab
-     * labels. This mode is commonly used with a {@link ViewPager}.</li>
+     * labels. This mode is commonly used with a {@link android.support.v4.view.ViewPager}.</li>
      * </ul>
      *
      * @param mode one of {@link #MODE_FIXED} or {@link #MODE_SCROLLABLE}.
@@ -564,6 +521,55 @@
         setTabTextColors(createColorStateList(normalColor, selectedColor));
     }
 
+    /**
+     * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}.
+     *
+     * <p>This method will:
+     * <ul>
+     *     <li>Add a {@link ViewPager.OnPageChangeListener} that will forward events to
+     *     this TabLayout.</li>
+     *     <li>Populate the TabLayout's tabs from the ViewPager's {@link PagerAdapter}.</li>
+     *     <li>Set our {@link TabLayout.OnTabSelectedListener} which will forward
+     *     selected events to the ViewPager</li>
+     * </ul>
+     * </p>
+     *
+     * @see #setTabsFromPagerAdapter(PagerAdapter)
+     * @see TabLayoutOnPageChangeListener
+     * @see ViewPagerOnTabSelectedListener
+     */
+    public void setupWithViewPager(ViewPager viewPager) {
+        final PagerAdapter adapter = viewPager.getAdapter();
+        if (adapter == null) {
+            throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set");
+        }
+
+        // First we'll add Tabs, using the adapter's page titles
+        setTabsFromPagerAdapter(adapter);
+
+        // Now we'll add our page change listener to the ViewPager
+        viewPager.addOnPageChangeListener(new TabLayoutOnPageChangeListener(this));
+
+        // Now we'll add a tab selected listener to set ViewPager's current item
+        setOnTabSelectedListener(new ViewPagerOnTabSelectedListener(viewPager));
+    }
+
+    /**
+     * Populate our tab content from the given {@link PagerAdapter}.
+     * <p>
+     * Any existing tabs will be removed first. Each tab will have it's text set to the value
+     * returned from {@link PagerAdapter#getPageTitle(int)}
+     * </p>
+     *
+     * @param adapter the adapter to populate from
+     */
+    public void setTabsFromPagerAdapter(PagerAdapter adapter) {
+        removeAllTabs();
+        for (int i = 0, count = adapter.getCount(); i < count; i++) {
+            addTab(newTab().setText(adapter.getPageTitle(i)));
+        }
+    }
+
     private void updateAllTabs() {
         for (int i = 0, z = mTabStrip.getChildCount(); i < z; i++) {
             updateTab(i);
@@ -1420,4 +1426,75 @@
         }
     }
 
+    /**
+     * A {@link ViewPager.OnPageChangeListener} class which contains the
+     * necessary calls back to the provided {@link TabLayout} so that the tab position is
+     * kept in sync.
+     *
+     * <p>This class stores the provided TabLayout weakly, meaning that you can use
+     * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)
+     * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and
+     * not cause a leak.
+     */
+    public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
+        private final WeakReference<TabLayout> mTabLayoutRef;
+        private int mScrollState;
+
+        public TabLayoutOnPageChangeListener(TabLayout tabLayout) {
+            mTabLayoutRef = new WeakReference<>(tabLayout);
+        }
+
+        @Override
+        public void onPageScrollStateChanged(int state) {
+            mScrollState = state;
+        }
+
+        @Override
+        public void onPageScrolled(int position, float positionOffset,
+                int positionOffsetPixels) {
+            final TabLayout tabLayout = mTabLayoutRef.get();
+            if (tabLayout != null) {
+                // Update the scroll position, only update the text selection if we're being
+                // dragged
+                tabLayout.setScrollPosition(position, positionOffset,
+                        mScrollState == ViewPager.SCROLL_STATE_DRAGGING);
+            }
+        }
+
+        @Override
+        public void onPageSelected(int position) {
+            final TabLayout tabLayout = mTabLayoutRef.get();
+            if (tabLayout != null) {
+                tabLayout.getTabAt(position).select();
+            }
+        }
+    }
+
+    /**
+     * A {@link TabLayout.OnTabSelectedListener} class which contains the necessary calls back
+     * to the provided {@link ViewPager} so that the tab position is kept in sync.
+     */
+    public static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener {
+        private final ViewPager mViewPager;
+
+        public ViewPagerOnTabSelectedListener(ViewPager viewPager) {
+            mViewPager = viewPager;
+        }
+
+        @Override
+        public void onTabSelected(TabLayout.Tab tab) {
+            mViewPager.setCurrentItem(tab.getPosition());
+        }
+
+        @Override
+        public void onTabUnselected(TabLayout.Tab tab) {
+            // No-op
+        }
+
+        @Override
+        public void onTabReselected(TabLayout.Tab tab) {
+            // No-op
+        }
+    }
+
 }