diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2017-06-26 22:11:26 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2017-06-26 22:11:27 +0000 |
commit | b647302a90c26f5d0c707be105d478602aa956f7 (patch) | |
tree | 0f1e630efdc74c50eb3e064bb9ecbdc9a7c4a38e /src/com/android | |
parent | f729eb529142b78aa03732a71f39e3a8c10d6188 (diff) | |
parent | 89d5c5a31bd6cf4caf815b680ec670896b91803d (diff) | |
download | android_packages_apps_Trebuchet-b647302a90c26f5d0c707be105d478602aa956f7.tar.gz android_packages_apps_Trebuchet-b647302a90c26f5d0c707be105d478602aa956f7.tar.bz2 android_packages_apps_Trebuchet-b647302a90c26f5d0c707be105d478602aa956f7.zip |
Merge "Updating fast scrollbar UI in Landscape" into ub-launcher3-dorval-polish
Diffstat (limited to 'src/com/android')
-rw-r--r-- | src/com/android/launcher3/BaseRecyclerView.java | 116 | ||||
-rw-r--r-- | src/com/android/launcher3/allapps/AllAppsContainerView.java | 12 | ||||
-rw-r--r-- | src/com/android/launcher3/allapps/AllAppsGridAdapter.java | 14 | ||||
-rw-r--r-- | src/com/android/launcher3/allapps/AllAppsRecyclerView.java | 10 | ||||
-rw-r--r-- | src/com/android/launcher3/allapps/AlphabeticalAppsList.java | 18 | ||||
-rw-r--r-- | src/com/android/launcher3/allapps/LandscapeFastScroller.java | 63 | ||||
-rw-r--r-- | src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java | 12 | ||||
-rw-r--r-- | src/com/android/launcher3/allapps/search/HeaderElevationController.java | 32 | ||||
-rw-r--r-- | src/com/android/launcher3/views/RecyclerViewFastScroller.java (renamed from src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java) | 204 |
9 files changed, 253 insertions, 228 deletions
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index 84358ea37..3ee6e51b8 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -22,8 +22,9 @@ import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.ViewGroup; +import android.widget.TextView; -import com.android.launcher3.util.Thunk; +import com.android.launcher3.views.RecyclerViewFastScroller; /** @@ -36,19 +37,7 @@ import com.android.launcher3.util.Thunk; public abstract class BaseRecyclerView extends RecyclerView implements RecyclerView.OnItemTouchListener { - private static final int SCROLL_DELTA_THRESHOLD_DP = 4; - - /** Keeps the last known scrolling delta/velocity along y-axis. */ - @Thunk int mDy = 0; - private float mDeltaThreshold; - - protected final BaseRecyclerViewFastScrollBar mScrollbar; - - private int mDownX; - private int mDownY; - private int mLastY; - - private boolean mScrollBarVisible = true; + protected RecyclerViewFastScroller mScrollbar; public BaseRecyclerView(Context context) { this(context, null); @@ -60,28 +49,6 @@ public abstract class BaseRecyclerView extends RecyclerView public BaseRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP; - mScrollbar = new BaseRecyclerViewFastScrollBar(this, getResources()); - - ScrollListener listener = new ScrollListener(); - setOnScrollListener(listener); - } - - private class ScrollListener extends OnScrollListener { - public ScrollListener() { - // Do nothing - } - - @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { - mDy = dy; - - // TODO(winsonc): If we want to animate the section heads while scrolling, we can - // initiate that here if the recycler view scroll state is not - // RecyclerView.SCROLL_STATE_IDLE. - - onUpdateScrollbar(dy); - } } @Override @@ -93,7 +60,9 @@ public abstract class BaseRecyclerView extends RecyclerView @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - mScrollbar.setPopupView(((ViewGroup) getParent()).findViewById(R.id.fast_scroller_popup)); + ViewGroup parent = (ViewGroup) getParent(); + mScrollbar = parent.findViewById(R.id.fast_scroller); + mScrollbar.setRecyclerView(this, (TextView) parent.findViewById(R.id.fast_scroller_popup)); } /** @@ -115,32 +84,15 @@ public abstract class BaseRecyclerView extends RecyclerView * it is already showing). */ private boolean handleTouchEvent(MotionEvent ev) { - ev.offsetLocation(0, -getPaddingTop()); - int action = ev.getAction(); - int x = (int) ev.getX(); - int y = (int) ev.getY(); - switch (action) { - case MotionEvent.ACTION_DOWN: - // Keep track of the down positions - mDownX = x; - mDownY = mLastY = y; - if (shouldStopScroll(ev)) { - stopScroll(); - } - mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY); - break; - case MotionEvent.ACTION_MOVE: - mLastY = y; - mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - onFastScrollCompleted(); - mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY); - break; + // Move to mScrollbar's coordinate system. + int left = getLeft() - mScrollbar.getLeft(); + int top = getTop() - mScrollbar.getTop(); + ev.offsetLocation(left, top); + try { + return mScrollbar.handleTouchEvent(ev); + } finally { + ev.offsetLocation(-left, -top); } - ev.offsetLocation(0, getPaddingTop()); - return mScrollbar.isDraggingThumb(); } public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { @@ -148,24 +100,9 @@ public abstract class BaseRecyclerView extends RecyclerView } /** - * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped. - */ - protected boolean shouldStopScroll(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - if ((Math.abs(mDy) < mDeltaThreshold && - getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) { - // now the touch events are being passed to the {@link WidgetCell} until the - // touch sequence goes over the touch slop. - return true; - } - } - return false; - } - - /** * Returns the height of the fast scroll bar */ - protected int getScrollbarTrackHeight() { + public int getScrollbarTrackHeight() { return getHeight() - getPaddingTop() - getPaddingBottom(); } @@ -187,25 +124,14 @@ public abstract class BaseRecyclerView extends RecyclerView /** * Returns the scrollbar for this recycler view. */ - public BaseRecyclerViewFastScrollBar getScrollBar() { + public RecyclerViewFastScroller getScrollBar() { return mScrollbar; } @Override protected void dispatchDraw(Canvas canvas) { + onUpdateScrollbar(0); super.dispatchDraw(canvas); - if (mScrollBarVisible) { - onUpdateScrollbar(0); - mScrollbar.draw(canvas); - } - } - - /** - * Sets the scrollbar visibility. The call does not refresh the UI, its the responsibility - * of the caller to call {@link #invalidate()}. - */ - public void setScrollBarVisible(boolean visible) { - mScrollBarVisible = visible; } /** @@ -236,7 +162,7 @@ public abstract class BaseRecyclerView extends RecyclerView /** * @return whether fast scrolling is supported in the current state. */ - protected boolean supportsFastScrolling() { + public boolean supportsFastScrolling() { return true; } @@ -252,16 +178,16 @@ public abstract class BaseRecyclerView extends RecyclerView * Maps the touch (from 0..1) to the adapter position that should be visible. * <p>Override in each subclass of this base class. */ - protected abstract String scrollToPositionAtProgress(float touchFraction); + public abstract String scrollToPositionAtProgress(float touchFraction); /** * Updates the bounds for the scrollbar. * <p>Override in each subclass of this base class. */ - protected abstract void onUpdateScrollbar(int dy); + public abstract void onUpdateScrollbar(int dy); /** * <p>Override in each subclass of this base class. */ - protected void onFastScrollCompleted() {} + public void onFastScrollCompleted() {} }
\ No newline at end of file diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 4954e0c44..47b68a2ee 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -171,19 +171,19 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc * Returns whether the view itself will handle the touch event or not. */ public boolean shouldContainerScroll(MotionEvent ev) { - int[] point = new int[2]; - point[0] = (int) ev.getX(); - point[1] = (int) ev.getY(); - Utilities.mapCoordInSelfToDescendant(mAppsRecyclerView, this, point); - // IF the MotionEvent is inside the search box, and the container keeps on receiving // touch input, container should move down. if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) { return true; } + int[] point = new int[2]; + point[0] = (int) ev.getX(); + point[1] = (int) ev.getY(); + Utilities.mapCoordInSelfToDescendant( + mAppsRecyclerView.getScrollBar(), mLauncher.getDragLayer(), point); // IF the MotionEvent is inside the thumb, container should not be pulled down. - if (mAppsRecyclerView.getScrollBar().isNearThumb(point[0], point[1])) { + if (mAppsRecyclerView.getScrollBar().shouldBlockIntercept(point[0], point[1])) { return false; } diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index d6514a83f..1054a5633 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -69,16 +69,13 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. // A divider that separates the apps list and the search market button public static final int VIEW_TYPE_SEARCH_MARKET_DIVIDER = 1 << 5; - // The divider under the search field - public static final int VIEW_TYPE_SEARCH_DIVIDER = 1 << 6; // The divider that separates prediction icons from the app list - public static final int VIEW_TYPE_PREDICTION_DIVIDER = 1 << 7; - public static final int VIEW_TYPE_APPS_LOADING_DIVIDER = 1 << 8; - public static final int VIEW_TYPE_DISCOVERY_ITEM = 1 << 9; + public static final int VIEW_TYPE_PREDICTION_DIVIDER = 1 << 6; + public static final int VIEW_TYPE_APPS_LOADING_DIVIDER = 1 << 7; + public static final int VIEW_TYPE_DISCOVERY_ITEM = 1 << 8; // Common view type masks - public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_SEARCH_DIVIDER - | VIEW_TYPE_SEARCH_MARKET_DIVIDER + public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_SEARCH_MARKET_DIVIDER | VIEW_TYPE_PREDICTION_DIVIDER; public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON | VIEW_TYPE_PREDICTION_ICON; @@ -319,9 +316,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. } }); return new ViewHolder(searchMarketView); - case VIEW_TYPE_SEARCH_DIVIDER: - return new ViewHolder(mLayoutInflater.inflate( - R.layout.all_apps_search_divider, parent, false)); case VIEW_TYPE_APPS_LOADING_DIVIDER: View loadingDividerView = mLayoutInflater.inflate( R.layout.all_apps_discovery_loading_divider, parent, false); diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java index 0607a1e5d..2b2fddcdd 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -72,7 +72,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView { super(context, attrs, defStyleAttr); Resources res = getResources(); addOnItemTouchListener(this); - mScrollbar.setDetachThumbOnFastScroll(); mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize( R.dimen.all_apps_empty_search_bg_top_offset); } @@ -110,7 +109,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView { RecyclerView.RecycledViewPool pool = getRecycledViewPool(); int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx); pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1); - pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER, 1); pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER, 1); pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET, 1); pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows * mNumAppsPerRow); @@ -137,8 +135,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView { AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER, AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER); putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec, - AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER); - putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec, AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET); putSameHeightFor(adapter, widthMeasureSpec, heightMeasureSpec, AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH); @@ -164,7 +160,9 @@ public class AllAppsRecyclerView extends BaseRecyclerView { */ public void scrollToTop() { // Ensure we reattach the scrollbar if it was previously detached while fast-scrolling - mScrollbar.reattachThumbToScroll(); + if (mScrollbar != null) { + mScrollbar.reattachThumbToScroll(); + } scrollToPosition(0); } @@ -356,7 +354,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView { } @Override - protected boolean supportsFastScrolling() { + public boolean supportsFastScrolling() { // Only allow fast scrolling when the user is not searching, since the results are not // grouped in a meaningful order return !mApps.hasFilter(); diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 7bf66510a..608e898ae 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -138,13 +138,6 @@ public class AlphabeticalAppsList { return item; } - public static AdapterItem asSearchDivider(int pos) { - AdapterItem item = new AdapterItem(); - item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_DIVIDER; - item.position = pos; - return item; - } - public static AdapterItem asMarketDivider(int pos) { AdapterItem item = new AdapterItem(); item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER; @@ -195,8 +188,6 @@ public class AlphabeticalAppsList { private int mNumPredictedAppsPerRow; private int mNumAppRowsInAdapter; - private boolean mHasSearchDivider = true; - public AlphabeticalAppsList(Context context) { mLauncher = Launcher.getLauncher(context); mIndexer = new AlphabeticIndexCompat(context); @@ -352,10 +343,6 @@ public class AlphabeticalAppsList { onAppsUpdated(); } - public void disableSearchDivider() { - mHasSearchDivider = false; - } - /** * Updates internals when the set of apps are updated. */ @@ -442,11 +429,6 @@ public class AlphabeticalAppsList { } } - if (mHasSearchDivider) { - // Add the search divider - mAdapterItems.add(AdapterItem.asSearchDivider(position++)); - } - // Process the predicted app components mPredictedApps.clear(); if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) { diff --git a/src/com/android/launcher3/allapps/LandscapeFastScroller.java b/src/com/android/launcher3/allapps/LandscapeFastScroller.java new file mode 100644 index 000000000..cdde65760 --- /dev/null +++ b/src/com/android/launcher3/allapps/LandscapeFastScroller.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.allapps; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import com.android.launcher3.views.RecyclerViewFastScroller; + +/** + * Extension of {@link RecyclerViewFastScroller} to be used in landscape layout. + */ +public class LandscapeFastScroller extends RecyclerViewFastScroller { + + public LandscapeFastScroller(Context context) { + super(context); + } + + public LandscapeFastScroller(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LandscapeFastScroller(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public boolean handleTouchEvent(MotionEvent ev) { + // We handle our own touch event, no need to handle recycler view touch delegates. + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + event.offsetLocation(0, -mRv.getPaddingTop()); + if (super.handleTouchEvent(event)) { + getParent().requestDisallowInterceptTouchEvent(true); + } + event.offsetLocation(0, mRv.getPaddingTop()); + return true; + } + + @Override + public boolean shouldBlockIntercept(int x, int y) { + // If the user touched the scroll bar area, block swipe + return x >= 0 && x < getWidth() && y >= 0 && y < getHeight(); + } +} diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java index 3f06ec9dd..5cb12d592 100644 --- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java +++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java @@ -54,12 +54,13 @@ public class AppsSearchContainerLayout extends FrameLayout private final int mSearchBoxHeight; private final AllAppsSearchBarController mSearchBarController; private final SpannableStringBuilder mSearchQueryBuilder; - private final HeaderElevationController mElevationController; private ExtendedEditText mSearchInput; private AlphabeticalAppsList mApps; private AllAppsRecyclerView mAppsRecyclerView; private AllAppsGridAdapter mAdapter; + private View mDivider; + private HeaderElevationController mElevationController; public AppsSearchContainerLayout(Context context) { this(context, null); @@ -77,7 +78,6 @@ public class AppsSearchContainerLayout extends FrameLayout mSearchBoxHeight = getResources() .getDimensionPixelSize(R.dimen.all_apps_search_bar_field_height); mSearchBarController = new AllAppsSearchBarController(); - mElevationController = new HeaderElevationController(this); mSearchQueryBuilder = new SpannableStringBuilder(); Selection.setSelection(mSearchQueryBuilder, 0); @@ -87,6 +87,8 @@ public class AppsSearchContainerLayout extends FrameLayout protected void onFinishInflate() { super.onFinishInflate(); mSearchInput = findViewById(R.id.search_box_input); + mDivider = findViewById(R.id.search_divider); + mElevationController = new HeaderElevationController(mDivider); // Update the hint to contain the icon. // Prefix the original hint with two spaces. The first space gets replaced by the icon @@ -96,6 +98,12 @@ public class AppsSearchContainerLayout extends FrameLayout spanned.setSpan(new TintedDrawableSpan(getContext(), R.drawable.ic_allapps_search), 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); mSearchInput.setHint(spanned); + + DeviceProfile dp = mLauncher.getDeviceProfile(); + if (!dp.isVerticalBarLayout()) { + LayoutParams lp = (LayoutParams) mDivider.getLayoutParams(); + lp.leftMargin = lp.rightMargin = dp.edgeMarginPx; + } } @Override diff --git a/src/com/android/launcher3/allapps/search/HeaderElevationController.java b/src/com/android/launcher3/allapps/search/HeaderElevationController.java index ab4e88fc8..7cd32b26e 100644 --- a/src/com/android/launcher3/allapps/search/HeaderElevationController.java +++ b/src/com/android/launcher3/allapps/search/HeaderElevationController.java @@ -4,11 +4,11 @@ import android.content.res.Resources; import android.graphics.Outline; import android.support.v7.widget.RecyclerView; import android.view.View; +import android.view.ViewGroup; import android.view.ViewOutlineProvider; import com.android.launcher3.BaseRecyclerView; import com.android.launcher3.R; -import com.android.launcher3.Utilities; /** * Helper class for controlling the header elevation in response to RecyclerView scroll. @@ -16,6 +16,7 @@ import com.android.launcher3.Utilities; public class HeaderElevationController extends RecyclerView.OnScrollListener { private final View mHeader; + private final View mHeaderChild; private final float mMaxElevation; private final float mScrollToElevation; @@ -28,23 +29,27 @@ public class HeaderElevationController extends RecyclerView.OnScrollListener { mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation); // We need to provide a custom outline so the shadow only appears on the bottom edge. - // The top, left and right edges are all extended out, and the shadow is clipped - // by the parent. + // The top, left and right edges are all extended out to match parent's edge, so that + // the shadow is clipped by the parent. final ViewOutlineProvider vop = new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { - final View parent = (View) mHeader.getParent(); + // Set the left and top to be at the parents edge. Since the coordinates are + // relative to this view, + // (x = -view.getLeft()) for this view => (x = 0) for parent + final int left = -view.getLeft(); + final int top = -view.getTop(); - final int left = parent.getLeft(); // Use the parent to account for offsets - final int top = view.getTop(); - final int right = left + view.getWidth(); - final int bottom = view.getBottom(); - - final int offset = Utilities.pxFromDp(mMaxElevation, res.getDisplayMetrics()); + // Since the view is centered align, the spacing on left and right are same. + // Add same spacing on the right to reach parent's edge. + final int right = view.getWidth() - left; + final int bottom = view.getHeight(); + final int offset = (int) mMaxElevation; outline.setRect(left - offset, top - offset, right + offset, bottom); } }; mHeader.setOutlineProvider(vop); + mHeaderChild = ((ViewGroup) mHeader).getChildAt(0); } public void reset() { @@ -63,6 +68,13 @@ public class HeaderElevationController extends RecyclerView.OnScrollListener { float newElevation = mMaxElevation * elevationPct; if (Float.compare(mHeader.getElevation(), newElevation) != 0) { mHeader.setElevation(newElevation); + + // To simulate a scrolling effect for the header, we translate the header down, and + // its content up by the same amount, so that it gets clipped by the parent, making it + // look like the content was scrolled out of the view. + int shift = Math.min(mHeader.getHeight(), scrollY); + mHeader.setTranslationY(-shift); + mHeaderChild.setTranslationY(shift); } } diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java index 9e8d300ae..7b5bcdbd4 100644 --- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java +++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,21 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.launcher3; + +package com.android.launcher3.views; import android.animation.ObjectAnimator; +import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; import android.util.Property; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.widget.TextView; +import com.android.launcher3.BaseRecyclerView; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.graphics.FastScrollThumbDrawable; import com.android.launcher3.util.Themes; @@ -35,18 +40,20 @@ import com.android.launcher3.util.Themes; /** * The track and scrollbar that shows when you scroll the list. */ -public class BaseRecyclerViewFastScrollBar { +public class RecyclerViewFastScroller extends View { + + private static final int SCROLL_DELTA_THRESHOLD_DP = 4; - private static final Property<BaseRecyclerViewFastScrollBar, Integer> TRACK_WIDTH = - new Property<BaseRecyclerViewFastScrollBar, Integer>(Integer.class, "width") { + private static final Property<RecyclerViewFastScroller, Integer> TRACK_WIDTH = + new Property<RecyclerViewFastScroller, Integer>(Integer.class, "width") { @Override - public Integer get(BaseRecyclerViewFastScrollBar scrollBar) { + public Integer get(RecyclerViewFastScroller scrollBar) { return scrollBar.mWidth; } @Override - public void set(BaseRecyclerViewFastScrollBar scrollBar, Integer value) { + public void set(RecyclerViewFastScroller scrollBar, Integer value) { scrollBar.setTrackWidth(value); } }; @@ -55,93 +62,113 @@ public class BaseRecyclerViewFastScrollBar { private final static int SCROLL_BAR_VIS_DURATION = 150; private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 0.75f; - private final Rect mTmpRect = new Rect(); - private final BaseRecyclerView mRv; - - private final boolean mIsRtl; - - // The inset is the buffer around which a point will still register as a click on the scrollbar - private final int mTouchInset; - private final int mMinWidth; private final int mMaxWidth; private final int mThumbPadding; + /** Keeps the last known scrolling delta/velocity along y-axis. */ + private int mDy = 0; + private final float mDeltaThreshold; + + private final ViewConfiguration mConfig; + // Current width of the track private int mWidth; private ObjectAnimator mWidthAnimator; private final Paint mThumbPaint; - private final int mThumbHeight; + protected final int mThumbHeight; private final Paint mTrackPaint; private float mLastTouchY; private boolean mIsDragging; private boolean mIsThumbDetached; - private boolean mCanThumbDetach; + private final boolean mCanThumbDetach; private boolean mIgnoreDragGesture; // This is the offset from the top of the scrollbar when the user first starts touching. To // prevent jumping, this offset is applied as the user scrolls. - private int mTouchOffsetY; - private int mThumbOffsetY; + protected int mTouchOffsetY; + protected int mThumbOffsetY; // Fast scroller popup private TextView mPopupView; private boolean mPopupVisible; private String mPopupSectionName; - public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) { - mRv = rv; + protected BaseRecyclerView mRv; + + private int mDownX; + private int mDownY; + private int mLastY; + + public RecyclerViewFastScroller(Context context) { + this(context, null); + } + + public RecyclerViewFastScroller(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public RecyclerViewFastScroller(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mTrackPaint = new Paint(); - mTrackPaint.setColor(Themes.getAttrColor(rv.getContext(), android.R.attr.textColorPrimary)); + mTrackPaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary)); mTrackPaint.setAlpha(MAX_TRACK_ALPHA); mThumbPaint = new Paint(); mThumbPaint.setAntiAlias(true); - mThumbPaint.setColor(Themes.getColorAccent(rv.getContext())); + mThumbPaint.setColor(Themes.getColorAccent(context)); mThumbPaint.setStyle(Paint.Style.FILL); + Resources res = getResources(); mWidth = mMinWidth = res.getDimensionPixelSize(R.dimen.fastscroll_track_min_width); mMaxWidth = res.getDimensionPixelSize(R.dimen.fastscroll_track_max_width); mThumbPadding = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_padding); mThumbHeight = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height); - mTouchInset = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_touch_inset); - mIsRtl = Utilities.isRtl(res); - } + mConfig = ViewConfiguration.get(context); + mDeltaThreshold = res.getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP; - public void setPopupView(View popup) { - mPopupView = (TextView) popup; - mPopupView.setBackground(new FastScrollThumbDrawable(mThumbPaint, mIsRtl)); + TypedArray ta = + context.obtainStyledAttributes(attrs, R.styleable.RecyclerViewFastScroller, defStyleAttr, 0); + mCanThumbDetach = ta.getBoolean(R.styleable.RecyclerViewFastScroller_canThumbDetach, false); + ta.recycle(); } - public void setDetachThumbOnFastScroll() { - mCanThumbDetach = true; + public void setRecyclerView(BaseRecyclerView rv, TextView popupView) { + mRv = rv; + mRv.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + mDy = dy; + + // TODO(winsonc): If we want to animate the section heads while scrolling, we can + // initiate that here if the recycler view scroll state is not + // RecyclerView.SCROLL_STATE_IDLE. + + mRv.onUpdateScrollbar(dy); + } + }); + + mPopupView = popupView; + mPopupView.setBackground( + new FastScrollThumbDrawable(mThumbPaint, Utilities.isRtl(getResources()))); } public void reattachThumbToScroll() { mIsThumbDetached = false; } - private int getDrawLeft() { - return mIsRtl ? 0 : (mRv.getWidth() - mMaxWidth); - } - public void setThumbOffsetY(int y) { if (mThumbOffsetY == y) { return; } - - // Invalidate the previous and new thumb area - int drawLeft = getDrawLeft(); - mTmpRect.set(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight); mThumbOffsetY = y; - mTmpRect.union(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight); - mTmpRect.offset(0, mRv.getPaddingTop()); - mRv.invalidate(mTmpRect); + invalidate(); } public int getThumbOffsetY() { @@ -152,12 +179,8 @@ public class BaseRecyclerViewFastScrollBar { if (mWidth == width) { return; } - int left = getDrawLeft(); - int top = mRv.getPaddingTop(); - // Invalidate the whole scroll bar area. - mRv.invalidate(left, top, left + mMaxWidth, top + mRv.getScrollbarTrackHeight()); - mWidth = width; + invalidate(); } public int getThumbHeight() { @@ -176,37 +199,48 @@ public class BaseRecyclerViewFastScrollBar { * Handles the touch event and determines whether to show the fast scroller (or updates it if * it is already showing). */ - public void handleTouchEvent(MotionEvent ev, int downX, int downY, int lastY) { - ViewConfiguration config = ViewConfiguration.get(mRv.getContext()); - - int action = ev.getAction(); + public boolean handleTouchEvent(MotionEvent ev) { + int x = (int) ev.getX(); int y = (int) ev.getY(); - switch (action) { + switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: - if (isNearThumb(downX, downY)) { - mTouchOffsetY = downY - mThumbOffsetY; + // Keep track of the down positions + mDownX = x; + mDownY = mLastY = y; + + if ((Math.abs(mDy) < mDeltaThreshold && + mRv.getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) { + // now the touch events are being passed to the {@link WidgetCell} until the + // touch sequence goes over the touch slop. + mRv.stopScroll(); + } + if (isNearThumb(x, y)) { + mTouchOffsetY = mDownY - mThumbOffsetY; } else if (FeatureFlags.LAUNCHER3_DIRECT_SCROLL && mRv.supportsFastScrolling() - && isNearScrollBar(downX)) { - calcTouchOffsetAndPrepToFastScroll(downY, lastY); - updateFastScrollSectionNameAndThumbOffset(lastY, y); + && isNearScrollBar(mDownX)) { + calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY); + updateFastScrollSectionNameAndThumbOffset(mLastY, y); } break; case MotionEvent.ACTION_MOVE: + mLastY = y; + // Check if we should start scrolling, but ignore this fastscroll gesture if we have // exceeded some fixed movement - mIgnoreDragGesture |= Math.abs(y - downY) > config.getScaledPagingTouchSlop(); + mIgnoreDragGesture |= Math.abs(y - mDownY) > mConfig.getScaledPagingTouchSlop(); if (!mIsDragging && !mIgnoreDragGesture && mRv.supportsFastScrolling() && - isNearThumb(downX, lastY) && - Math.abs(y - downY) > config.getScaledTouchSlop()) { - calcTouchOffsetAndPrepToFastScroll(downY, lastY); + isNearThumb(mDownX, mLastY) && + Math.abs(y - mDownY) > mConfig.getScaledTouchSlop()) { + calcTouchOffsetAndPrepToFastScroll(mDownY, mLastY); } if (mIsDragging) { - updateFastScrollSectionNameAndThumbOffset(lastY, y); + updateFastScrollSectionNameAndThumbOffset(mLastY, y); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: + mRv.onFastScrollCompleted(); mTouchOffsetY = 0; mLastTouchY = 0; mIgnoreDragGesture = false; @@ -217,6 +251,7 @@ public class BaseRecyclerViewFastScrollBar { } break; } + return mIsDragging; } private void calcTouchOffsetAndPrepToFastScroll(int downY, int lastY) { @@ -245,25 +280,25 @@ public class BaseRecyclerViewFastScrollBar { setThumbOffsetY((int) mLastTouchY); } - public void draw(Canvas canvas) { + public void onDraw(Canvas canvas) { if (mThumbOffsetY < 0) { return; } int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); - if (!mIsRtl) { - canvas.translate(mRv.getWidth() - mWidth, 0); - } - canvas.translate(0, mRv.getPaddingTop()); + canvas.translate(getWidth() / 2, mRv.getPaddingTop()); // Draw the track - canvas.drawRoundRect(0, 0, mWidth, mRv.getScrollbarTrackHeight(), + float halfW = mWidth / 2; + canvas.drawRoundRect(-halfW, 0, halfW, mRv.getScrollbarTrackHeight(), mWidth, mWidth, mTrackPaint); - canvas.translate(-mThumbPadding, mThumbOffsetY); + canvas.translate(0, mThumbOffsetY); + halfW += mThumbPadding; float r = mWidth + mThumbPadding + mThumbPadding; - canvas.drawRoundRect(0, 0, r, mThumbHeight, r, r, mThumbPaint); + canvas.drawRoundRect(-halfW, 0, halfW, mThumbHeight, r, r, mThumbPaint); canvas.restoreToCount(saveCount); } + /** * Animates the width of the scrollbar. */ @@ -281,19 +316,25 @@ public class BaseRecyclerViewFastScrollBar { /** * Returns whether the specified point is inside the thumb bounds. */ - public boolean isNearThumb(int x, int y) { - int left = getDrawLeft(); - mTmpRect.set(left, mThumbOffsetY, left + mMaxWidth, mThumbOffsetY + mThumbHeight); - mTmpRect.inset(mTouchInset, mTouchInset); - return mTmpRect.contains(x, y); + private boolean isNearThumb(int x, int y) { + int offset = y - mRv.getPaddingTop() - mThumbOffsetY; + + return x >= 0 && x < getWidth() && offset >= 0 && offset <= mThumbHeight; + } + + /** + * Returns true if AllAppsTransitionController can handle vertical motion + * beginning at this point. + */ + public boolean shouldBlockIntercept(int x, int y) { + return isNearThumb(x, y); } /** * Returns whether the specified x position is near the scroll bar. */ public boolean isNearScrollBar(int x) { - int left = getDrawLeft(); - return x >= left && x <= left + mMaxWidth; + return x >= (getWidth() - mMaxWidth) / 2 && x <= (getWidth() + mMaxWidth) / 2; } private void animatePopupVisibility(boolean visible) { @@ -306,7 +347,8 @@ public class BaseRecyclerViewFastScrollBar { private void updatePopupY(int lastTouchY) { int height = mPopupView.getHeight(); - float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height); + float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height) + + mRv.getPaddingTop(); top = Utilities.boundToRange(top, mMaxWidth, mRv.getScrollbarTrackHeight() - mMaxWidth - height); mPopupView.setTranslationY(top); |