summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
diff options
context:
space:
mode:
authorWinson Chung <winsonc@google.com>2015-05-22 11:12:27 -0700
committerWinson Chung <winsonc@google.com>2015-05-22 12:21:40 -0700
commit5f4e0fdd2e4edeb9211e2dcd1c99497f175731f8 (patch)
tree3abefdc96cf11c695db912016598157f94a6cca4 /src/com/android/launcher3/allapps/AllAppsRecyclerView.java
parentc6205603efe1f2987caf96504c87d720a25b5a94 (diff)
downloadandroid_packages_apps_Trebuchet-5f4e0fdd2e4edeb9211e2dcd1c99497f175731f8.tar.gz
android_packages_apps_Trebuchet-5f4e0fdd2e4edeb9211e2dcd1c99497f175731f8.tar.bz2
android_packages_apps_Trebuchet-5f4e0fdd2e4edeb9211e2dcd1c99497f175731f8.zip
Moving all apps code into sub package.
- Renaming resources, dimens, etc to be more consistent - Removing old AppsCustomize resources and other unused code Change-Id: I15ce35e7cb7a9b9344fc7103963e4e4c9e45d89a
Diffstat (limited to 'src/com/android/launcher3/allapps/AllAppsRecyclerView.java')
-rw-r--r--src/com/android/launcher3/allapps/AllAppsRecyclerView.java528
1 files changed, 528 insertions, 0 deletions
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
new file mode 100644
index 000000000..e95fa325a
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -0,0 +1,528 @@
+/*
+ * 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.
+ */
+package com.android.launcher3.allapps;
+
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import com.android.launcher3.BaseRecyclerView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
+import java.util.List;
+
+/**
+ * A RecyclerView with custom fastscroll support. This is the main container for the all apps
+ * icons.
+ */
+public class AllAppsRecyclerView extends BaseRecyclerView {
+
+ /**
+ * 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 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;
+ private int mNumAppsPerRow;
+ private int mNumPredictedAppsPerRow;
+
+ private Drawable mScrollbar;
+ private Drawable mFastScrollerBg;
+ private Rect mTmpFastScrollerInvalidateRect = new Rect();
+ private Rect mFastScrollerBounds = new Rect();
+ private Rect mVerticalScrollbarBounds = new Rect();
+ private boolean mDraggingFastScroller;
+ private String mFastScrollSectionName;
+ private Paint mFastScrollTextPaint;
+ private Rect mFastScrollTextBounds = new Rect();
+ private float mFastScrollAlpha;
+ private int mPredictionBarHeight;
+ private int mDownX;
+ private int mDownY;
+ private int mLastX;
+ private int mLastY;
+ private int mScrollbarWidth;
+ private int mScrollbarMinHeight;
+ private int mScrollbarInset;
+ private Rect mBackgroundPadding = new Rect();
+ private ScrollPositionState mScrollPosState = new ScrollPositionState();
+
+ private Launcher mLauncher;
+
+ public AllAppsRecyclerView(Context context) {
+ this(context, null);
+ }
+
+ public AllAppsRecyclerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr);
+
+ mLauncher = (Launcher) context;
+ Resources res = context.getResources();
+ int fastScrollerSize = res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_popup_size);
+ mScrollbar = res.getDrawable(R.drawable.all_apps_scrollbar_thumb);
+ mFastScrollerBg = res.getDrawable(R.drawable.all_apps_fastscroll_bg);
+ mFastScrollerBg.setBounds(0, 0, fastScrollerSize, fastScrollerSize);
+ mFastScrollTextPaint = new Paint();
+ mFastScrollTextPaint.setColor(Color.WHITE);
+ mFastScrollTextPaint.setAntiAlias(true);
+ mFastScrollTextPaint.setTextSize(res.getDimensionPixelSize(
+ R.dimen.all_apps_fast_scroll_text_size));
+ mScrollbarWidth = res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_bar_width);
+ mScrollbarMinHeight =
+ res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_bar_min_height);
+ mScrollbarInset =
+ res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_scrubber_touch_inset);
+ setFastScrollerAlpha(getFastScrollerAlpha());
+ setOverScrollMode(View.OVER_SCROLL_NEVER);
+ }
+
+ /**
+ * Sets the list of apps in this view, used to determine the fastscroll position.
+ */
+ public void setApps(AlphabeticalAppsList apps) {
+ mApps = apps;
+ }
+
+ /**
+ * Sets the number of apps per row in this recycler view.
+ */
+ public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
+ mNumAppsPerRow = numAppsPerRow;
+ mNumPredictedAppsPerRow = numPredictedAppsPerRow;
+
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ RecyclerView.RecycledViewPool pool = getRecycledViewPool();
+ int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
+ pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_BAR_SPACER_TYPE, 1);
+ pool.setMaxRecycledViews(AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE, 1);
+ pool.setMaxRecycledViews(AllAppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow);
+ pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
+ }
+
+ public void updateBackgroundPadding(Drawable background) {
+ background.getPadding(mBackgroundPadding);
+ }
+
+ /**
+ * Sets the prediction bar height.
+ */
+ public void setPredictionBarHeight(int height) {
+ mPredictionBarHeight = height;
+ }
+
+ /**
+ * Sets the fast scroller alpha.
+ */
+ public void setFastScrollerAlpha(float alpha) {
+ mFastScrollAlpha = alpha;
+ invalidateFastScroller(mFastScrollerBounds);
+ }
+
+ /**
+ * Gets the fast scroller alpha.
+ */
+ public float getFastScrollerAlpha() {
+ return mFastScrollAlpha;
+ }
+
+ /**
+ * Returns the scroll bar width.
+ */
+ public int getScrollbarWidth() {
+ return mScrollbarWidth;
+ }
+
+ /**
+ * Scrolls this recycler view to the top.
+ */
+ public void scrollToTop() {
+ scrollToPosition(0);
+ }
+
+ /**
+ * Returns the current scroll position.
+ */
+ public int getScrollPosition() {
+ List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+ getCurScrollState(mScrollPosState, items);
+ if (mScrollPosState.rowIndex != -1) {
+ int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
+ return getPaddingTop() + (mScrollPosState.rowIndex * mScrollPosState.rowHeight) +
+ predictionBarHeight - mScrollPosState.rowTopOffset;
+ }
+ return 0;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ addOnItemTouchListener(this);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ drawVerticalScrubber(canvas);
+ drawFastScrollerPopup(canvas);
+ }
+
+ /**
+ * We intercept the touch handling only to support fast scrolling when initiated from the
+ * scroll bar. Otherwise, we fall back to the default RecyclerView touch handling.
+ */
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
+ return handleTouchEvent(ev);
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
+ handleTouchEvent(ev);
+ }
+
+ /**
+ * Handles the touch event and determines whether to show the fast scroller (or updates it if
+ * it is already showing).
+ */
+ private boolean handleTouchEvent(MotionEvent ev) {
+ ViewConfiguration config = ViewConfiguration.get(getContext());
+
+ 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 = mLastX = x;
+ mDownY = mLastY = y;
+ if (shouldStopScroll(ev)) {
+ stopScroll();
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // Check if we are scrolling
+ if (!mDraggingFastScroller && isPointNearScrollbar(mDownX, mDownY) &&
+ Math.abs(y - mDownY) > config.getScaledTouchSlop()) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ mDraggingFastScroller = true;
+ animateFastScrollerVisibility(true);
+ }
+ if (mDraggingFastScroller) {
+ mLastX = x;
+ mLastY = y;
+
+ // Scroll to the right position, and update the section name
+ int top = getPaddingTop() + (mFastScrollerBg.getBounds().height() / 2);
+ int bottom = getHeight() - getPaddingBottom() -
+ (mFastScrollerBg.getBounds().height() / 2);
+ float boundedY = (float) Math.max(top, Math.min(bottom, y));
+ mFastScrollSectionName = scrollToPositionAtProgress((boundedY - top) /
+ (bottom - top));
+
+ // Combine the old and new fast scroller bounds to create the full invalidate
+ // rect
+ mTmpFastScrollerInvalidateRect.set(mFastScrollerBounds);
+ updateFastScrollerBounds();
+ mTmpFastScrollerInvalidateRect.union(mFastScrollerBounds);
+ invalidateFastScroller(mTmpFastScrollerInvalidateRect);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mDraggingFastScroller = false;
+ animateFastScrollerVisibility(false);
+ break;
+ }
+ return mDraggingFastScroller;
+ }
+
+ /**
+ * Animates the visibility of the fast scroller popup.
+ */
+ private void animateFastScrollerVisibility(boolean visible) {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(this, "fastScrollerAlpha", visible ? 1f : 0f);
+ anim.setDuration(visible ? 200 : 150);
+ 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) {
+ if (mFastScrollAlpha > 0f && !mFastScrollSectionName.isEmpty()) {
+ // Draw the fast scroller popup
+ int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.translate(mFastScrollerBounds.left, mFastScrollerBounds.top);
+ mFastScrollerBg.setAlpha((int) (mFastScrollAlpha * 255));
+ mFastScrollerBg.draw(canvas);
+ mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255));
+ mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0,
+ mFastScrollSectionName.length(), mFastScrollTextBounds);
+ float textWidth = mFastScrollTextPaint.measureText(mFastScrollSectionName);
+ canvas.drawText(mFastScrollSectionName,
+ (mFastScrollerBounds.width() - textWidth) / 2,
+ mFastScrollerBounds.height() -
+ (mFastScrollerBounds.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(Rect bounds) {
+ invalidate(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ }
+
+ /**
+ * Maps the touch (from 0..1) to the adapter position that should be visible.
+ */
+ private String scrollToPositionAtProgress(float touchFraction) {
+ // Ensure that we have any sections
+ List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
+ mApps.getFastScrollerSections();
+ if (fastScrollSections.isEmpty()) {
+ return "";
+ }
+
+ // Stop the scroller if it is scrolling
+ LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
+ stopScroll();
+
+ // If there is a prediction bar, then capture the appropriate area for the prediction bar
+ float predictionBarFraction = 0f;
+ if (!mApps.getPredictedApps().isEmpty()) {
+ predictionBarFraction = (float) mNumPredictedAppsPerRow / mApps.getSize();
+ if (touchFraction <= predictionBarFraction) {
+ // Scroll to the top of the view, where the prediction bar is
+ layoutManager.scrollToPositionWithOffset(0, 0);
+ return "";
+ }
+ }
+
+ // Since the app ranges are from 0..1, we need to map the touch fraction back to 0..1 from
+ // predictionBarFraction..1
+ touchFraction = (touchFraction - predictionBarFraction) *
+ (1f / (1f - predictionBarFraction));
+ AlphabeticalAppsList.FastScrollSectionInfo lastScrollSection = fastScrollSections.get(0);
+ for (int i = 1; i < fastScrollSections.size(); i++) {
+ AlphabeticalAppsList.FastScrollSectionInfo scrollSection = fastScrollSections.get(i);
+ if (lastScrollSection.appRangeFraction <= touchFraction &&
+ touchFraction < scrollSection.appRangeFraction) {
+ break;
+ }
+ lastScrollSection = scrollSection;
+ }
+
+ // Scroll to the view at the position, anchored at the top of the screen. We call the scroll
+ // method on the LayoutManager directly since it is not exposed by RecyclerView.
+ layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0);
+
+ return lastScrollSection.sectionName;
+ }
+
+ /**
+ * Updates the bounds for the scrollbar.
+ */
+ private void updateVerticalScrollbarBounds() {
+ List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+
+ // Skip early if there are no items
+ if (items.isEmpty()) {
+ mVerticalScrollbarBounds.setEmpty();
+ return;
+ }
+
+ // Find the index and height of the first visible row (all rows have the same height)
+ int x;
+ int y;
+ int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight;
+ int rowCount = getNumRows();
+ getCurScrollState(mScrollPosState, items);
+ if (mScrollPosState.rowIndex != -1) {
+ int height = getHeight() - getPaddingTop() - getPaddingBottom();
+ int totalScrollHeight = rowCount * mScrollPosState.rowHeight + predictionBarHeight;
+ if (totalScrollHeight > height) {
+ int scrollbarHeight = Math.max(mScrollbarMinHeight,
+ (int) (height / ((float) totalScrollHeight / height)));
+
+ // Calculate the position and size of the scroll bar
+ if (Utilities.isRtl(getResources())) {
+ x = mBackgroundPadding.left;
+ } else {
+ 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 = (mScrollPosState.rowIndex * mScrollPosState.rowHeight) + predictionBarHeight
+ - mScrollPosState.rowTopOffset;
+ y = getPaddingTop() +
+ (int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY);
+
+ mVerticalScrollbarBounds.set(x, y, x + mScrollbarWidth, y + scrollbarHeight);
+ return;
+ }
+ }
+ mVerticalScrollbarBounds.setEmpty();
+ }
+
+ /**
+ * Updates the bounds for the fast scroller.
+ */
+ private void updateFastScrollerBounds() {
+ if (mFastScrollAlpha > 0f && !mFastScrollSectionName.isEmpty()) {
+ int x;
+ int y;
+
+ // Calculate the position for the fast scroller popup
+ Rect bgBounds = mFastScrollerBg.getBounds();
+ if (Utilities.isRtl(getResources())) {
+ x = mBackgroundPadding.left + 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()));
+ mFastScrollerBounds.set(x, y, x + bgBounds.width(), y + bgBounds.height());
+ } else {
+ mFastScrollerBounds.setEmpty();
+ }
+ }
+
+ /**
+ * Returns the row index for a app index in the list.
+ */
+ private int findRowForAppIndex(int index) {
+ List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
+ int appIndex = 0;
+ int rowCount = 0;
+ for (AlphabeticalAppsList.SectionInfo info : sections) {
+ int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow);
+ if (appIndex + info.numApps > index) {
+ return rowCount + ((index - appIndex) / mNumAppsPerRow);
+ }
+ appIndex += info.numApps;
+ 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.numApps / mNumAppsPerRow);
+ rowCount += numRowsInSection;
+ }
+ return rowCount;
+ }
+
+ /**
+ * Returns the current scroll state.
+ */
+ private void getCurScrollState(ScrollPositionState stateOut,
+ List<AlphabeticalAppsList.AdapterItem> items) {
+ stateOut.rowIndex = -1;
+ stateOut.rowTopOffset = -1;
+ stateOut.rowHeight = -1;
+
+ // Return early if there are no items
+ if (items.isEmpty()) {
+ return;
+ }
+
+ 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.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
+ stateOut.rowIndex = findRowForAppIndex(item.appIndex);
+ stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
+ stateOut.rowHeight = child.getHeight();
+ break;
+ }
+ }
+ }
+ }
+}