diff options
Diffstat (limited to 'src/com/android/launcher3/AppsContainerRecyclerView.java')
-rw-r--r-- | src/com/android/launcher3/AppsContainerRecyclerView.java | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java new file mode 100644 index 000000000..2280e99ef --- /dev/null +++ b/src/com/android/launcher3/AppsContainerRecyclerView.java @@ -0,0 +1,271 @@ +/* + * 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; + +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.RecyclerView; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import java.util.List; + +/** + * A RecyclerView with custom fastscroll support. This is the main container for the all apps + * icons. + */ +public class AppsContainerRecyclerView extends RecyclerView + implements RecyclerView.OnItemTouchListener { + + private AlphabeticalAppsList mApps; + private int mNumAppsPerRow; + + private Drawable mFastScrollerBg; + private boolean mDraggingFastScroller; + private String mFastScrollSectionName; + private Paint mFastScrollTextPaint; + private Rect mFastScrollTextBounds = new Rect(); + private float mFastScrollAlpha; + private int mDownX; + private int mDownY; + private int mLastX; + private int mLastY; + private int mGutterSize; + + public AppsContainerRecyclerView(Context context) { + this(context, null); + } + + public AppsContainerRecyclerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public AppsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AppsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr); + + 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); + 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); + setFastScrollerAlpha(getFastScrollerAlpha()); + } + + /** + * 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 rowSize) { + mNumAppsPerRow = rowSize; + } + + /** + * Sets the fast scroller alpha. + */ + public void setFastScrollerAlpha(float alpha) { + mFastScrollAlpha = alpha; + invalidateFastScroller(); + } + + /** + * Gets the fast scroller alpha. + */ + public float getFastScrollerAlpha() { + return mFastScrollAlpha; + } + + @Override + protected void onFinishInflate() { + addOnItemTouchListener(this); + } + + @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 - bgBounds.height() / 2; + 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 + * gutter. 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; + stopScroll(); + 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 && + 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)); + invalidateFastScroller(); + } + 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(); + } + + /** + * Invalidates the fast scroller popup. + */ + private void invalidateFastScroller() { + invalidate(getWidth() - getPaddingRight() - getScrollBarSize() - + mFastScrollerBg.getIntrinsicWidth(), 0, getWidth(), getHeight()); + } + + /** + * Maps the progress (from 0..1) to the position that should be visible + */ + 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; + } + + // Find the index of the first app in that row and scroll to that position + int rowAtProgress = (int) (progress * rowCount); + int appIndex = 0; + rowCount = 0; + for (AlphabeticalAppsList.SectionInfo info : sections) { + int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow); + if (rowCount + numRowsInSection > rowAtProgress) { + appIndex += (rowAtProgress - rowCount) * mNumAppsPerRow; + break; + } + rowCount += numRowsInSection; + appIndex += info.numAppsInSection; + } + appIndex = Math.max(0, Math.min(mApps.getAppsWithoutSectionBreaks().size() - 1, appIndex)); + AppInfo appInfo = mApps.getAppsWithoutSectionBreaks().get(appIndex); + int sectionedAppIndex = mApps.getApps().indexOf(appInfo); + scrollToPosition(sectionedAppIndex); + + // Returns the section name of the row + return mApps.getSectionNameForApp(appInfo); + } +} |