Merge "Making the scrollbar scrubbable." into ub-launcher3-burnaby

This commit is contained in:
Winson Chung
2015-03-24 17:33:30 +00:00
committed by Android (Google) Code Review
6 changed files with 221 additions and 55 deletions
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/apps_view_scrollbar_thumb_color" />
<size
android:width="64dp"
android:height="64dp" />
<corners
android:topLeftRadius="64dp"
android:topRightRadius="64dp"
android:bottomRightRadius="64dp" />
</shape>
+1 -1
View File
@@ -17,5 +17,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="@color/apps_view_scrollbar_thumb_color" /> <solid android:color="@color/apps_view_scrollbar_thumb_color" />
<size android:width="@dimen/apps_view_fast_scroll_bar_size" /> <size android:width="@dimen/apps_view_fast_scroll_bar_width" />
</shape> </shape>
+1 -4
View File
@@ -36,7 +36,7 @@
android:textSize="16sp" android:textSize="16sp"
android:textColor="#4c4c4c" android:textColor="#4c4c4c"
android:textColorHint="#9c9c9c" android:textColorHint="#9c9c9c"
android:imeOptions="flagNoExtractUi" android:imeOptions="actionDone|flagNoExtractUi"
android:background="@drawable/apps_list_search_bg" android:background="@drawable/apps_list_search_bg"
android:elevation="4dp" /> android:elevation="4dp" />
<com.android.launcher3.AppsContainerRecyclerView <com.android.launcher3.AppsContainerRecyclerView
@@ -47,9 +47,6 @@
android:paddingTop="12dp" android:paddingTop="12dp"
android:paddingBottom="12dp" android:paddingBottom="12dp"
android:clipToPadding="false" android:clipToPadding="false"
android:fadeScrollbars="false"
android:scrollbars="vertical"
android:scrollbarThumbVertical="@drawable/apps_list_scrollbar_thumb"
android:focusable="true" android:focusable="true"
android:descendantFocusability="afterDescendants" android:descendantFocusability="afterDescendants"
android:background="@drawable/apps_list_bg" /> android:background="@drawable/apps_list_bg" />
+3 -2
View File
@@ -52,8 +52,9 @@
<dimen name="apps_grid_view_start_margin">52dp</dimen> <dimen name="apps_grid_view_start_margin">52dp</dimen>
<dimen name="apps_view_row_height">64dp</dimen> <dimen name="apps_view_row_height">64dp</dimen>
<dimen name="apps_view_section_text_size">24sp</dimen> <dimen name="apps_view_section_text_size">24sp</dimen>
<dimen name="apps_view_fast_scroll_bar_size">6dp</dimen> <dimen name="apps_view_fast_scroll_bar_width">6dp</dimen>
<dimen name="apps_view_fast_scroll_gutter_size">40dp</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_popup_size">64dp</dimen>
<dimen name="apps_view_fast_scroll_text_size">40dp</dimen> <dimen name="apps_view_fast_scroll_text_size">40dp</dimen>
@@ -26,6 +26,7 @@ import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration; import android.view.ViewConfiguration;
import java.util.List; import java.util.List;
@@ -42,7 +43,9 @@ public class AppsContainerRecyclerView extends RecyclerView
private AlphabeticalAppsList mApps; private AlphabeticalAppsList mApps;
private int mNumAppsPerRow; private int mNumAppsPerRow;
private Drawable mScrollbar;
private Drawable mFastScrollerBg; private Drawable mFastScrollerBg;
private Rect mVerticalScrollbarBounds = new Rect();
private boolean mDraggingFastScroller; private boolean mDraggingFastScroller;
private String mFastScrollSectionName; private String mFastScrollSectionName;
private Paint mFastScrollTextPaint; private Paint mFastScrollTextPaint;
@@ -52,7 +55,9 @@ public class AppsContainerRecyclerView extends RecyclerView
private int mDownY; private int mDownY;
private int mLastX; private int mLastX;
private int mLastY; private int mLastY;
private int mGutterSize; private int mScrollbarWidth;
private int mScrollbarMinHeight;
private int mScrollbarInset;
public AppsContainerRecyclerView(Context context) { public AppsContainerRecyclerView(Context context) {
this(context, null); this(context, null);
@@ -72,14 +77,19 @@ public class AppsContainerRecyclerView extends RecyclerView
Resources res = context.getResources(); Resources res = context.getResources();
int fastScrollerSize = res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_popup_size); int fastScrollerSize = res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_popup_size);
mFastScrollerBg = res.getDrawable(R.drawable.apps_list_fastscroll_bg); mScrollbar = context.getDrawable(R.drawable.apps_list_scrollbar_thumb);
mFastScrollerBg = context.getDrawable(R.drawable.apps_list_fastscroll_bg);
mFastScrollerBg.setBounds(0, 0, fastScrollerSize, fastScrollerSize); mFastScrollerBg.setBounds(0, 0, fastScrollerSize, fastScrollerSize);
mFastScrollTextPaint = new Paint(); mFastScrollTextPaint = new Paint();
mFastScrollTextPaint.setColor(Color.WHITE); mFastScrollTextPaint.setColor(Color.WHITE);
mFastScrollTextPaint.setAntiAlias(true); mFastScrollTextPaint.setAntiAlias(true);
mFastScrollTextPaint.setTextSize(res.getDimensionPixelSize( mFastScrollTextPaint.setTextSize(res.getDimensionPixelSize(
R.dimen.apps_view_fast_scroll_text_size)); R.dimen.apps_view_fast_scroll_text_size));
mGutterSize = res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_gutter_size); mScrollbarWidth = res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_bar_width);
mScrollbarMinHeight =
res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_bar_min_height);
mScrollbarInset =
res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_scrubber_touch_inset);
setFastScrollerAlpha(getFastScrollerAlpha()); setFastScrollerAlpha(getFastScrollerAlpha());
} }
@@ -112,6 +122,13 @@ public class AppsContainerRecyclerView extends RecyclerView
return mFastScrollAlpha; return mFastScrollAlpha;
} }
/**
* Returns the scroll bar width.
*/
public int getScrollbarWidth() {
return mScrollbarWidth;
}
@Override @Override
protected void onFinishInflate() { protected void onFinishInflate() {
addOnItemTouchListener(this); addOnItemTouchListener(this);
@@ -120,38 +137,13 @@ public class AppsContainerRecyclerView extends RecyclerView
@Override @Override
protected void dispatchDraw(Canvas canvas) { protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas); super.dispatchDraw(canvas);
drawVerticalScrubber(canvas);
if (mFastScrollAlpha > 0f) { drawFastScrollerPopup(canvas);
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
LAYOUT_DIRECTION_RTL);
Rect bgBounds = mFastScrollerBg.getBounds();
int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
int x;
if (isRtl) {
x = getPaddingLeft() + getScrollBarSize();
} else {
x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width();
}
int y = mLastY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgBounds.height());
y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() -
bgBounds.height()));
canvas.translate(x, y);
mFastScrollerBg.setAlpha((int) (mFastScrollAlpha * 255));
mFastScrollerBg.draw(canvas);
mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255));
mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0,
mFastScrollSectionName.length(), mFastScrollTextBounds);
canvas.drawText(mFastScrollSectionName,
(bgBounds.width() - mFastScrollTextBounds.width()) / 2,
bgBounds.height() - (bgBounds.height() - mFastScrollTextBounds.height()) / 2,
mFastScrollTextPaint);
canvas.restoreToCount(restoreCount);
}
} }
/** /**
* We intercept the touch handling only to support fast scrolling when initiated from the * We intercept the touch handling only to support fast scrolling when initiated from the
* gutter. Otherwise, we fall back to the default RecyclerView touch handling. * scroll bar. Otherwise, we fall back to the default RecyclerView touch handling.
*/ */
@Override @Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) { public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
@@ -182,15 +174,7 @@ public class AppsContainerRecyclerView extends RecyclerView
break; break;
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
// Check if we are scrolling // Check if we are scrolling
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() == if (!mDraggingFastScroller && isPointNearScrollbar(mDownX, mDownY) &&
LAYOUT_DIRECTION_RTL);
boolean isInGutter;
if (isRtl) {
isInGutter = mDownX < mGutterSize;
} else {
isInGutter = mDownX >= (getWidth() - mGutterSize);
}
if (!mDraggingFastScroller && isInGutter &&
Math.abs(y - mDownY) > config.getScaledTouchSlop()) { Math.abs(y - mDownY) > config.getScaledTouchSlop()) {
getParent().requestDisallowInterceptTouchEvent(true); getParent().requestDisallowInterceptTouchEvent(true);
mDraggingFastScroller = true; mDraggingFastScroller = true;
@@ -229,6 +213,67 @@ public class AppsContainerRecyclerView extends RecyclerView
anim.start(); anim.start();
} }
/**
* Returns whether a given point is near the scrollbar.
*/
private boolean isPointNearScrollbar(int x, int y) {
// Check if we are scrolling
updateVerticalScrollbarBounds();
mVerticalScrollbarBounds.inset(mScrollbarInset, mScrollbarInset);
return mVerticalScrollbarBounds.contains(x, y);
}
/**
* Draws the fast scroller popup.
*/
private void drawFastScrollerPopup(Canvas canvas) {
int x;
int y;
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
LAYOUT_DIRECTION_RTL);
if (mFastScrollAlpha > 0f) {
// Calculate the position for the fast scroller popup
Rect bgBounds = mFastScrollerBg.getBounds();
if (isRtl) {
x = getPaddingLeft() + getScrollBarSize();
} else {
x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width();
}
y = mLastY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgBounds.height());
y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() -
bgBounds.height()));
// Draw the fast scroller popup
int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(x, y);
mFastScrollerBg.setAlpha((int) (mFastScrollAlpha * 255));
mFastScrollerBg.draw(canvas);
mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255));
mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0,
mFastScrollSectionName.length(), mFastScrollTextBounds);
canvas.drawText(mFastScrollSectionName,
(bgBounds.width() - mFastScrollTextBounds.width()) / 2,
bgBounds.height() - (bgBounds.height() - mFastScrollTextBounds.height()) / 2,
mFastScrollTextPaint);
canvas.restoreToCount(restoreCount);
}
}
/**
* Draws the vertical scrollbar.
*/
private void drawVerticalScrubber(Canvas canvas) {
updateVerticalScrollbarBounds();
// Draw the scroll bar
int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(mVerticalScrollbarBounds.left, mVerticalScrollbarBounds.top);
mScrollbar.setBounds(0, 0, mScrollbarWidth, mVerticalScrollbarBounds.height());
mScrollbar.draw(canvas);
canvas.restoreToCount(restoreCount);
}
/** /**
* Invalidates the fast scroller popup. * Invalidates the fast scroller popup.
*/ */
@@ -243,11 +288,7 @@ public class AppsContainerRecyclerView extends RecyclerView
private String scrollToPositionAtProgress(float progress) { private String scrollToPositionAtProgress(float progress) {
List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections(); List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
// Get the total number of rows // Get the total number of rows
int rowCount = 0; int rowCount = getNumRows();
for (AlphabeticalAppsList.SectionInfo info : sections) {
int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
rowCount += numRowsInSection;
}
// Find the index of the first app in that row and scroll to that position // Find the index of the first app in that row and scroll to that position
int rowAtProgress = (int) (progress * rowCount); int rowAtProgress = (int) (progress * rowCount);
@@ -270,4 +311,100 @@ public class AppsContainerRecyclerView extends RecyclerView
// Returns the section name of the row // Returns the section name of the row
return mApps.getSectionNameForApp(appInfo); return mApps.getSectionNameForApp(appInfo);
} }
/**
* Returns the bounds for the scrollbar.
*/
private void updateVerticalScrollbarBounds() {
int x;
int y;
boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
LAYOUT_DIRECTION_RTL);
// Skip early if there are no items
if (mApps.getApps().isEmpty()) {
mVerticalScrollbarBounds.setEmpty();
return;
}
// Find the index and height of the first visible row (all rows have the same height)
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) {
AppInfo info = mApps.getApps().get(position);
if (info != AlphabeticalAppsList.SECTION_BREAK_INFO) {
int appIndex = mApps.getAppsWithoutSectionBreaks().indexOf(info);
rowIndex = findRowForAppIndex(appIndex);
rowTopOffset = getLayoutManager().getDecoratedTop(child);
rowHeight = child.getHeight();
break;
}
}
}
if (rowIndex != -1) {
int height = getHeight() - getPaddingTop() - getPaddingBottom();
int totalScrollHeight = rowCount * 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();
} else {
x = getWidth() - getPaddingRight() - 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 = getPaddingTop() +
(int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY);
mVerticalScrollbarBounds.set(x, y, x + mScrollbarWidth, y + scrollbarHeight);
return;
}
}
mVerticalScrollbarBounds.setEmpty();
}
/**
* Returns the row index for a given position in the list.
*/
private int findRowForAppIndex(int position) {
List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
int appIndex = 0;
int rowCount = 0;
for (AlphabeticalAppsList.SectionInfo info : sections) {
int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
if (appIndex + info.numAppsInSection > position) {
return rowCount + ((position - appIndex) / mNumAppsPerRow);
}
appIndex += info.numAppsInSection;
rowCount += numRowsInSection;
}
return appIndex;
}
/**
* Returns the total number of rows in the list.
*/
private int getNumRows() {
List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
int rowCount = 0;
for (AlphabeticalAppsList.SectionInfo info : sections) {
int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
rowCount += numRowsInSection;
}
return rowCount;
}
} }
@@ -168,12 +168,16 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett
mAppsListView.setAdapter(mAdapter); mAppsListView.setAdapter(mAdapter);
mAppsListView.setHasFixedSize(true); mAppsListView.setHasFixedSize(true);
if (isRtl) { if (isRtl) {
mAppsListView.setPadding(mAppsListView.getPaddingLeft(), mAppsListView.getPaddingTop(), mAppsListView.setPadding(
mAppsListView.getPaddingLeft(),
mAppsListView.getPaddingTop(),
mAppsListView.getPaddingRight() + mContentMarginStart, mAppsListView.getPaddingRight() + mContentMarginStart,
mAppsListView.getPaddingBottom()); mAppsListView.getPaddingBottom());
} else { } else {
mAppsListView.setPadding(mAppsListView.getPaddingLeft() + mContentMarginStart, mAppsListView.setPadding(
mAppsListView.getPaddingTop(), mAppsListView.getPaddingRight(), mAppsListView.getPaddingLeft() + mContentMarginStart,
mAppsListView.getPaddingTop(),
mAppsListView.getPaddingRight(),
mAppsListView.getPaddingBottom()); mAppsListView.getPaddingBottom());
} }
if (mItemDecoration != null) { if (mItemDecoration != null) {