1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3; 17 18 import android.animation.Animator; 19 import android.animation.ObjectAnimator; 20 import android.content.res.Resources; 21 import android.graphics.Bitmap; 22 import android.graphics.Canvas; 23 import android.graphics.Color; 24 import android.graphics.Paint; 25 import android.graphics.Rect; 26 import android.graphics.drawable.Drawable; 27 28 /** 29 * The fast scroller popup that shows the section name the list will jump to. 30 */ 31 public class BaseRecyclerViewFastScrollPopup { 32 33 private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f; 34 35 private static final int SHADOW_INSET = 3; 36 private static final int SHADOW_SHIFT_Y = 2; 37 private static final float SHADOW_ALPHA_MULTIPLIER = 0.67f; 38 39 private Resources mRes; 40 private BaseRecyclerView mRv; 41 42 private Bitmap mShadow; 43 private Paint mShadowPaint; 44 45 private Drawable mBg; 46 // The absolute bounds of the fast scroller bg 47 private Rect mBgBounds = new Rect(); 48 private int mBgOriginalSize; 49 private Rect mInvalidateRect = new Rect(); 50 private Rect mTmpRect = new Rect(); 51 52 private String mSectionName; 53 private Paint mTextPaint; 54 private Rect mTextBounds = new Rect(); 55 private float mAlpha; 56 57 private Animator mAlphaAnimator; 58 private boolean mVisible; 59 BaseRecyclerViewFastScrollPopup(BaseRecyclerView rv, Resources res)60 public BaseRecyclerViewFastScrollPopup(BaseRecyclerView rv, Resources res) { 61 mRes = res; 62 mRv = rv; 63 64 mBgOriginalSize = res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_size); 65 mBg = rv.getContext().getDrawable(R.drawable.container_fastscroll_popup_bg); 66 mBg.setBounds(0, 0, mBgOriginalSize, mBgOriginalSize); 67 68 mTextPaint = new Paint(); 69 mTextPaint.setColor(Color.WHITE); 70 mTextPaint.setAntiAlias(true); 71 mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.container_fastscroll_popup_text_size)); 72 73 mShadowPaint = new Paint(); 74 mShadowPaint.setAntiAlias(true); 75 mShadowPaint.setFilterBitmap(true); 76 mShadowPaint.setDither(true); 77 } 78 79 /** 80 * Sets the section name. 81 */ setSectionName(String sectionName)82 public void setSectionName(String sectionName) { 83 if (!sectionName.equals(mSectionName)) { 84 mSectionName = sectionName; 85 mTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTextBounds); 86 // Update the width to use measureText since that is more accurate 87 mTextBounds.right = (int) (mTextBounds.left + mTextPaint.measureText(sectionName)); 88 } 89 } 90 91 /** 92 * Updates the bounds for the fast scroller. 93 * 94 * @return the invalidation rect for this update. 95 */ updateFastScrollerBounds(int lastTouchY)96 public Rect updateFastScrollerBounds(int lastTouchY) { 97 mInvalidateRect.set(mBgBounds); 98 99 if (isVisible()) { 100 // Calculate the dimensions and position of the fast scroller popup 101 int edgePadding = mRv.getMaxScrollbarWidth(); 102 int bgPadding = (mBgOriginalSize - mTextBounds.height()) / 2; 103 int bgHeight = mBgOriginalSize; 104 int bgWidth = Math.max(mBgOriginalSize, mTextBounds.width() + (2 * bgPadding)); 105 if (Utilities.isRtl(mRes)) { 106 mBgBounds.left = mRv.getBackgroundPadding().left + (2 * mRv.getMaxScrollbarWidth()); 107 mBgBounds.right = mBgBounds.left + bgWidth; 108 } else { 109 mBgBounds.right = mRv.getWidth() - mRv.getBackgroundPadding().right - 110 (2 * mRv.getMaxScrollbarWidth()); 111 mBgBounds.left = mBgBounds.right - bgWidth; 112 } 113 mBgBounds.top = lastTouchY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgHeight); 114 mBgBounds.top = Math.max(edgePadding, 115 Math.min(mBgBounds.top, mRv.getVisibleHeight() - edgePadding - bgHeight)); 116 mBgBounds.bottom = mBgBounds.top + bgHeight; 117 118 // Generate a bitmap for a shadow matching these bounds 119 mShadow = HolographicOutlineHelper.obtain( 120 mRv.getContext()).createMediumDropShadow(mBg, false /* shouldCache */); 121 } else { 122 mShadow = null; 123 mBgBounds.setEmpty(); 124 } 125 126 // Combine the old and new fast scroller bounds to create the full invalidate rect 127 mInvalidateRect.union(mBgBounds); 128 return mInvalidateRect; 129 } 130 131 /** 132 * Animates the visibility of the fast scroller popup. 133 */ animateVisibility(boolean visible)134 public void animateVisibility(boolean visible) { 135 if (mVisible != visible) { 136 mVisible = visible; 137 if (mAlphaAnimator != null) { 138 mAlphaAnimator.cancel(); 139 } 140 mAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", visible ? 1f : 0f); 141 mAlphaAnimator.setDuration(visible ? 200 : 150); 142 mAlphaAnimator.start(); 143 } 144 } 145 146 // Setter/getter for the popup alpha for animations setAlpha(float alpha)147 public void setAlpha(float alpha) { 148 mAlpha = alpha; 149 mRv.invalidate(mBgBounds); 150 } 151 getAlpha()152 public float getAlpha() { 153 return mAlpha; 154 } 155 getHeight()156 public int getHeight() { 157 return mBgOriginalSize; 158 } 159 draw(Canvas c)160 public void draw(Canvas c) { 161 if (isVisible()) { 162 // Determine the alpha and prepare the canvas 163 final int alpha = (int) (mAlpha * 255); 164 int restoreCount = c.save(Canvas.MATRIX_SAVE_FLAG); 165 c.translate(mBgBounds.left, mBgBounds.top); 166 mTmpRect.set(mBgBounds); 167 mTmpRect.offsetTo(0, 0); 168 169 // Expand the rect (with a negative inset), translate it, and draw the shadow 170 if (mShadow != null) { 171 mTmpRect.inset(-SHADOW_INSET * 2, -SHADOW_INSET * 2); 172 mTmpRect.offset(0, SHADOW_SHIFT_Y); 173 mShadowPaint.setAlpha((int) (alpha * SHADOW_ALPHA_MULTIPLIER)); 174 c.drawBitmap(mShadow, null, mTmpRect, mShadowPaint); 175 mTmpRect.inset(SHADOW_INSET * 2, SHADOW_INSET * 2); 176 mTmpRect.offset(0, -SHADOW_SHIFT_Y); 177 } 178 179 // Draw the background 180 mBg.setBounds(mTmpRect); 181 mBg.setAlpha(alpha); 182 mBg.draw(c); 183 184 // Draw the text 185 mTextPaint.setAlpha(alpha); 186 c.drawText(mSectionName, (mBgBounds.width() - mTextBounds.width()) / 2, 187 mBgBounds.height() - (mBgBounds.height() / 2) - mTextBounds.exactCenterY(), 188 mTextPaint); 189 c.restoreToCount(restoreCount); 190 } 191 } 192 isVisible()193 public boolean isVisible() { 194 return (mAlpha > 0f) && (mSectionName != null); 195 } 196 } 197