diff options
-rw-r--r-- | res/drawable-ldrtl/apps_list_fastscroll_bg.xml | 27 | ||||
-rw-r--r-- | res/drawable/apps_list_scrollbar_thumb.xml | 2 | ||||
-rw-r--r-- | res/layout/apps_list_view.xml | 5 | ||||
-rw-r--r-- | res/values/dimens.xml | 5 | ||||
-rw-r--r-- | src/com/android/launcher3/AppsContainerRecyclerView.java | 227 | ||||
-rw-r--r-- | src/com/android/launcher3/AppsContainerView.java | 10 |
6 files changed, 221 insertions, 55 deletions
diff --git a/res/drawable-ldrtl/apps_list_fastscroll_bg.xml b/res/drawable-ldrtl/apps_list_fastscroll_bg.xml new file mode 100644 index 000000000..772975a71 --- /dev/null +++ b/res/drawable-ldrtl/apps_list_fastscroll_bg.xml @@ -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>
\ No newline at end of file diff --git a/res/drawable/apps_list_scrollbar_thumb.xml b/res/drawable/apps_list_scrollbar_thumb.xml index 59383a5bb..318d40678 100644 --- a/res/drawable/apps_list_scrollbar_thumb.xml +++ b/res/drawable/apps_list_scrollbar_thumb.xml @@ -17,5 +17,5 @@ <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="@dimen/apps_view_fast_scroll_bar_size" /> + <size android:width="@dimen/apps_view_fast_scroll_bar_width" /> </shape>
\ No newline at end of file diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml index 59c04103f..3e42f8489 100644 --- a/res/layout/apps_list_view.xml +++ b/res/layout/apps_list_view.xml @@ -36,7 +36,7 @@ android:textSize="16sp" android:textColor="#4c4c4c" android:textColorHint="#9c9c9c" - android:imeOptions="flagNoExtractUi" + android:imeOptions="actionDone|flagNoExtractUi" android:background="@drawable/apps_list_search_bg" android:elevation="4dp" /> <com.android.launcher3.AppsContainerRecyclerView @@ -47,9 +47,6 @@ android:paddingTop="12dp" android:paddingBottom="12dp" android:clipToPadding="false" - android:fadeScrollbars="false" - android:scrollbars="vertical" - android:scrollbarThumbVertical="@drawable/apps_list_scrollbar_thumb" android:focusable="true" android:descendantFocusability="afterDescendants" android:background="@drawable/apps_list_bg" /> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index b9b9a2412..c327ec2d4 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -52,8 +52,9 @@ <dimen name="apps_grid_view_start_margin">52dp</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_size">6dp</dimen> - <dimen name="apps_view_fast_scroll_gutter_size">40dp</dimen> + <dimen name="apps_view_fast_scroll_bar_width">6dp</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> diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java index c5a508c9c..4d6b9d412 100644 --- a/src/com/android/launcher3/AppsContainerRecyclerView.java +++ b/src/com/android/launcher3/AppsContainerRecyclerView.java @@ -26,6 +26,7 @@ import android.graphics.drawable.Drawable; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.MotionEvent; +import android.view.View; import android.view.ViewConfiguration; import java.util.List; @@ -42,7 +43,9 @@ public class AppsContainerRecyclerView extends RecyclerView private AlphabeticalAppsList mApps; private int mNumAppsPerRow; + private Drawable mScrollbar; private Drawable mFastScrollerBg; + private Rect mVerticalScrollbarBounds = new Rect(); private boolean mDraggingFastScroller; private String mFastScrollSectionName; private Paint mFastScrollTextPaint; @@ -52,7 +55,9 @@ public class AppsContainerRecyclerView extends RecyclerView private int mDownY; private int mLastX; private int mLastY; - private int mGutterSize; + private int mScrollbarWidth; + private int mScrollbarMinHeight; + private int mScrollbarInset; public AppsContainerRecyclerView(Context context) { this(context, null); @@ -72,14 +77,19 @@ public class AppsContainerRecyclerView extends RecyclerView Resources res = context.getResources(); 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); mFastScrollTextPaint = new Paint(); mFastScrollTextPaint.setColor(Color.WHITE); mFastScrollTextPaint.setAntiAlias(true); mFastScrollTextPaint.setTextSize(res.getDimensionPixelSize( 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()); } @@ -112,6 +122,13 @@ public class AppsContainerRecyclerView extends RecyclerView return mFastScrollAlpha; } + /** + * Returns the scroll bar width. + */ + public int getScrollbarWidth() { + return mScrollbarWidth; + } + @Override protected void onFinishInflate() { addOnItemTouchListener(this); @@ -120,38 +137,13 @@ public class AppsContainerRecyclerView extends RecyclerView @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); - - if (mFastScrollAlpha > 0f) { - 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); - } + drawVerticalScrubber(canvas); + drawFastScrollerPopup(canvas); } /** * 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 public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) { @@ -182,15 +174,7 @@ public class AppsContainerRecyclerView extends RecyclerView break; case MotionEvent.ACTION_MOVE: // Check if we are scrolling - boolean isRtl = (getResources().getConfiguration().getLayoutDirection() == - LAYOUT_DIRECTION_RTL); - boolean isInGutter; - if (isRtl) { - isInGutter = mDownX < mGutterSize; - } else { - isInGutter = mDownX >= (getWidth() - mGutterSize); - } - if (!mDraggingFastScroller && isInGutter && + if (!mDraggingFastScroller && isPointNearScrollbar(mDownX, mDownY) && Math.abs(y - mDownY) > config.getScaledTouchSlop()) { getParent().requestDisallowInterceptTouchEvent(true); mDraggingFastScroller = true; @@ -230,6 +214,67 @@ public class AppsContainerRecyclerView extends RecyclerView } /** + * 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. */ private void invalidateFastScroller() { @@ -243,11 +288,7 @@ public class AppsContainerRecyclerView extends RecyclerView private String scrollToPositionAtProgress(float progress) { List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections(); // Get the total number of rows - int rowCount = 0; - for (AlphabeticalAppsList.SectionInfo info : sections) { - int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow); - rowCount += numRowsInSection; - } + int rowCount = getNumRows(); // Find the index of the first app in that row and scroll to that position int rowAtProgress = (int) (progress * rowCount); @@ -270,4 +311,100 @@ public class AppsContainerRecyclerView extends RecyclerView // Returns the section name of the row 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; + } } diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java index ea12fa361..e7b6628cc 100644 --- a/src/com/android/launcher3/AppsContainerView.java +++ b/src/com/android/launcher3/AppsContainerView.java @@ -168,12 +168,16 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett mAppsListView.setAdapter(mAdapter); mAppsListView.setHasFixedSize(true); if (isRtl) { - mAppsListView.setPadding(mAppsListView.getPaddingLeft(), mAppsListView.getPaddingTop(), + mAppsListView.setPadding( + mAppsListView.getPaddingLeft(), + mAppsListView.getPaddingTop(), mAppsListView.getPaddingRight() + mContentMarginStart, mAppsListView.getPaddingBottom()); } else { - mAppsListView.setPadding(mAppsListView.getPaddingLeft() + mContentMarginStart, - mAppsListView.getPaddingTop(), mAppsListView.getPaddingRight(), + mAppsListView.setPadding( + mAppsListView.getPaddingLeft() + mContentMarginStart, + mAppsListView.getPaddingTop(), + mAppsListView.getPaddingRight(), mAppsListView.getPaddingBottom()); } if (mItemDecoration != null) { |