Index: chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java |
index e2b074a2d2f9a38bbbe412a1bdbaf4973062b053..c43625bee6ceef310c98a4c7dc07972b36a73952 100644 |
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java |
@@ -16,7 +16,6 @@ |
import android.net.Uri; |
import android.support.v4.graphics.drawable.RoundedBitmapDrawable; |
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; |
-import android.support.v7.widget.LinearLayoutManager; |
import android.support.v7.widget.RecyclerView; |
import android.text.Editable; |
import android.text.TextUtils; |
@@ -34,10 +33,8 @@ |
import org.chromium.base.Log; |
import org.chromium.base.VisibleForTesting; |
-import org.chromium.base.metrics.RecordHistogram; |
import org.chromium.base.metrics.RecordUserAction; |
import org.chromium.chrome.R; |
-import org.chromium.chrome.browser.ChromeFeatureList; |
import org.chromium.chrome.browser.favicon.FaviconHelper.FaviconImageCallback; |
import org.chromium.chrome.browser.favicon.FaviconHelper.IconAvailabilityCallback; |
import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback; |
@@ -45,7 +42,9 @@ |
import org.chromium.chrome.browser.ntp.LogoBridge.LogoObserver; |
import org.chromium.chrome.browser.ntp.MostVisitedItem.MostVisitedItemManager; |
import org.chromium.chrome.browser.ntp.NewTabPage.OnSearchBoxScrollListener; |
-import org.chromium.chrome.browser.ntp.snippets.SnippetsManager; |
+import org.chromium.chrome.browser.ntp.cards.NewTabPageAdapter; |
+import org.chromium.chrome.browser.ntp.cards.NewTabPageRecyclerView; |
+import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge.SnippetsObserver; |
import org.chromium.chrome.browser.profiles.MostVisitedSites.MostVisitedURLsObserver; |
import org.chromium.chrome.browser.profiles.MostVisitedSites.ThumbnailCallback; |
import org.chromium.chrome.browser.util.ViewUtils; |
@@ -62,10 +61,20 @@ |
private static final int SHADOW_COLOR = 0x11000000; |
private static final long SNAP_SCROLL_DELAY_MS = 30; |
- private static final String TAG = "NewTabPageView"; |
+ private static final String TAG = "Ntp"; |
- private ViewGroup mContentView; |
- private NewTabScrollView mScrollView; |
+ /** |
+ * Indicates which UI mode we are using. Should be checked when manipulating some members, as |
+ * they may be unused or {@code null} depending on the mode. |
+ */ |
+ private boolean mUseCardsUi; |
+ |
+ // Note: Only one of these will be valid at a time, depending on if we are using the old NTP |
+ // (NewTabPageScrollView) or the new NTP with cards (NewTabPageRecyclerView). |
+ private NewTabPageScrollView mScrollView; |
+ private NewTabPageRecyclerView mRecyclerView; |
+ |
+ private NewTabPageLayout mContentView; |
private LogoView mSearchProviderLogoView; |
private View mSearchBoxView; |
private TextView mSearchBoxTextView; |
@@ -73,7 +82,9 @@ |
private MostVisitedLayout mMostVisitedLayout; |
private View mMostVisitedPlaceholder; |
private View mNoSearchLogoSpacer; |
- private RecyclerView mSnippetsView; |
+ |
+ /** Adapter for {@link #mRecyclerView}. Will be {@code null} when using the old UI */ |
+ private NewTabPageAdapter mNtpAdapter; |
private OnSearchBoxScrollListener mSearchBoxScrollListener; |
@@ -142,6 +153,9 @@ |
*/ |
void setMostVisitedURLsObserver(MostVisitedURLsObserver observer, int numResults); |
+ /** Sets the observer that will be notified of new snippets. */ |
+ void setSnippetsObserver(SnippetsObserver observer); |
+ |
/** |
* Gets a cached thumbnail of a URL. |
* @param url The URL whose thumbnail is being retrieved. |
@@ -234,22 +248,41 @@ public NewTabPageView(Context context, AttributeSet attrs) { |
* with the page. |
* @param isSingleUrlBarMode Whether the NTP is in single URL bar mode. |
* @param searchProviderHasLogo Whether the search provider has a logo. |
+ * @param useCardsUi Whether to use the new cards based UI or the old one. |
*/ |
public void initialize(NewTabPageManager manager, boolean isSingleUrlBarMode, |
- boolean searchProviderHasLogo, SnippetsManager snippetsManager) { |
+ boolean searchProviderHasLogo, boolean useCardsUi) { |
mManager = manager; |
+ ViewStub stub = (ViewStub) findViewById(R.id.new_tab_page_layout_stub); |
+ |
+ mUseCardsUi = useCardsUi; |
+ if (mUseCardsUi) { |
+ stub.setLayoutResource(R.layout.new_tab_page_recycler_view); |
+ mRecyclerView = (NewTabPageRecyclerView) stub.inflate(); |
+ |
+ // Don't attach now, the recyclerView itself will determine when to do it. |
+ mContentView = (NewTabPageLayout) LayoutInflater.from(getContext()) |
+ .inflate(R.layout.new_tab_page_layout, mRecyclerView, false); |
- mScrollView = (NewTabScrollView) findViewById(R.id.ntp_scrollview); |
- mScrollView.enableBottomShadow(SHADOW_COLOR); |
- mContentView = (ViewGroup) findViewById(R.id.ntp_content); |
+ // Tailor the LayoutParams for the snippets UI, as the configuration in the XML is |
+ // made for the ScrollView UI. |
+ ViewGroup.LayoutParams params = mContentView.getLayoutParams(); |
+ params.height = ViewGroup.LayoutParams.WRAP_CONTENT; |
+ } else { |
+ stub.setLayoutResource(R.layout.new_tab_page_scroll_view); |
+ mScrollView = (NewTabPageScrollView) stub.inflate(); |
+ mScrollView.enableBottomShadow(SHADOW_COLOR); |
+ mContentView = (NewTabPageLayout) findViewById(R.id.ntp_content); |
+ } |
mMostVisitedDesign = new MostVisitedDesign(getContext()); |
- mMostVisitedLayout = (MostVisitedLayout) findViewById(R.id.most_visited_layout); |
+ mMostVisitedLayout = |
+ (MostVisitedLayout) mContentView.findViewById(R.id.most_visited_layout); |
mMostVisitedDesign.initMostVisitedLayout(mMostVisitedLayout, searchProviderHasLogo); |
- mSearchProviderLogoView = (LogoView) findViewById(R.id.search_provider_logo); |
- mSearchBoxView = findViewById(R.id.search_box); |
- mNoSearchLogoSpacer = findViewById(R.id.no_search_logo_spacer); |
+ mSearchProviderLogoView = (LogoView) mContentView.findViewById(R.id.search_provider_logo); |
+ mSearchBoxView = mContentView.findViewById(R.id.search_box); |
+ mNoSearchLogoSpacer = mContentView.findViewById(R.id.no_search_logo_spacer); |
mSearchBoxTextView = (TextView) mSearchBoxView.findViewById(R.id.search_box_text); |
String hintText = getResources().getString(R.string.search_or_type_url); |
@@ -281,7 +314,7 @@ public void afterTextChanged(Editable s) { |
} |
}); |
- mVoiceSearchButton = (ImageView) findViewById(R.id.voice_search_button); |
+ mVoiceSearchButton = (ImageView) mContentView.findViewById(R.id.voice_search_button); |
mVoiceSearchButton.setOnClickListener(new View.OnClickListener() { |
@Override |
public void onClick(View v) { |
@@ -317,13 +350,16 @@ public void onClick(View v) { |
} |
} else { |
((ViewGroup) toolbar.getParent()).removeView(toolbar); |
- FrameLayout.LayoutParams params = |
- (FrameLayout.LayoutParams) mScrollView.getLayoutParams(); |
- params.bottomMargin = 0; |
- mScrollView.setLayoutParams(params); |
+ if (!mUseCardsUi) { |
+ // Only remove if we're using the old NTP view, the new one does not use a |
+ // ScrollView |
+ FrameLayout.LayoutParams params = |
+ (FrameLayout.LayoutParams) mScrollView.getLayoutParams(); |
+ params.bottomMargin = 0; |
+ mScrollView.setLayoutParams(params); |
+ } |
} |
- initializeSearchBoxScrollHandling(); |
addOnLayoutChangeListener(this); |
setSearchProviderHasLogo(searchProviderHasLogo); |
@@ -332,13 +368,12 @@ public void onClick(View v) { |
mMostVisitedDesign.getNumberOfTiles(searchProviderHasLogo)); |
// Set up snippets |
- if (ChromeFeatureList.isEnabled(ChromeFeatureList.NTP_SNIPPETS)) { |
- mSnippetsView = (RecyclerView) findViewById(R.id.snippets_card_list); |
- mSnippetsView.setVisibility(View.VISIBLE); |
- RecordHistogram.recordEnumeratedHistogram(SnippetsManager.SNIPPETS_STATE_HISTOGRAM, |
- SnippetsManager.SNIPPETS_SHOWN, SnippetsManager.NUM_SNIPPETS_ACTIONS); |
- mSnippetsView.setLayoutManager(new LinearLayoutManager(getContext())); |
- mSnippetsView.addOnScrollListener(new RecyclerView.OnScrollListener() { |
+ if (mUseCardsUi) { |
+ mNtpAdapter = new NewTabPageAdapter(mManager, mContentView); |
+ mRecyclerView.setAdapter(mNtpAdapter); |
+ |
+ NewTabPageUma.recordSnippetAction(NewTabPageUma.SNIPPETS_ACTION_SHOWN); |
+ mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { |
private boolean mScrolledOnce = false; |
@Override |
public void onScrollStateChanged(RecyclerView recyclerView, int newState) { |
@@ -346,13 +381,12 @@ public void onScrollStateChanged(RecyclerView recyclerView, int newState) { |
RecordUserAction.record("MobileNTP.Snippets.Scrolled"); |
if (mScrolledOnce) return; |
mScrolledOnce = true; |
- RecordHistogram.recordEnumeratedHistogram( |
- SnippetsManager.SNIPPETS_STATE_HISTOGRAM, |
- SnippetsManager.SNIPPETS_SCROLLED, |
- SnippetsManager.NUM_SNIPPETS_ACTIONS); |
+ NewTabPageUma.recordSnippetAction(NewTabPageUma.SNIPPETS_ACTION_SCROLLED); |
} |
}); |
- snippetsManager.setSnippetsView(mSnippetsView); |
+ initializeSearchBoxRecyclerViewScrollHandling(); |
+ } else { |
+ initializeSearchBoxScrollHandling(); |
} |
} |
@@ -362,10 +396,10 @@ private void updateSearchBoxOnScroll() { |
float percentage = 0; |
// During startup the view may not be fully initialized, so we only calculate the current |
// percentage if some basic view properties are sane. |
- if (mScrollView.getHeight() != 0 && mSearchBoxView.getTop() != 0) { |
- int scrollY = mScrollView.getScrollY(); |
- percentage = Math.max( |
- 0f, Math.min(1f, scrollY / (float) mSearchBoxView.getTop())); |
+ View wrapperView = mUseCardsUi ? mRecyclerView : mScrollView; |
+ if (wrapperView.getHeight() != 0 && mSearchBoxView.getTop() != 0) { |
+ int scrollY = getVerticalScroll(); |
+ percentage = Math.max(0f, Math.min(1f, scrollY / (float) mSearchBoxView.getTop())); |
} |
updateVisualsForToolbarTransition(percentage); |
@@ -375,6 +409,15 @@ private void updateSearchBoxOnScroll() { |
} |
} |
+ private void initializeSearchBoxRecyclerViewScrollHandling() { |
+ mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { |
+ @Override |
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { |
+ updateSearchBoxOnScroll(); |
+ } |
+ }); |
+ } |
+ |
private void initializeSearchBoxScrollHandling() { |
final Runnable mSnapScrollRunnable = new Runnable() { |
@Override |
@@ -388,7 +431,7 @@ public void run() { |
mPendingSnapScroll = false; |
} |
}; |
- mScrollView.setOnScrollListener(new NewTabScrollView.OnScrollListener() { |
+ mScrollView.setOnScrollListener(new NewTabPageScrollView.OnScrollListener() { |
@Override |
public void onScrollChanged(int l, int t, int oldl, int oldt) { |
if (mPendingSnapScroll) { |
@@ -548,7 +591,7 @@ float getUrlFocusChangeAnimationPercent() { |
*/ |
private void setUrlFocusChangeAnimationPercentInternal(float percent) { |
mContentView.setTranslationY(percent * (-mMostVisitedLayout.getTop() |
- + mScrollView.getScrollY() + mContentView.getPaddingTop())); |
+ + getVerticalScroll() + mContentView.getPaddingTop())); |
updateVisualsForToolbarTransition(percent); |
} |
@@ -586,7 +629,12 @@ void getSearchBoxBounds(Rect originalBounds, Rect transformedBounds) { |
transformedBounds.set(originalBounds); |
View view = (View) mSearchBoxView.getParent(); |
while (view != null) { |
- transformedBounds.offset(-view.getScrollX(), -view.getScrollY()); |
+ if (view instanceof RecyclerView) { |
+ transformedBounds.offset(-((RecyclerView) view).computeHorizontalScrollOffset(), |
+ -((RecyclerView) view).computeVerticalScrollOffset()); |
+ } else { |
+ transformedBounds.offset(-view.getScrollX(), -view.getScrollY()); |
+ } |
if (view == this) break; |
transformedBounds.offset((int) view.getX(), (int) view.getY()); |
view = (View) view.getParent(); |
@@ -640,23 +688,6 @@ protected void onWindowVisibilityChanged(int visibility) { |
} |
} |
- @Override |
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
- super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
- |
- // Make the search box and logo the same width as the most visited tiles. |
- if (mMostVisitedLayout.getVisibility() != GONE) { |
- int mostVisitedWidth = MeasureSpec.makeMeasureSpec(mMostVisitedLayout.getMeasuredWidth() |
- - mMostVisitedDesign.getMostVisitedLayoutBleed(), MeasureSpec.EXACTLY); |
- int searchBoxHeight = MeasureSpec.makeMeasureSpec( |
- mSearchBoxView.getMeasuredHeight(), MeasureSpec.EXACTLY); |
- int logoHeight = MeasureSpec.makeMeasureSpec( |
- mSearchProviderLogoView.getMeasuredHeight(), MeasureSpec.EXACTLY); |
- mSearchBoxView.measure(mostVisitedWidth, searchBoxHeight); |
- mSearchProviderLogoView.measure(mostVisitedWidth, logoHeight); |
- } |
- } |
- |
/** |
* @see org.chromium.chrome.browser.compositor.layouts.content. |
* InvalidationAwareThumbnailProvider#shouldCaptureThumbnail() |
@@ -664,10 +695,8 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
boolean shouldCaptureThumbnail() { |
if (getWidth() == 0 || getHeight() == 0) return false; |
- return mSnapshotMostVisitedChanged |
- || getWidth() != mSnapshotWidth |
- || getHeight() != mSnapshotHeight |
- || mScrollView.getScrollY() != mSnapshotScrollY; |
+ return mSnapshotMostVisitedChanged || getWidth() != mSnapshotWidth |
+ || getHeight() != mSnapshotHeight || getVerticalScroll() != mSnapshotScrollY; |
} |
/** |
@@ -679,7 +708,7 @@ void captureThumbnail(Canvas canvas) { |
ViewUtils.captureBitmap(this, canvas); |
mSnapshotWidth = getWidth(); |
mSnapshotHeight = getHeight(); |
- mSnapshotScrollY = mScrollView.getScrollY(); |
+ mSnapshotScrollY = getVerticalScroll(); |
mSnapshotMostVisitedChanged = false; |
} |
@@ -825,15 +854,12 @@ private void updateMostVisitedPlaceholderVisibility() { |
private static final int ICON_BACKGROUND_COLOR = 0xff787878; |
private static final int ICON_MIN_SIZE_PX = 48; |
- private int mMostVisitedLayoutBleed; |
private int mMinIconSize; |
private int mDesiredIconSize; |
private RoundedIconGenerator mIconGenerator; |
MostVisitedDesign(Context context) { |
Resources res = context.getResources(); |
- mMostVisitedLayoutBleed = res.getDimensionPixelSize( |
- R.dimen.most_visited_layout_bleed); |
mDesiredIconSize = res.getDimensionPixelSize(R.dimen.most_visited_icon_size); |
// On ldpi devices, mDesiredIconSize could be even smaller than ICON_MIN_SIZE_PX. |
mMinIconSize = Math.min(mDesiredIconSize, ICON_MIN_SIZE_PX); |
@@ -848,10 +874,6 @@ public int getNumberOfTiles(boolean searchProviderHasLogo) { |
return searchProviderHasLogo ? NUM_TILES : NUM_TILES_NO_LOGO; |
} |
- public int getMostVisitedLayoutBleed() { |
- return mMostVisitedLayoutBleed; |
- } |
- |
public void initMostVisitedLayout(MostVisitedLayout mostVisitedLayout, |
boolean searchProviderHasLogo) { |
mostVisitedLayout.setMaxRows(searchProviderHasLogo ? MAX_ROWS : MAX_ROWS_NO_LOGO); |
@@ -942,4 +964,12 @@ public void onIconUpdated(final String url) { |
} |
} |
} |
+ |
+ private int getVerticalScroll() { |
+ if (mUseCardsUi) { |
+ return mRecyclerView.computeVerticalScrollOffset(); |
+ } else { |
+ return mScrollView.getScrollY(); |
+ } |
+ } |
} |