AllAppsRecyclerView.java revision f819dc2bc782e93ac9ecc163a99af0da62821d31
193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung/*
293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * Copyright (C) 2015 The Android Open Source Project
393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung *
493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * Licensed under the Apache License, Version 2.0 (the "License");
593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * you may not use this file except in compliance with the License.
693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * You may obtain a copy of the License at
793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung *
893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung *      http://www.apache.org/licenses/LICENSE-2.0
993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung *
1093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * Unless required by applicable law or agreed to in writing, software
1193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * distributed under the License is distributed on an "AS IS" BASIS,
1293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * See the License for the specific language governing permissions and
1493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * limitations under the License.
1593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung */
1693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungpackage com.android.launcher3;
1793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
1893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.animation.ObjectAnimator;
1993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.content.Context;
2093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.content.res.Resources;
2193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.graphics.Canvas;
2293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.graphics.Color;
2393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.graphics.Paint;
2493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.graphics.Rect;
2593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.graphics.drawable.Drawable;
2693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.support.v7.widget.RecyclerView;
2793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.util.AttributeSet;
2893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.view.MotionEvent;
29f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chungimport android.view.View;
3093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport android.view.ViewConfiguration;
3193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
3293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungimport java.util.List;
3393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
3493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung/**
3593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * A RecyclerView with custom fastscroll support.  This is the main container for the all apps
3693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung * icons.
3793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung */
3893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chungpublic class AppsContainerRecyclerView extends RecyclerView
3993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        implements RecyclerView.OnItemTouchListener {
4093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
41aa2ab254ea6b59dfe4183015e76c31262036282dWinson Chung    private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
42aa2ab254ea6b59dfe4183015e76c31262036282dWinson Chung
4393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private AlphabeticalAppsList mApps;
4493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private int mNumAppsPerRow;
4593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
46f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    private Drawable mScrollbar;
4793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private Drawable mFastScrollerBg;
48f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    private Rect mVerticalScrollbarBounds = new Rect();
4993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private boolean mDraggingFastScroller;
5093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private String mFastScrollSectionName;
5193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private Paint mFastScrollTextPaint;
5293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private Rect mFastScrollTextBounds = new Rect();
5393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private float mFastScrollAlpha;
5493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private int mDownX;
5593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private int mDownY;
5693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private int mLastX;
5793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private int mLastY;
58f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    private int mScrollbarWidth;
59f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    private int mScrollbarMinHeight;
60f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    private int mScrollbarInset;
6193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
6293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    public AppsContainerRecyclerView(Context context) {
6393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        this(context, null);
6493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
6593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
6693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    public AppsContainerRecyclerView(Context context, AttributeSet attrs) {
6793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        this(context, attrs, 0);
6893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
6993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
7093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    public AppsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
7193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        this(context, attrs, defStyleAttr, 0);
7293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
7393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
7493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    public AppsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
7593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung            int defStyleRes) {
7693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        super(context, attrs, defStyleAttr);
7793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
7893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        Resources res = context.getResources();
7993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        int fastScrollerSize = res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_popup_size);
80f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        mScrollbar = context.getDrawable(R.drawable.apps_list_scrollbar_thumb);
81f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        mFastScrollerBg = context.getDrawable(R.drawable.apps_list_fastscroll_bg);
8293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        mFastScrollerBg.setBounds(0, 0, fastScrollerSize, fastScrollerSize);
8393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        mFastScrollTextPaint = new Paint();
8493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        mFastScrollTextPaint.setColor(Color.WHITE);
8593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        mFastScrollTextPaint.setAntiAlias(true);
8693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        mFastScrollTextPaint.setTextSize(res.getDimensionPixelSize(
8793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                R.dimen.apps_view_fast_scroll_text_size));
88f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        mScrollbarWidth = res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_bar_width);
89f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        mScrollbarMinHeight =
90f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_bar_min_height);
91f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        mScrollbarInset =
92f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_scrubber_touch_inset);
9393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        setFastScrollerAlpha(getFastScrollerAlpha());
9493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
9593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
9693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
9793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Sets the list of apps in this view, used to determine the fastscroll position.
9893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
9993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    public void setApps(AlphabeticalAppsList apps) {
10093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        mApps = apps;
10193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
10293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
10393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
10493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Sets the number of apps per row in this recycler view.
10593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
10693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    public void setNumAppsPerRow(int rowSize) {
10793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        mNumAppsPerRow = rowSize;
10893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
10993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
11093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
11193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Sets the fast scroller alpha.
11293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
11393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    public void setFastScrollerAlpha(float alpha) {
11493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        mFastScrollAlpha = alpha;
11593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        invalidateFastScroller();
11693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
11793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
11893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
11993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Gets the fast scroller alpha.
12093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
12193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    public float getFastScrollerAlpha() {
12293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        return mFastScrollAlpha;
12393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
12493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
125f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    /**
126f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     * Returns the scroll bar width.
127f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     */
128f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    public int getScrollbarWidth() {
129f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        return mScrollbarWidth;
130f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    }
131f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
13293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    @Override
13393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    protected void onFinishInflate() {
13493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        addOnItemTouchListener(this);
13593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
13693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
13793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    @Override
13893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    protected void dispatchDraw(Canvas canvas) {
13993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        super.dispatchDraw(canvas);
140f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        drawVerticalScrubber(canvas);
141f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        drawFastScrollerPopup(canvas);
14293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
14393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
14493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
14593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * We intercept the touch handling only to support fast scrolling when initiated from the
146f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     * scroll bar.  Otherwise, we fall back to the default RecyclerView touch handling.
14793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
14893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    @Override
14993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
15093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        return handleTouchEvent(ev);
15193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
15293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
15393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    @Override
15493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
15593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        handleTouchEvent(ev);
15693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
15793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
15893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
15993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Handles the touch event and determines whether to show the fast scroller (or updates it if
16093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * it is already showing).
16193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
16293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private boolean handleTouchEvent(MotionEvent ev) {
16393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        ViewConfiguration config = ViewConfiguration.get(getContext());
16493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
16593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        int action = ev.getAction();
16693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        int x = (int) ev.getX();
16793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        int y = (int) ev.getY();
16893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        switch (action) {
16993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung            case MotionEvent.ACTION_DOWN:
17093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                // Keep track of the down positions
17193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                mDownX = mLastX = x;
17293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                mDownY = mLastY = y;
17393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                stopScroll();
17493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                break;
17593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung            case MotionEvent.ACTION_MOVE:
17693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                // Check if we are scrolling
177f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                if (!mDraggingFastScroller && isPointNearScrollbar(mDownX, mDownY) &&
17893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                        Math.abs(y - mDownY) > config.getScaledTouchSlop()) {
17993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                    getParent().requestDisallowInterceptTouchEvent(true);
18093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                    mDraggingFastScroller = true;
18193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                    animateFastScrollerVisibility(true);
18293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                }
18393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                if (mDraggingFastScroller) {
18493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                    mLastX = x;
18593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                    mLastY = y;
18693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
18793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                    // Scroll to the right position, and update the section name
18893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                    int top = getPaddingTop() + (mFastScrollerBg.getBounds().height() / 2);
18993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                    int bottom = getHeight() - getPaddingBottom() -
19093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                            (mFastScrollerBg.getBounds().height() / 2);
19193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                    float boundedY = (float) Math.max(top, Math.min(bottom, y));
19293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                    mFastScrollSectionName = scrollToPositionAtProgress((boundedY - top) /
19393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                            (bottom - top));
19493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                    invalidateFastScroller();
19593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                }
19693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                break;
19793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung            case MotionEvent.ACTION_UP:
19893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung            case MotionEvent.ACTION_CANCEL:
19993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                mDraggingFastScroller = false;
20093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                animateFastScrollerVisibility(false);
20193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                break;
20293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        }
20393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        return mDraggingFastScroller;
20493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
20593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
20693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
20793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
20893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Animates the visibility of the fast scroller popup.
20993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
21093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private void animateFastScrollerVisibility(boolean visible) {
21193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        ObjectAnimator anim = ObjectAnimator.ofFloat(this, "fastScrollerAlpha", visible ? 1f : 0f);
21293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        anim.setDuration(visible ? 200 : 150);
21393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        anim.start();
21493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
21593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
21693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
217f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     * Returns whether a given point is near the scrollbar.
218f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     */
219f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    private boolean isPointNearScrollbar(int x, int y) {
220f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        // Check if we are scrolling
221f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        updateVerticalScrollbarBounds();
222f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        mVerticalScrollbarBounds.inset(mScrollbarInset, mScrollbarInset);
223f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        return mVerticalScrollbarBounds.contains(x, y);
224f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    }
225f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
226f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    /**
227f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     * Draws the fast scroller popup.
228f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     */
229f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    private void drawFastScrollerPopup(Canvas canvas) {
230f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int x;
231f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int y;
232f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
233f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                LAYOUT_DIRECTION_RTL);
234f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
235f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        if (mFastScrollAlpha > 0f) {
236f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            // Calculate the position for the fast scroller popup
237f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            Rect bgBounds = mFastScrollerBg.getBounds();
238f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            if (isRtl) {
239f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                x = getPaddingLeft() + getScrollBarSize();
240f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            } else {
241f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width();
242f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            }
243f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            y = mLastY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgBounds.height());
244f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() -
245f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                    bgBounds.height()));
246f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
247f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            // Draw the fast scroller popup
248f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
249f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            canvas.translate(x, y);
250f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            mFastScrollerBg.setAlpha((int) (mFastScrollAlpha * 255));
251f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            mFastScrollerBg.draw(canvas);
252f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255));
253f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0,
254f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                    mFastScrollSectionName.length(), mFastScrollTextBounds);
255f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            canvas.drawText(mFastScrollSectionName,
256f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                    (bgBounds.width() - mFastScrollTextBounds.width()) / 2,
257f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                    bgBounds.height() - (bgBounds.height() - mFastScrollTextBounds.height()) / 2,
258f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                    mFastScrollTextPaint);
259f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            canvas.restoreToCount(restoreCount);
260f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        }
261f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    }
262f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
263f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    /**
264f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     * Draws the vertical scrollbar.
265f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     */
266f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    private void drawVerticalScrubber(Canvas canvas) {
267f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        updateVerticalScrollbarBounds();
268f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
269f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        // Draw the scroll bar
270f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
271f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        canvas.translate(mVerticalScrollbarBounds.left, mVerticalScrollbarBounds.top);
272f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        mScrollbar.setBounds(0, 0, mScrollbarWidth, mVerticalScrollbarBounds.height());
273f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        mScrollbar.draw(canvas);
274f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        canvas.restoreToCount(restoreCount);
275f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    }
276f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
277f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    /**
27893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Invalidates the fast scroller popup.
27993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
28093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private void invalidateFastScroller() {
28193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        invalidate(getWidth() - getPaddingRight() - getScrollBarSize() -
28293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                mFastScrollerBg.getIntrinsicWidth(), 0, getWidth(), getHeight());
28393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
28493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
28593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    /**
28693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     * Maps the progress (from 0..1) to the position that should be visible
28793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung     */
28893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    private String scrollToPositionAtProgress(float progress) {
28993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
29093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        // Get the total number of rows
291f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int rowCount = getNumRows();
29293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
29393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        // Find the index of the first app in that row and scroll to that position
29493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        int rowAtProgress = (int) (progress * rowCount);
29593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        int appIndex = 0;
29693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        rowCount = 0;
29793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        for (AlphabeticalAppsList.SectionInfo info : sections) {
29893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung            int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
29993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung            if (rowCount + numRowsInSection > rowAtProgress) {
30093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                appIndex += (rowAtProgress - rowCount) * mNumAppsPerRow;
30193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung                break;
30293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung            }
30393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung            rowCount += numRowsInSection;
30493f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung            appIndex += info.numAppsInSection;
30593f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        }
30693f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        appIndex = Math.max(0, Math.min(mApps.getAppsWithoutSectionBreaks().size() - 1, appIndex));
30793f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        AppInfo appInfo = mApps.getAppsWithoutSectionBreaks().get(appIndex);
30893f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        int sectionedAppIndex = mApps.getApps().indexOf(appInfo);
30993f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        scrollToPosition(sectionedAppIndex);
31093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung
31193f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        // Returns the section name of the row
31293f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung        return mApps.getSectionNameForApp(appInfo);
31393f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung    }
314f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
315f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    /**
316f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     * Returns the bounds for the scrollbar.
317f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     */
318f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    private void updateVerticalScrollbarBounds() {
319f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int x;
320f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int y;
321f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
322f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                LAYOUT_DIRECTION_RTL);
323f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
324f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        // Skip early if there are no items
325f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        if (mApps.getApps().isEmpty()) {
326f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            mVerticalScrollbarBounds.setEmpty();
327f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            return;
328f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        }
329f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
330f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        // Find the index and height of the first visible row (all rows have the same height)
331f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int rowIndex = -1;
332f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int rowTopOffset = -1;
333f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int rowHeight = -1;
334f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int rowCount = getNumRows();
335f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int childCount = getChildCount();
336f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        for (int i = 0; i < childCount; i++) {
337f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            View child = getChildAt(i);
338f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            int position = getChildPosition(child);
339f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            if (position != NO_POSITION) {
340f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                AppInfo info = mApps.getApps().get(position);
341f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                if (info != AlphabeticalAppsList.SECTION_BREAK_INFO) {
342f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                    int appIndex = mApps.getAppsWithoutSectionBreaks().indexOf(info);
343f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                    rowIndex = findRowForAppIndex(appIndex);
344f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                    rowTopOffset = getLayoutManager().getDecoratedTop(child);
345f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                    rowHeight = child.getHeight();
346f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                    break;
347f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                }
348f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            }
349f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        }
350f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
351f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        if (rowIndex != -1) {
352f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            int height = getHeight() - getPaddingTop() - getPaddingBottom();
353f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            int totalScrollHeight = rowCount * rowHeight;
354f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            if (totalScrollHeight > height) {
355f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                int scrollbarHeight = Math.max(mScrollbarMinHeight,
356f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                        (int) (height / ((float) totalScrollHeight / height)));
357f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
358f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                // Calculate the position and size of the scroll bar
359f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                if (isRtl) {
360f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                    x = getPaddingLeft();
361f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                } else {
362f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                    x = getWidth() - getPaddingRight() - mScrollbarWidth;
363f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                }
364f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
365f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                // To calculate the offset, we compute the percentage of the total scrollable height
366f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                // that the user has already scrolled and then map that to the scroll bar bounds
367f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                int availableY = totalScrollHeight - height;
368f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                int availableScrollY = height - scrollbarHeight;
369f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                y = (rowIndex * rowHeight) - rowTopOffset;
370f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                y = getPaddingTop() +
371f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                        (int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY);
372f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
373f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                mVerticalScrollbarBounds.set(x, y, x + mScrollbarWidth, y + scrollbarHeight);
374f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                return;
375f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            }
376f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        }
377f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        mVerticalScrollbarBounds.setEmpty();
378f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    }
379f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
380f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    /**
381f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     * Returns the row index for a given position in the list.
382f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     */
383f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    private int findRowForAppIndex(int position) {
384f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
385f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int appIndex = 0;
386f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int rowCount = 0;
387f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        for (AlphabeticalAppsList.SectionInfo info : sections) {
388f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
389f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            if (appIndex + info.numAppsInSection > position) {
390f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung                return rowCount + ((position - appIndex) / mNumAppsPerRow);
391f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            }
392f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            appIndex += info.numAppsInSection;
393f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            rowCount += numRowsInSection;
394f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        }
395f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        return appIndex;
396f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    }
397f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung
398f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    /**
399f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     * Returns the total number of rows in the list.
400f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung     */
401f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    private int getNumRows() {
402f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
403f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        int rowCount = 0;
404f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        for (AlphabeticalAppsList.SectionInfo info : sections) {
405f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
406f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung            rowCount += numRowsInSection;
407f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        }
408f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung        return rowCount;
409f819dc2bc782e93ac9ecc163a99af0da62821d31Winson Chung    }
41093f98eaf1800024cb2f28379bdd997f3debae63aWinson Chung}
411