Bug 763049: Tablet animation should slide on phones, slide and shrink on tablets. [r=mfinkle]
authorSriram Ramasubramanian <sriram@mozilla.com>
Tue, 12 Jun 2012 22:47:52 -0700
changeset 96554 764e33d3648f3fbdb637349731b8850a983321a5
parent 96553 70b9974c9b4f438f39fd59c90b8a5207c59054f4
child 96555 d4a216c7534aee8d02bad829e754e58d07a5afa9
push id22911
push useremorley@mozilla.com
push dateWed, 13 Jun 2012 12:49:30 +0000
treeherdermozilla-central@efbb6480e98e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs763049
milestone16.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 763049: Tablet animation should slide on phones, slide and shrink on tablets. [r=mfinkle]
mobile/android/base/BrowserApp.java
mobile/android/base/BrowserToolbar.java
mobile/android/base/GeckoApp.java
mobile/android/base/PropertyAnimator.java
mobile/android/base/TabsPanel.java
mobile/android/base/resources/layout-sw600dp/gecko_app.xml
mobile/android/base/resources/layout-xlarge/gecko_app.xml
mobile/android/base/resources/layout/gecko_app.xml
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -56,22 +56,26 @@ import android.util.*;
 import android.net.*;
 import android.database.*;
 import android.database.sqlite.*;
 import android.provider.*;
 import android.content.pm.*;
 import android.content.pm.PackageManager.*;
 import dalvik.system.*;
 
-abstract public class BrowserApp extends GeckoApp {
+abstract public class BrowserApp extends GeckoApp
+                                 implements TabsPanel.TabsLayoutChangeListener,
+                                            PropertyAnimator.PropertyAnimationListener {
     private static final String LOGTAG = "GeckoBrowserApp";
 
     public static BrowserToolbar mBrowserToolbar;
     private AboutHomeContent mAboutHomeContent;
 
+    private PropertyAnimator mMainLayoutAnimator;
+
     @Override
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
         switch(msg) {
             case LOCATION_CHANGE:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     String url = tab.getURL();
                     if (url.equals("about:home"))
                         showAboutHome();
@@ -193,16 +197,19 @@ abstract public class BrowserApp extends
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         LinearLayout actionBar = (LinearLayout) findViewById(R.id.browser_toolbar);
 
         mBrowserToolbar = new BrowserToolbar(mAppContext);
         mBrowserToolbar.from(actionBar);
 
+        if (mTabsPanel != null)
+            mTabsPanel.setTabsLayoutChangeListener(this);
+
         if (savedInstanceState != null) {
             mBrowserToolbar.setTitle(savedInstanceState.getString(SAVED_STATE_TITLE));
         }
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
@@ -317,16 +324,67 @@ abstract public class BrowserApp extends
         mTabsPanel.hide();
         mBrowserToolbar.updateTabs(false);
     }
 
     public boolean areTabsShown() {
         return mTabsPanel.isShown();
     }
 
+    @Override
+    public void onTabsLayoutChange(int width, int height) {
+        if (mMainLayoutAnimator != null)
+            mMainLayoutAnimator.stop();
+
+        mMainLayoutAnimator = new PropertyAnimator(150);
+        mMainLayoutAnimator.setPropertyAnimationListener(this);
+
+        if (isTablet()) {
+            mMainLayoutAnimator.attach(mBrowserToolbar.getLayout(),
+                                       PropertyAnimator.Property.SHRINK_LEFT,
+                                       width);
+
+            // Set the gecko layout for sliding.
+            if (!mTabsPanel.isShown()) {
+                ((LinearLayout.LayoutParams) mGeckoLayout.getLayoutParams()).setMargins(0, 0, 0, 0);
+                mGeckoLayout.scrollTo(mTabsPanel.getWidth() * -1, 0);
+                mGeckoLayout.requestLayout();
+            }
+
+            mMainLayoutAnimator.attach(mGeckoLayout,
+                                       PropertyAnimator.Property.SLIDE_LEFT,
+                                       width);
+
+        } else {
+            mMainLayoutAnimator.attach(mMainLayout,
+                                       PropertyAnimator.Property.SLIDE_TOP,
+                                       height);
+        }
+
+        mMainLayoutAnimator.start();
+    }
+
+    @Override
+    public void onPropertyAnimationStart() {
+    }
+
+    @Override
+    public void onPropertyAnimationEnd() {
+        mMainHandler.post(new Runnable() {
+            public void run() {
+                if (isTablet() && mTabsPanel.isShown()) {
+                    // Fake the gecko layout to have been shrunk, instead of sliding.
+                    ((LinearLayout.LayoutParams) mGeckoLayout.getLayoutParams()).setMargins(mTabsPanel.getWidth(), 0, 0, 0);
+                    mGeckoLayout.scrollTo(0, 0);
+                    mGeckoLayout.requestLayout();
+                }
+            }
+        });
+    }
+
     /* Doorhanger notification methods */
     @Override
     void updatePopups(final Tab tab) {
         mDoorHangerPopup.updatePopup(mBrowserToolbar.mFavicon);
     }
 
     @Override
     void addDoorHanger(String message, String value, JSONArray buttons, Tab tab, JSONObject options) {
--- a/mobile/android/base/BrowserToolbar.java
+++ b/mobile/android/base/BrowserToolbar.java
@@ -259,16 +259,20 @@ public class BrowserToolbar implements V
                 if (mHasSoftMenuButton) {
                     mMenuPopup = new MenuPopup(mContext);
                     mMenuPopup.setPanelView(panel);
                 }
             }
         }
     }
 
+    public View getLayout() {
+        return mLayout;
+    }
+
     public void requestLayout() {
         mLayout.invalidate();
     }
 
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
         switch(msg) {
             case TITLE:
                 // if (sameDocument)
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -60,18 +60,17 @@ import android.provider.*;
 import android.content.pm.*;
 import android.content.pm.PackageManager.*;
 import dalvik.system.*;
 
 abstract public class GeckoApp
                 extends GeckoActivity 
                 implements GeckoEventListener, SensorEventListener, LocationListener,
                            GeckoApplication.ApplicationLifecycleCallbacks,
-                           Tabs.OnTabsChangedListener,
-                           TabsPanel.TabsLayoutChangeListener
+                           Tabs.OnTabsChangedListener
 {
     private static final String LOGTAG = "GeckoApp";
 
     public static enum StartupMode {
         NORMAL,
         NEW_VERSION,
         NEW_PROFILE
     }
@@ -82,18 +81,18 @@ abstract public class GeckoApp
     public static final String ACTION_DEBUG         = "org.mozilla.gecko.DEBUG";
     public static final String ACTION_BOOKMARK      = "org.mozilla.gecko.BOOKMARK";
     public static final String ACTION_LOAD          = "org.mozilla.gecko.LOAD";
     public static final String ACTION_UPDATE        = "org.mozilla.gecko.UPDATE";
     public static final String ACTION_INIT_PW       = "org.mozilla.gecko.INIT_PW";
     public static final String SAVED_STATE_TITLE    = "title";
 
     StartupMode mStartupMode = null;
-    private LinearLayout mMainLayout;
-    private RelativeLayout mGeckoLayout;
+    protected LinearLayout mMainLayout;
+    protected RelativeLayout mGeckoLayout;
     public View getView() { return mGeckoLayout; }
     public static SurfaceView cameraView;
     public static GeckoApp mAppContext;
     public static boolean mDOMFullScreen = false;
     protected MenuPanel mMenuPanel;
     public static Menu sMenu;
     private static GeckoThread sGeckoThread = null;
     public Handler mMainHandler;
@@ -109,18 +108,16 @@ abstract public class GeckoApp
     public TabsPanel mTabsPanel;
     public Favicons mFavicons;
 
     private static LayerController mLayerController;
     private static GeckoLayerClient mLayerClient;
     private static AbsoluteLayout mPluginContainer;
     private static FindInPageBar mFindInPageBar;
 
-    private PropertyAnimator mMainLayoutAnimator;
-
     private FullScreenHolder mFullScreenPluginContainer;
     private View mFullScreenPluginView;
 
     private HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
 
     protected int mRestoreMode = GeckoAppShell.RESTORE_NONE;
     private boolean mInitialized = false;
 
@@ -918,38 +915,16 @@ abstract public class GeckoApp
     public void showRemoteTabs() { }
 
     private void showTabs(TabsPanel.Panel panel) { }
 
     public void hideTabs() { }
 
     public boolean areTabsShown() { return false; }
 
-    @Override
-    public void onTabsLayoutChange(int width, int height) {
-        if (mMainLayoutAnimator != null)
-            mMainLayoutAnimator.stop();
-
-        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mMainLayout.getLayoutParams();
-
-        if (isTablet())
-            mMainLayoutAnimator = new PropertyAnimator(mMainLayout,
-                                                       PropertyAnimator.Property.MARGIN_LEFT,
-                                                       params.leftMargin,
-                                                       width,
-                                                       200);
-        else
-            mMainLayoutAnimator = new PropertyAnimator(mMainLayout,
-                                                       PropertyAnimator.Property.MARGIN_TOP,
-                                                       params.topMargin,
-                                                       height,
-                                                       200);
-        mMainLayoutAnimator.start();
-    }
-
     public void handleMessage(String event, JSONObject message) {
         Log.i(LOGTAG, "Got message: " + event);
         try {
             if (event.equals("Menu:Add")) {
                 final String label = message.getString("name");
                 final int id = message.getInt("id");
                 String iconRes = null;
                 try { // icon is optional
@@ -1699,18 +1674,16 @@ abstract public class GeckoApp
         setContentView(R.layout.gecko_app);
 
         // setup gecko layout
         mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
         mMainLayout = (LinearLayout) findViewById(R.id.main_layout);
 
         // setup tabs panel
         mTabsPanel = (TabsPanel) findViewById(R.id.tabs_panel);
-        if (mTabsPanel != null)
-            mTabsPanel.setTabsLayoutChangeListener(this);
 
         if (savedInstanceState != null) {
             mRestoreMode = GeckoAppShell.RESTORE_OOM;
         }
 
         ((GeckoApplication) getApplication()).addApplicationLifecycleCallbacks(this);
     }
 
--- a/mobile/android/base/PropertyAnimator.java
+++ b/mobile/android/base/PropertyAnimator.java
@@ -1,113 +1,156 @@
 /* -*- 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;
 
+import java.util.List;
+import java.util.ArrayList;
 import java.util.Timer;
 import java.util.TimerTask;
 
 import android.os.Handler;
 import android.view.animation.Interpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.view.View;
-import android.widget.RelativeLayout;
+import android.view.ViewGroup;
 
 public class PropertyAnimator extends TimerTask {
     private static final String LOGTAG = "GeckoPropertyAnimator";
 
     private Timer mTimer;
-    private TimerTask mShowTask;
     private Interpolator mInterpolator;
 
     public static enum Property {
-        MARGIN_LEFT,
-        MARGIN_RIGHT,
-        MARGIN_TOP,
-        MARGIN_BOTTOM
+        SHRINK_LEFT,
+        SHRINK_TOP,
+        SLIDE_TOP,
+        SLIDE_LEFT
     }
 
-    private View mView;
-    private Property mProperty;
-    private int mDuration;
-    private int mFrom;
-    private int mTo;
+    private class ElementHolder {
+        View view;
+        Property property;
+        int from;
+        int to;
+    }
+
+    public static interface PropertyAnimationListener {
+        public void onPropertyAnimationStart();
+        public void onPropertyAnimationEnd();
+    }
 
     private int mCount;
+    private int mDuration;
+    private List<ElementHolder> mElementsList;
+    private PropertyAnimationListener mListener;
 
     // Default refresh rate in ms.
-    private static final int sInterval = 10;
+    private static final int INTERVAL = 10;
 
-    public PropertyAnimator(View view, Property property, int from, int to, int duration) {
-        mView = view;
-        mProperty = property;
+    public PropertyAnimator(int duration) {
         mDuration = duration;
-        mFrom = from;
-        mTo = to;
-
         mTimer = new Timer();
         mInterpolator = new DecelerateInterpolator();
+        mElementsList = new ArrayList<ElementHolder>();
+    }
+
+    public void attach(View view, Property property, int to) {
+        ElementHolder element = new ElementHolder();
+
+        element.view = view;
+        element.property = property;
+
+        // Sliding should happen in the negative.
+        if (property == Property.SLIDE_TOP || property == Property.SLIDE_LEFT)
+            element.to = to * -1;
+        else
+            element.to = to;
+
+        mElementsList.add(element);
+    }
+
+    public void setPropertyAnimationListener(PropertyAnimationListener listener) {
+        mListener = listener;
     }
 
     @Override
     public void run() {
-        float interpolation = mInterpolator.getInterpolation((float) (mCount * sInterval) / (float) mDuration);
-        int delta;
-        if (mFrom < mTo)
-            delta = mFrom + (int) ((mTo - mFrom) * interpolation);
-        else
-            delta = mFrom - (int) ((mFrom - mTo) * interpolation);
+        float interpolation = mInterpolator.getInterpolation((float) (mCount * INTERVAL) / (float) mDuration);
 
-        invalidate(delta);
+        for (ElementHolder element : mElementsList) { 
+            int delta = element.from + (int) ((element.to - element.from) * interpolation);
+            invalidate(element, delta);
+        }
 
         mCount++;
 
-        if (mCount * sInterval >= mDuration)
+        if (mCount * INTERVAL >= mDuration)
             stop();
     }
 
     public void start() {
         mCount = 0;
-        mTimer.scheduleAtFixedRate(this, 0, sInterval);
+
+        // Fix the from value based on current position and property
+        for (ElementHolder element : mElementsList) {
+            ViewGroup.MarginLayoutParams params = ((ViewGroup.MarginLayoutParams) element.view.getLayoutParams());
+ 
+            if (element.property == Property.SHRINK_TOP)
+                element.from = params.topMargin;
+            else if (element.property == Property.SHRINK_LEFT)
+                element.from = params.leftMargin;
+            else if (element.property == Property.SLIDE_TOP)
+                element.from = element.view.getScrollY();
+            else if (element.property == Property.SLIDE_LEFT)
+                element.from = element.view.getScrollX();
+        }
+
+        if (mDuration != 0) {
+            mTimer.scheduleAtFixedRate(this, 0, INTERVAL);
+
+            if (mListener != null)
+                mListener.onPropertyAnimationStart();
+        }
     }
 
     public void stop() {
         cancel();
         mTimer.cancel();
         mTimer.purge();
 
         // Make sure to snap to the end position.
-        invalidate(mTo);
+        for (ElementHolder element : mElementsList) { 
+            invalidate(element, element.to);
+        }
+
+        mElementsList.clear();
+
+        if (mListener != null) {
+            mListener.onPropertyAnimationEnd();
+            mListener = null;
+        }
     }
 
-    private void invalidate(final int delta) {
+    private void invalidate(final ElementHolder element, final int delta) {
         // Post the layout changes on the view's UI thread.
-        mView.getHandler().post(new Runnable() {
+        element.view.getHandler().post(new Runnable() {
             @Override
             public void run() {
-                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(mView.getLayoutParams());
-                switch(mProperty) {
-                    case MARGIN_TOP:
-                        params.setMargins(0, delta, 0, 0);
-                        break;
-                 
-                    case MARGIN_BOTTOM:
-                        params.setMargins(0, 0, 0, delta);
-                        break;
-                 
-                    case MARGIN_LEFT:
-                        params.setMargins(delta, 0, 0, 0);
-                        break;
-                 
-                    case MARGIN_RIGHT:
-                        params.setMargins(0, 0, delta, 0);
-                        break;
-                }
-                    
-                mView.setLayoutParams(params);
-                mView.requestLayout();
+                ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) element.view.getLayoutParams();
+ 
+                if (element.property == Property.SHRINK_TOP)
+                    params.setMargins(params.leftMargin, delta, params.rightMargin, params.bottomMargin);
+                else if (element.property == Property.SHRINK_LEFT)
+                    params.setMargins(delta, params.topMargin, params.rightMargin, params.bottomMargin);
+                else if (element.property == Property.SLIDE_TOP)
+                    element.view.scrollTo(element.view.getScrollX(), delta);
+                else if (element.property == Property.SLIDE_LEFT)
+                    element.view.scrollTo(delta, element.view.getScrollY());
+ 
+                element.view.requestLayout();
             }
         });
     }
 }
--- a/mobile/android/base/TabsPanel.java
+++ b/mobile/android/base/TabsPanel.java
@@ -152,18 +152,20 @@ public class TabsPanel extends LinearLay
                         mRemoteTabs.setVisibility(visibility);
                     }
                 });
             }
         }.execute(context);
     }
 
     public void hide() {
-        mVisible = false;
-        dispatchLayoutChange(0, 0);
+        if (mVisible) {
+            mVisible = false;
+            dispatchLayoutChange(0, 0);
+        }
     }
 
     public void refresh() {
         if (mVisible) {
             mListContainer.requestLayout();
             show(mCurrentPanel);
         }
     }
--- a/mobile/android/base/resources/layout-sw600dp/gecko_app.xml
+++ b/mobile/android/base/resources/layout-sw600dp/gecko_app.xml
@@ -8,17 +8,17 @@
                 android:layout_height="fill_parent">
 
    <org.mozilla.gecko.TabsPanel android:id="@+id/tabs_panel"
                                 android:layout_width="200dip"
                                 android:layout_height="fill_parent"
                                 android:background="@drawable/tabs_tray_bg_repeat"/>
 
    <LinearLayout android:id="@+id/main_layout"
-                 style="@style/Screen">
+                 style="@style/Screen.Transparent">
  
         <include layout="@layout/browser_toolbar"/>
 
         <RelativeLayout android:id="@+id/gecko_layout"
                         android:layout_width="fill_parent"
                         android:layout_height="fill_parent"
                         android:layout_weight="1">
 
--- a/mobile/android/base/resources/layout-xlarge/gecko_app.xml
+++ b/mobile/android/base/resources/layout-xlarge/gecko_app.xml
@@ -8,17 +8,17 @@
                 android:layout_height="fill_parent">
 
    <org.mozilla.gecko.TabsPanel android:id="@+id/tabs_panel"
                                 android:layout_width="200dip"
                                 android:layout_height="fill_parent"
                                 android:background="@drawable/tabs_tray_bg_repeat"/>
 
    <LinearLayout android:id="@+id/main_layout"
-                 style="@style/Screen">
+                 style="@style/Screen.Transparent">
  
         <include layout="@layout/browser_toolbar"/>
 
         <RelativeLayout android:id="@+id/gecko_layout"
                         android:layout_width="fill_parent"
                         android:layout_height="fill_parent"
                         android:layout_weight="1">
 
--- a/mobile/android/base/resources/layout/gecko_app.xml
+++ b/mobile/android/base/resources/layout/gecko_app.xml
@@ -8,17 +8,17 @@
                 android:layout_height="fill_parent">
 
    <org.mozilla.gecko.TabsPanel android:id="@+id/tabs_panel"
                                 android:layout_width="fill_parent"
                                 android:layout_height="fill_parent"
                                 android:background="@drawable/tabs_tray_bg_repeat"/>
 
    <LinearLayout android:id="@+id/main_layout"
-                 style="@style/Screen">
+                 style="@style/Screen.Transparent">
  
         <include layout="@layout/browser_toolbar"/>
 
         <RelativeLayout android:id="@+id/gecko_layout"
                         android:layout_width="fill_parent"
                         android:layout_height="fill_parent"
                         android:layout_weight="1">