Minor visual tweaks.
- Drawing full divider under predicted apps - Enabling dynamic search bar elevation - Increasing fast scroller pop-up size - Insetting the padding to prevent the scroll bar from overlapping with app titles Bug: 20299865 Change-Id: I48ffde43c2158c23b54cd43fede722da41ccc111
This commit is contained in:
@@ -21,7 +21,6 @@
|
||||
<dimen name="apps_container_inset">18dp</dimen>
|
||||
<dimen name="apps_grid_view_start_margin">0dp</dimen>
|
||||
<dimen name="apps_view_section_text_size">26sp</dimen>
|
||||
<dimen name="apps_view_row_height">72dp</dimen>
|
||||
<dimen name="apps_icon_top_bottom_padding">12dp</dimen>
|
||||
|
||||
<!-- AppsCustomize -->
|
||||
|
||||
@@ -50,15 +50,14 @@
|
||||
<dimen name="apps_container_width">0dp</dimen>
|
||||
<dimen name="apps_container_height">0dp</dimen>
|
||||
<dimen name="apps_container_inset">8dp</dimen>
|
||||
<dimen name="apps_grid_view_start_margin">52dp</dimen>
|
||||
<dimen name="apps_grid_view_start_margin">56dp</dimen>
|
||||
<dimen name="apps_grid_section_y_offset">8dp</dimen>
|
||||
<dimen name="apps_view_row_height">64dp</dimen>
|
||||
<dimen name="apps_view_section_text_size">24sp</dimen>
|
||||
<dimen name="apps_view_fast_scroll_bar_width">6dp</dimen>
|
||||
<dimen name="apps_view_fast_scroll_bar_width">4dp</dimen>
|
||||
<dimen name="apps_view_fast_scroll_bar_min_height">64dp</dimen>
|
||||
<dimen name="apps_view_fast_scroll_scrubber_touch_inset">-16dp</dimen>
|
||||
<dimen name="apps_view_fast_scroll_popup_size">64dp</dimen>
|
||||
<dimen name="apps_view_fast_scroll_text_size">40dp</dimen>
|
||||
<dimen name="apps_view_fast_scroll_popup_size">72dp</dimen>
|
||||
<dimen name="apps_view_fast_scroll_text_size">48dp</dimen>
|
||||
<dimen name="apps_search_bar_height">52dp</dimen>
|
||||
<dimen name="apps_icon_top_bottom_padding">8dp</dimen>
|
||||
|
||||
|
||||
@@ -38,6 +38,23 @@ import java.util.List;
|
||||
*/
|
||||
public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
||||
|
||||
/**
|
||||
* The current scroll state of the recycler view. We use this in updateVerticalScrollbarBounds()
|
||||
* and scrollToPositionAtProgress() to determine the scroll position of the recycler view so
|
||||
* that we can calculate what the scroll bar looks like, and where to jump to from the fast
|
||||
* scroller.
|
||||
*/
|
||||
private static class ScrollPositionState {
|
||||
// The index of the first app in the row (Note that is this not the position)
|
||||
int rowFirstAppIndex;
|
||||
// The index of the first visible row
|
||||
int rowIndex;
|
||||
// The offset of the first visible row
|
||||
int rowTopOffset;
|
||||
// The height of a given row (they are currently all the same height)
|
||||
int rowHeight;
|
||||
}
|
||||
|
||||
private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
|
||||
|
||||
private AlphabeticalAppsList mApps;
|
||||
@@ -58,6 +75,8 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
||||
private int mScrollbarWidth;
|
||||
private int mScrollbarMinHeight;
|
||||
private int mScrollbarInset;
|
||||
private Rect mBackgroundPadding = new Rect();
|
||||
private ScrollPositionState mScrollPosState = new ScrollPositionState();
|
||||
|
||||
public AppsContainerRecyclerView(Context context) {
|
||||
this(context, null);
|
||||
@@ -91,6 +110,7 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
||||
mScrollbarInset =
|
||||
res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_scrubber_touch_inset);
|
||||
setFastScrollerAlpha(getFastScrollerAlpha());
|
||||
setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,6 +127,12 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
||||
mNumAppsPerRow = rowSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackground(Drawable background) {
|
||||
super.setBackground(background);
|
||||
background.getPadding(mBackgroundPadding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fast scroller alpha.
|
||||
*/
|
||||
@@ -129,6 +155,14 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
||||
return mScrollbarWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls this recycler view to the top.
|
||||
*/
|
||||
public void scrollToTop() {
|
||||
scrollToPosition(0);
|
||||
updateScrollY(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
@@ -238,7 +272,7 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
||||
// Calculate the position for the fast scroller popup
|
||||
Rect bgBounds = mFastScrollerBg.getBounds();
|
||||
if (isRtl) {
|
||||
x = getPaddingLeft() + getScrollBarSize();
|
||||
x = mBackgroundPadding.left + getScrollBarSize();
|
||||
} else {
|
||||
x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width();
|
||||
}
|
||||
@@ -281,7 +315,7 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
||||
* Invalidates the fast scroller popup.
|
||||
*/
|
||||
private void invalidateFastScroller() {
|
||||
invalidate(getWidth() - getPaddingRight() - getScrollBarSize() -
|
||||
invalidate(getWidth() - mBackgroundPadding.right - getScrollBarSize() -
|
||||
mFastScrollerBg.getIntrinsicWidth(), 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
@@ -312,6 +346,15 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
||||
stopScroll();
|
||||
layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0);
|
||||
|
||||
// We need to workaround the RecyclerView to get the right scroll position after scrolling
|
||||
List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
|
||||
getCurScrollState(mScrollPosState, items);
|
||||
if (mScrollPosState.rowIndex != -1) {
|
||||
int rowIndex = findRowForAppIndex(mScrollPosState.rowFirstAppIndex);
|
||||
int y = (rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset;
|
||||
updateScrollY(y);
|
||||
}
|
||||
|
||||
return lastScrollSection.sectionName;
|
||||
}
|
||||
|
||||
@@ -332,44 +375,29 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
||||
int y;
|
||||
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
|
||||
LAYOUT_DIRECTION_RTL);
|
||||
int rowIndex = -1;
|
||||
int rowTopOffset = -1;
|
||||
int rowHeight = -1;
|
||||
int rowCount = getNumRows();
|
||||
int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = getChildAt(i);
|
||||
int position = getChildPosition(child);
|
||||
if (position != NO_POSITION) {
|
||||
AlphabeticalAppsList.AdapterItem item = items.get(position);
|
||||
if (!item.isSectionHeader) {
|
||||
rowIndex = findRowForAppIndex(item.appIndex);
|
||||
rowTopOffset = getLayoutManager().getDecoratedTop(child);
|
||||
rowHeight = child.getHeight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
getCurScrollState(mScrollPosState, items);
|
||||
|
||||
if (rowIndex != -1) {
|
||||
if (mScrollPosState.rowIndex != -1) {
|
||||
int height = getHeight() - getPaddingTop() - getPaddingBottom();
|
||||
int totalScrollHeight = rowCount * rowHeight;
|
||||
int totalScrollHeight = rowCount * mScrollPosState.rowHeight;
|
||||
if (totalScrollHeight > height) {
|
||||
int scrollbarHeight = Math.max(mScrollbarMinHeight,
|
||||
(int) (height / ((float) totalScrollHeight / height)));
|
||||
|
||||
// Calculate the position and size of the scroll bar
|
||||
if (isRtl) {
|
||||
x = getPaddingLeft();
|
||||
x = mBackgroundPadding.left;
|
||||
} else {
|
||||
x = getWidth() - getPaddingRight() - mScrollbarWidth;
|
||||
x = getWidth() - mBackgroundPadding.right - mScrollbarWidth;
|
||||
}
|
||||
|
||||
// To calculate the offset, we compute the percentage of the total scrollable height
|
||||
// that the user has already scrolled and then map that to the scroll bar bounds
|
||||
int availableY = totalScrollHeight - height;
|
||||
int availableScrollY = height - scrollbarHeight;
|
||||
y = (rowIndex * rowHeight) - rowTopOffset;
|
||||
y = (mScrollPosState.rowIndex * mScrollPosState.rowHeight) -
|
||||
mScrollPosState.rowTopOffset;
|
||||
y = getPaddingTop() +
|
||||
(int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY);
|
||||
|
||||
@@ -410,4 +438,30 @@ public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
|
||||
}
|
||||
return rowCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current scroll state.
|
||||
*/
|
||||
private void getCurScrollState(ScrollPositionState stateOut,
|
||||
List<AlphabeticalAppsList.AdapterItem> items) {
|
||||
stateOut.rowFirstAppIndex = -1;
|
||||
stateOut.rowIndex = -1;
|
||||
stateOut.rowTopOffset = -1;
|
||||
stateOut.rowHeight = -1;
|
||||
int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = getChildAt(i);
|
||||
int position = getChildPosition(child);
|
||||
if (position != NO_POSITION) {
|
||||
AlphabeticalAppsList.AdapterItem item = items.get(position);
|
||||
if (!item.isSectionHeader) {
|
||||
stateOut.rowFirstAppIndex = item.appIndex;
|
||||
stateOut.rowIndex = findRowForAppIndex(item.appIndex);
|
||||
stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
|
||||
stateOut.rowHeight = child.getHeight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +51,11 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
||||
public static final boolean GRID_HIDE_SECTION_HEADERS = false;
|
||||
|
||||
private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
|
||||
private static final boolean DYNAMIC_HEADER_ELEVATION = false;
|
||||
private static final boolean DYNAMIC_HEADER_ELEVATION = true;
|
||||
private static final boolean DISMISS_SEARCH_ON_BACK = true;
|
||||
private static final float HEADER_ELEVATION_DP = 4;
|
||||
// How far the user has to scroll in order to reach the full elevation
|
||||
private static final float HEADER_SCROLL_TO_ELEVATION_DP = 16;
|
||||
private static final int FADE_IN_DURATION = 175;
|
||||
private static final int FADE_OUT_DURATION = 100;
|
||||
private static final int SEARCH_TRANSLATION_X_DP = 18;
|
||||
@@ -159,8 +161,7 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
||||
* Scrolls this list view to the top.
|
||||
*/
|
||||
public void scrollToTop() {
|
||||
mAppsRecyclerView.scrollToPosition(0);
|
||||
mRecyclerViewScrollY = 0;
|
||||
mAppsRecyclerView.scrollToTop();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,18 +231,14 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
||||
mAppsRecyclerView.setLayoutManager(mLayoutManager);
|
||||
mAppsRecyclerView.setAdapter(mAdapter);
|
||||
mAppsRecyclerView.setHasFixedSize(true);
|
||||
mAppsRecyclerView.setOnScrollListenerProxy(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
mRecyclerViewScrollY += dy;
|
||||
onRecyclerViewScrolled();
|
||||
}
|
||||
});
|
||||
mAppsRecyclerView.setOnScrollListenerProxy(
|
||||
new BaseContainerRecyclerView.OnScrollToListener() {
|
||||
@Override
|
||||
public void onScrolledTo(int x, int y) {
|
||||
mRecyclerViewScrollY = y;
|
||||
onRecyclerViewScrolled();
|
||||
}
|
||||
});
|
||||
if (mItemDecoration != null) {
|
||||
mAppsRecyclerView.addItemDecoration(mItemDecoration);
|
||||
}
|
||||
@@ -291,9 +288,11 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
||||
int startMargin = grid.isPhone() ? mContentMarginStart : 0;
|
||||
int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
|
||||
if (isRtl) {
|
||||
mAppsRecyclerView.setPadding(inset, inset, inset + startMargin, inset);
|
||||
mAppsRecyclerView.setPadding(inset + mAppsRecyclerView.getScrollbarWidth(), inset,
|
||||
inset + startMargin, inset);
|
||||
} else {
|
||||
mAppsRecyclerView.setPadding(inset + startMargin, inset, inset, inset);
|
||||
mAppsRecyclerView.setPadding(inset + startMargin, inset,
|
||||
inset + mAppsRecyclerView.getScrollbarWidth(), inset);
|
||||
}
|
||||
|
||||
// Update the header bar
|
||||
@@ -456,7 +455,10 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
||||
String formatStr = getResources().getString(R.string.apps_view_no_search_results);
|
||||
mAdapter.setEmptySearchText(String.format(formatStr, queryText));
|
||||
|
||||
// Do an intersection of the words in the query and each title, and filter out all the
|
||||
// apps that don't match all of the words in the query.
|
||||
final String queryTextLower = queryText.toLowerCase();
|
||||
final String[] queryWords = SPLIT_PATTERN.split(queryTextLower);
|
||||
mApps.setFilter(new AlphabeticalAppsList.Filter() {
|
||||
@Override
|
||||
public boolean retainApp(AppInfo info, String sectionName) {
|
||||
@@ -465,12 +467,21 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
||||
}
|
||||
String title = info.title.toString();
|
||||
String[] words = SPLIT_PATTERN.split(title.toLowerCase());
|
||||
for (int i = 0; i < words.length; i++) {
|
||||
if (words[i].startsWith(queryTextLower)) {
|
||||
return true;
|
||||
for (int qi = 0; qi < queryWords.length; qi++) {
|
||||
boolean foundMatch = false;
|
||||
for (int i = 0; i < words.length; i++) {
|
||||
if (words[i].startsWith(queryWords[qi])) {
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundMatch) {
|
||||
// If there is a word in the query that does not match any words in this
|
||||
// title, so skip it.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -531,11 +542,16 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
|
||||
* Updates the container when the recycler view is scrolled.
|
||||
*/
|
||||
private void onRecyclerViewScrolled() {
|
||||
if (DYNAMIC_HEADER_ELEVATION) {
|
||||
int elevation = Math.min(mRecyclerViewScrollY, DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
|
||||
getContext().getResources().getDisplayMetrics()));
|
||||
if (Float.compare(mHeaderView.getElevation(), elevation) != 0) {
|
||||
mHeaderView.setElevation(elevation);
|
||||
if (DYNAMIC_HEADER_ELEVATION && Utilities.isLmpOrAbove()) {
|
||||
int elevation = DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
|
||||
getContext().getResources().getDisplayMetrics());
|
||||
int scrollToElevation = DynamicGrid.pxFromDp(HEADER_SCROLL_TO_ELEVATION_DP,
|
||||
getContext().getResources().getDisplayMetrics());
|
||||
float elevationPct = (float) Math.min(mRecyclerViewScrollY, scrollToElevation) /
|
||||
scrollToElevation;
|
||||
float newElevation = elevation * elevationPct;
|
||||
if (Float.compare(mHeaderView.getElevation(), newElevation) != 0) {
|
||||
mHeaderView.setElevation(newElevation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,11 +101,10 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
||||
|
||||
if (shouldDrawItemDivider(holder, items) && !hasDrawnPredictedAppDivider) {
|
||||
// Draw the divider under the predicted app
|
||||
parent.getBackground().getPadding(mTmpBounds);
|
||||
int top = child.getTop() + child.getHeight();
|
||||
int left = parent.getPaddingLeft();
|
||||
int right = parent.getWidth() - parent.getPaddingRight();
|
||||
int iconInset = (((right - left) / mAppsPerRow) - grid.allAppsIconSizePx) / 2;
|
||||
c.drawLine(left + iconInset, top, right - iconInset, top, mPredictedAppsDividerPaint);
|
||||
c.drawLine(mTmpBounds.left, top, parent.getWidth() - mTmpBounds.right, top,
|
||||
mPredictedAppsDividerPaint);
|
||||
hasDrawnPredictedAppDivider = true;
|
||||
|
||||
} else if (grid.isPhone() && shouldDrawItemSection(holder, i, items)) {
|
||||
@@ -297,8 +296,8 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
|
||||
mSectionTextPaint.setAntiAlias(true);
|
||||
|
||||
mPredictedAppsDividerPaint = new Paint();
|
||||
mPredictedAppsDividerPaint.setStrokeWidth(DynamicGrid.pxFromDp(1.5f, res.getDisplayMetrics()));
|
||||
mPredictedAppsDividerPaint.setColor(0x10000000);
|
||||
mPredictedAppsDividerPaint.setStrokeWidth(DynamicGrid.pxFromDp(1f, res.getDisplayMetrics()));
|
||||
mPredictedAppsDividerPaint.setColor(0x1E000000);
|
||||
mPredictedAppsDividerPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,12 +29,20 @@ import com.android.launcher3.util.Thunk;
|
||||
public class BaseContainerRecyclerView extends RecyclerView
|
||||
implements RecyclerView.OnItemTouchListener {
|
||||
|
||||
/**
|
||||
* Listener to get notified when the absolute scroll changes.
|
||||
*/
|
||||
public interface OnScrollToListener {
|
||||
void onScrolledTo(int x, int y);
|
||||
}
|
||||
|
||||
private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
|
||||
|
||||
/** Keeps the last known scrolling delta/velocity along y-axis. */
|
||||
@Thunk int mDy = 0;
|
||||
@Thunk int mScrollY;
|
||||
private float mDeltaThreshold;
|
||||
private RecyclerView.OnScrollListener mScrollListenerProxy;
|
||||
private OnScrollToListener mScrollToListener;
|
||||
|
||||
public BaseContainerRecyclerView(Context context) {
|
||||
this(context, null);
|
||||
@@ -60,8 +68,9 @@ public class BaseContainerRecyclerView extends RecyclerView
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
mDy = dy;
|
||||
if (mScrollListenerProxy != null) {
|
||||
mScrollListenerProxy.onScrolled(recyclerView, dx, dy);
|
||||
mScrollY += dy;
|
||||
if (mScrollToListener != null) {
|
||||
mScrollToListener.onScrolledTo(0, mScrollY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,8 +78,8 @@ public class BaseContainerRecyclerView extends RecyclerView
|
||||
/**
|
||||
* Sets an additional scroll listener, only needed for LMR1 version of the support lib.
|
||||
*/
|
||||
public void setOnScrollListenerProxy(RecyclerView.OnScrollListener listener) {
|
||||
mScrollListenerProxy = listener;
|
||||
public void setOnScrollListenerProxy(OnScrollToListener listener) {
|
||||
mScrollToListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,6 +105,17 @@ public class BaseContainerRecyclerView extends RecyclerView
|
||||
// DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the scroll position, used to workaround a RecyclerView issue with scrolling to
|
||||
* position.
|
||||
*/
|
||||
protected void updateScrollY(int scrollY) {
|
||||
mScrollY = scrollY;
|
||||
if (mScrollToListener != null) {
|
||||
mScrollToListener.onScrolledTo(0, mScrollY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this {@link MotionEvent} should trigger the scroll to be stopped.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user