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
--- 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>