/* * Copyright (C) 2016 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 android.view; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.util.DisplayMetrics; /** * Helper class for drawing round scroll bars on round Wear devices. */ class RoundScrollbarRenderer { // The range of the scrollbar position represented as an angle in degrees. private static final float SCROLLBAR_ANGLE_RANGE = 28.8f; private static final float MAX_SCROLLBAR_ANGLE_SWIPE = 26.3f; // 90% private static final float MIN_SCROLLBAR_ANGLE_SWIPE = 3.1f; // 10% private static final float THUMB_WIDTH_DP = 4f; private static final float OUTER_PADDING_DP = 2f; private static final int DEFAULT_THUMB_COLOR = 0xFFFFFFFF; private static final int DEFAULT_TRACK_COLOR = 0x4CFFFFFF; // Rate at which the scrollbar will resize itself when the size of the view changes private static final float RESIZING_RATE = 0.8f; // Threshold at which the scrollbar will stop resizing smoothly and jump to the correct size private static final int RESIZING_THRESHOLD_PX = 20; private final Paint mThumbPaint = new Paint(); private final Paint mTrackPaint = new Paint(); private final RectF mRect = new RectF(); private final View mParent; private final int mMaskThickness; private float mPreviousMaxScroll = 0; private float mMaxScrollDiff = 0; private float mPreviousCurrentScroll = 0; private float mCurrentScrollDiff = 0; public RoundScrollbarRenderer(View parent) { // Paints for the round scrollbar. // Set up the thumb paint mThumbPaint.setAntiAlias(true); mThumbPaint.setStrokeCap(Paint.Cap.ROUND); mThumbPaint.setStyle(Paint.Style.STROKE); // Set up the track paint mTrackPaint.setAntiAlias(true); mTrackPaint.setStrokeCap(Paint.Cap.ROUND); mTrackPaint.setStyle(Paint.Style.STROKE); mParent = parent; // Fetch the resource indicating the thickness of CircularDisplayMask, rounding in the same // way WindowManagerService.showCircularMask does. The scroll bar is inset by this amount so // that it doesn't get clipped. mMaskThickness = parent.getContext().getResources().getDimensionPixelSize( com.android.internal.R.dimen.circular_display_mask_thickness); } public void drawRoundScrollbars(Canvas canvas, float alpha, Rect bounds, boolean drawToLeft) { if (alpha == 0) { return; } // Get information about the current scroll state of the parent view. float maxScroll = mParent.computeVerticalScrollRange(); float scrollExtent = mParent.computeVerticalScrollExtent(); float newScroll = mParent.computeVerticalScrollOffset(); if (scrollExtent <= 0) { if (!mParent.canScrollVertically(1) && !mParent.canScrollVertically(-1)) { return; } else { scrollExtent = 0; } } else if (maxScroll <= scrollExtent) { return; } // Make changes to the VerticalScrollRange happen gradually if (Math.abs(maxScroll - mPreviousMaxScroll) > RESIZING_THRESHOLD_PX && mPreviousMaxScroll != 0) { mMaxScrollDiff += maxScroll - mPreviousMaxScroll; mCurrentScrollDiff += newScroll - mPreviousCurrentScroll; } mPreviousMaxScroll = maxScroll; mPreviousCurrentScroll = newScroll; if (Math.abs(mMaxScrollDiff) > RESIZING_THRESHOLD_PX || Math.abs(mCurrentScrollDiff) > RESIZING_THRESHOLD_PX) { mMaxScrollDiff *= RESIZING_RATE; mCurrentScrollDiff *= RESIZING_RATE; maxScroll -= mMaxScrollDiff; newScroll -= mCurrentScrollDiff; } else { mMaxScrollDiff = 0; mCurrentScrollDiff = 0; } float currentScroll = Math.max(0, newScroll); float linearThumbLength = scrollExtent; float thumbWidth = dpToPx(THUMB_WIDTH_DP); mThumbPaint.setStrokeWidth(thumbWidth); mTrackPaint.setStrokeWidth(thumbWidth); setThumbColor(applyAlpha(DEFAULT_THUMB_COLOR, alpha)); setTrackColor(applyAlpha(DEFAULT_TRACK_COLOR, alpha)); // Normalize the sweep angle for the scroll bar. float sweepAngle = (linearThumbLength / maxScroll) * SCROLLBAR_ANGLE_RANGE; sweepAngle = clamp(sweepAngle, MIN_SCROLLBAR_ANGLE_SWIPE, MAX_SCROLLBAR_ANGLE_SWIPE); // Normalize the start angle so that it falls on the track. float startAngle = (currentScroll * (SCROLLBAR_ANGLE_RANGE - sweepAngle)) / (maxScroll - linearThumbLength) - SCROLLBAR_ANGLE_RANGE / 2f; startAngle = clamp(startAngle, -SCROLLBAR_ANGLE_RANGE / 2f, SCROLLBAR_ANGLE_RANGE / 2f - sweepAngle); // Draw the track and the thumb. float inset = thumbWidth / 2 + mMaskThickness; mRect.set( bounds.left + inset, bounds.top + inset, bounds.right - inset, bounds.bottom - inset); if (drawToLeft) { canvas.drawArc(mRect, 180 + SCROLLBAR_ANGLE_RANGE / 2f, -SCROLLBAR_ANGLE_RANGE, false, mTrackPaint); canvas.drawArc(mRect, 180 - startAngle, -sweepAngle, false, mThumbPaint); } else { canvas.drawArc(mRect, -SCROLLBAR_ANGLE_RANGE / 2f, SCROLLBAR_ANGLE_RANGE, false, mTrackPaint); canvas.drawArc(mRect, startAngle, sweepAngle, false, mThumbPaint); } } void getRoundVerticalScrollBarBounds(Rect bounds) { float padding = dpToPx(OUTER_PADDING_DP); final int width = mParent.mRight - mParent.mLeft; final int height = mParent.mBottom - mParent.mTop; bounds.left = mParent.mScrollX + (int) padding; bounds.top = mParent.mScrollY + (int) padding; bounds.right = mParent.mScrollX + width - (int) padding; bounds.bottom = mParent.mScrollY + height - (int) padding; } private static float clamp(float val, float min, float max) { if (val < min) { return min; } else if (val > max) { return max; } else { return val; } } private static int applyAlpha(int color, float alpha) { int alphaByte = (int) (Color.alpha(color) * alpha); return Color.argb(alphaByte, Color.red(color), Color.green(color), Color.blue(color)); } private void setThumbColor(int thumbColor) { if (mThumbPaint.getColor() != thumbColor) { mThumbPaint.setColor(thumbColor); } } private void setTrackColor(int trackColor) { if (mTrackPaint.getColor() != trackColor) { mTrackPaint.setColor(trackColor); } } private float dpToPx(float dp) { return dp * ((float) mParent.getContext().getResources().getDisplayMetrics().densityDpi) / DisplayMetrics.DENSITY_DEFAULT; } }