Bug 1319254 - Move Highlights title into separate item to make animation better r?sebastian draft
authorAndrzej Hunt <ahunt@mozilla.com>
Mon, 12 Dec 2016 15:09:28 -0800
changeset 448795 ea97f01af0b573a39bbec2ba35b06f3ba63f7d9f
parent 448794 b3246aa7bbe4e2e12d7e8fe702848fac485d4020
child 539372 0bf50dd1c0fd2c8bde9ecbf3305d1cf407b66ea9
push id38432
push userahunt@mozilla.com
push dateMon, 12 Dec 2016 23:53:27 +0000
reviewerssebastian
bugs1319254
milestone53.0a1
Bug 1319254 - Move Highlights title into separate item to make animation better r?sebastian This results in the highlights title smoothly animating upwards with the remaining RecyclerView items. Previously RV would crossfade between a panel containing both the welcome message AND the highlights title, which means the Highlights title would vanish and reappear. This patch results in a more correct and pleasing animation. We also upgrade to using a ViewStub for the welcome panel as part of this commit. MozReview-Commit-ID: GYxrSiqKeS5
mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
mobile/android/base/resources/layout/activity_stream_main_highlightstitle.xml
mobile/android/base/resources/layout/activity_stream_main_welcomepanel.xml
mobile/android/base/resources/layout/activity_stream_main_welcomepanel_content.xml
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
@@ -10,16 +10,17 @@ import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Color;
 import android.support.v4.view.ViewPager;
 import android.support.v7.widget.RecyclerView;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewStub;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.activitystream.ActivityStream.LabelCallback;
 import org.mozilla.gecko.db.BrowserContract;
@@ -39,39 +40,47 @@ import java.util.concurrent.Future;
 
 import static org.mozilla.gecko.activitystream.ActivityStream.extractLabel;
 
 public abstract class StreamItem extends RecyclerView.ViewHolder {
     public StreamItem(View itemView) {
         super(itemView);
     }
 
-    public static class HighlightsHeaderPanel
+    public static class HighlightsTitle extends StreamItem {
+        public static final int LAYOUT_ID = R.layout.activity_stream_main_highlightstitle;
+
+        public HighlightsTitle(final View itemView) {
+            super(itemView);
+        }
+    }
+
+    public static class WelcomePanel
             extends StreamItem
             implements View.OnClickListener {
-        public static final int LAYOUT_ID = R.layout.activity_stream_main_highlightstitle;
+        public static final int LAYOUT_ID = R.layout.activity_stream_main_welcomepanel;
 
         public static final String PREF_WELCOME_DISMISSED = "activitystream.welcome_dismissed";
 
         private final RecyclerView.Adapter<StreamItem> adapter;
         private final Context context;
 
-        public HighlightsHeaderPanel(final View itemView, final RecyclerView.Adapter<StreamItem> adapter) {
+        public WelcomePanel(final View itemView, final RecyclerView.Adapter<StreamItem> adapter) {
             super(itemView);
 
             this.adapter = adapter;
             this.context = itemView.getContext();
 
-            final View wrapper = itemView.findViewById(R.id.welcome_panel);
-
             final SharedPreferences sharedPrefs = GeckoSharedPrefs.forApp(itemView.getContext());
 
-            if (sharedPrefs.getBoolean(PREF_WELCOME_DISMISSED, false)) {
-                wrapper.setVisibility(View.GONE);
-            } else {
+            if (!sharedPrefs.getBoolean(PREF_WELCOME_DISMISSED, false)) {
+                final ViewStub welcomePanelStub = (ViewStub) itemView.findViewById(R.id.welcomepanel_stub);
+
+                welcomePanelStub.inflate();
+
                 final Button dismissButton = (Button) itemView.findViewById(R.id.dismiss_welcomepanel);
 
                 dismissButton.setOnClickListener(this);
             }
         }
 
         @Override
         public void onClick(View v) {
@@ -80,17 +89,17 @@ public abstract class StreamItem extends
             // animating between those two versions. Hence we just need to make sure that
             // any future calls to onCreateViewHolder create a version of the Header Item
             // with the welcome panel hidden (i.e. we don't need to care about animations ourselves).
             // We communicate this state change via the pref.
 
             final SharedPreferences sharedPrefs = GeckoSharedPrefs.forApp(context);
 
             sharedPrefs.edit()
-                    .putBoolean(StreamItem.HighlightsHeaderPanel.PREF_WELCOME_DISMISSED, true)
+                    .putBoolean(WelcomePanel.PREF_WELCOME_DISMISSED, true)
                     .apply();
 
             adapter.notifyItemChanged(getAdapterPosition());
         }
     }
 
     public static class TopPanel extends StreamItem {
         public static final int LAYOUT_ID = R.layout.activity_stream_main_toppanel;
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
@@ -11,17 +11,18 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.home.HomePager;
 import org.mozilla.gecko.home.activitystream.StreamItem.HighlightItem;
-import org.mozilla.gecko.home.activitystream.StreamItem.HighlightsHeaderPanel;
+import org.mozilla.gecko.home.activitystream.StreamItem.WelcomePanel;
+import org.mozilla.gecko.home.activitystream.StreamItem.HighlightsTitle;
 import org.mozilla.gecko.home.activitystream.StreamItem.TopPanel;
 import org.mozilla.gecko.widget.RecyclerViewClickSupport;
 
 import java.util.EnumSet;
 
 public class StreamRecyclerAdapter extends RecyclerView.Adapter<StreamItem> implements RecyclerViewClickSupport.OnItemClickListener {
     private Cursor highlightsCursor;
     private Cursor topSitesCursor;
@@ -50,46 +51,50 @@ public class StreamRecyclerAdapter exten
         notifyDataSetChanged();
     }
 
     @Override
     public int getItemViewType(int position) {
         if (position == 0) {
             return TopPanel.LAYOUT_ID;
         } else if (position == 1) {
-            return HighlightsHeaderPanel.LAYOUT_ID;
+            return WelcomePanel.LAYOUT_ID;
+        } else if (position == 2) {
+            return HighlightsTitle.LAYOUT_ID;
         } else if (position < getItemCount()) {
             return HighlightItem.LAYOUT_ID;
         } else {
             throw new IllegalArgumentException("Requested position does not exist");
         }
     }
 
     @Override
     public StreamItem onCreateViewHolder(ViewGroup parent, final int type) {
         final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
 
         if (type == TopPanel.LAYOUT_ID) {
             return new TopPanel(inflater.inflate(type, parent, false), onUrlOpenListener, onUrlOpenInBackgroundListener);
-        } else if (type == HighlightsHeaderPanel.LAYOUT_ID) {
-            return new HighlightsHeaderPanel(inflater.inflate(type, parent, false), this);
+        } else if (type == WelcomePanel.LAYOUT_ID) {
+            return new WelcomePanel(inflater.inflate(type, parent, false), this);
         } else if (type == HighlightItem.LAYOUT_ID) {
             return new HighlightItem(inflater.inflate(type, parent, false), onUrlOpenListener, onUrlOpenInBackgroundListener);
+        } else if (type == HighlightsTitle.LAYOUT_ID) {
+            return new HighlightsTitle(inflater.inflate(type, parent, false));
         } else {
             throw new IllegalStateException("Missing inflation for ViewType " + type);
         }
     }
 
     private int translatePositionToCursor(int position) {
         if (getItemViewType(position) != HighlightItem.LAYOUT_ID) {
             throw new IllegalArgumentException("Requested cursor position for invalid item");
         }
 
-        // We have two blank panels at the top, hence remove that to obtain the cursor position
-        return position - 2;
+        // We have three blank panels at the top, hence remove that to obtain the cursor position
+        return position - 3;
     }
 
     @Override
     public void onBindViewHolder(StreamItem holder, int position) {
         int type = getItemViewType(position);
 
         if (type == HighlightItem.LAYOUT_ID) {
             final int cursorPosition = translatePositionToCursor(position);
@@ -124,17 +129,17 @@ public class StreamRecyclerAdapter exten
         final int highlightsCount;
 
         if (highlightsCursor != null) {
             highlightsCount = highlightsCursor.getCount();
         } else {
             highlightsCount = 0;
         }
 
-        return highlightsCount + 2;
+        return highlightsCount + 3;
     }
 
     public void swapHighlightsCursor(Cursor cursor) {
         highlightsCursor = cursor;
 
         notifyDataSetChanged();
     }
 
@@ -154,18 +159,20 @@ public class StreamRecyclerAdapter exten
         // - multiply bookmark ID's by -1 to not clash with history, and add an offset to not
         //   clash with the fixed panels above
         final int offset = -10;
 
         // RecyclerView.NO_ID is -1, so start our hard-coded IDs at -2.
         switch (type) {
             case TopPanel.LAYOUT_ID:
                 return -2;
+            case WelcomePanel.LAYOUT_ID:
+                return -3;
             case HighlightsTitle.LAYOUT_ID:
-                return -3;
+                return -4;
             case HighlightItem.LAYOUT_ID:
                 final int cursorPosition = translatePositionToCursor(position);
                 highlightsCursor.moveToPosition(cursorPosition);
 
                 final long historyID = highlightsCursor.getLong(highlightsCursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID));
                 final boolean isHistory = -1 != historyID;
 
                 if (isHistory) {
--- a/mobile/android/base/resources/layout/activity_stream_main_highlightstitle.xml
+++ b/mobile/android/base/resources/layout/activity_stream_main_highlightstitle.xml
@@ -1,78 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:orientation="vertical"
               android:layout_width="match_parent"
               android:layout_height="wrap_content">
 
-    <RelativeLayout
-        android:id="@+id/welcome_panel"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-
-        <View
-            android:id="@+id/divider0"
-            android:layout_width="match_parent"
-            android:layout_height="0.5dp"
-            android:padding="4dp"
-            android:background="#ffe0e0e0" />
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/foxfinder"
-            android:adjustViewBounds="true"
-            android:scaleType="fitXY"
-            android:id="@+id/welcome_fox"
-            android:layout_marginTop="@dimen/activity_stream_base_margin"
-            android:layout_marginBottom="@dimen/activity_stream_base_margin"
-            android:layout_marginLeft="-30dp"
-            android:layout_alignParentRight="true" />
-
-        <TextView
-            android:id="@+id/title_welcome"
-            android:layout_marginLeft="@dimen/activity_stream_base_margin"
-            android:layout_marginStart="@dimen/activity_stream_base_margin"
-            android:layout_marginTop="@dimen/activity_stream_base_margin"
-            android:layout_marginBottom="@dimen/activity_stream_base_margin"
-            android:layout_marginRight="@dimen/activity_stream_base_margin"
-            android:layout_marginEnd="@dimen/activity_stream_base_margin"
-            android:text="@string/activity_stream_welcome_title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textStyle="bold"
-            android:textSize="16sp"
-            android:textColor="#FF858585" />
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/activity_stream_welcome_content"
-            android:textColor="#FF858585"
-            android:layout_below="@+id/title_welcome"
-            android:layout_alignLeft="@+id/title_welcome"
-            android:layout_alignStart="@+id/title_welcome"
-            android:layout_toLeftOf="@+id/welcome_fox"
-            android:layout_toStartOf="@+id/welcome_fox"
-            android:id="@+id/welcome_text"/>
-
-        <Button
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/activity_stream_dismiss"
-            android:theme="@style/ActivityStreamButton"
-            android:elevation="1dp"
-            android:id="@+id/dismiss_welcomepanel"
-            android:layout_alignBottom="@+id/welcome_fox"
-            android:layout_alignLeft="@+id/title_welcome"
-            android:layout_alignStart="@+id/title_welcome"/>
-
-    </RelativeLayout>
-
     <View
         android:id="@+id/divider1"
         android:layout_width="match_parent"
         android:layout_height="0.5dp"
         android:padding="4dp"
         android:background="#ffe0e0e0" />
 
     <TextView
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/activity_stream_main_welcomepanel.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Viewstub requires a parent (since inflation involves removing the stub and replacing it with
+     the new layout, hence we need this empty framelayout (we don't attach the welcomepanel
+     to it's enclosing RecyclerView until after inflation - otherwise this issue wouldn't exist).
+     In this case, we need a FrameLayout for animations to work correctly, it would originally have lived
+     within welcomepanel_content, but this placing it here is just as good. -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <ViewStub android:id="@+id/welcomepanel_stub"
+              android:layout="@layout/activity_stream_main_welcomepanel_content"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"/>
+</FrameLayout>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/activity_stream_main_welcomepanel_content.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- We need an enclosing layout for the animation calculations to work correctly. This is provided
+     in the enclosing layout which wraps the ViewStub in a FrameLayout. -->
+<RelativeLayout
+    android:id="@+id/welcome_panel"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <View
+        android:id="@+id/divider0"
+        android:layout_width="match_parent"
+        android:layout_height="0.5dp"
+        android:background="#ffe0e0e0"
+        android:padding="4dp"/>
+
+    <ImageView
+        android:id="@+id/welcome_fox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_marginBottom="@dimen/activity_stream_base_margin"
+        android:layout_marginLeft="-30dp"
+        android:layout_marginTop="@dimen/activity_stream_base_margin"
+        android:adjustViewBounds="true"
+        android:scaleType="fitXY"
+        android:src="@drawable/foxfinder"/>
+
+    <TextView
+        android:id="@+id/title_welcome"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/activity_stream_base_margin"
+        android:layout_marginEnd="@dimen/activity_stream_base_margin"
+        android:layout_marginLeft="@dimen/activity_stream_base_margin"
+        android:layout_marginRight="@dimen/activity_stream_base_margin"
+        android:layout_marginStart="@dimen/activity_stream_base_margin"
+        android:layout_marginTop="@dimen/activity_stream_base_margin"
+        android:text="@string/activity_stream_welcome_title"
+        android:textColor="#FF858585"
+        android:textSize="16sp"
+        android:textStyle="bold"/>
+
+    <TextView
+        android:id="@+id/welcome_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignLeft="@+id/title_welcome"
+        android:layout_alignStart="@+id/title_welcome"
+        android:layout_below="@+id/title_welcome"
+        android:layout_toLeftOf="@+id/welcome_fox"
+        android:layout_toStartOf="@+id/welcome_fox"
+        android:text="@string/activity_stream_welcome_content"
+        android:textColor="#FF858585"/>
+
+    <Button
+        android:id="@+id/dismiss_welcomepanel"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBottom="@+id/welcome_fox"
+        android:layout_alignLeft="@+id/title_welcome"
+        android:layout_alignStart="@+id/title_welcome"
+        android:elevation="1dp"
+        android:text="@string/activity_stream_dismiss"
+        android:theme="@style/ActivityStreamButton"/>
+
+</RelativeLayout>