Loading core/java/android/view/RoundScrollbarRenderer.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * 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.RectF; /** * 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 int SCROLLBAR_ANGLE_RANGE = 90; private static final int MAX_SCROLLBAR_ANGLE_SWIPE = 16; private static final int MIN_SCROLLBAR_ANGLE_SWIPE = 6; private static final float WIDTH_PERCENTAGE = 0.02f; private static final int DEFAULT_THUMB_COLOR = 0xFF757575; private static final int DEFAULT_TRACK_COLOR = 0x21FFFFFF; private final Paint mThumbPaint = new Paint(); private final Paint mTrackPaint = new Paint(); private final RectF mRect = new RectF(); private final View mParent; 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; } public void drawRoundScrollbars(Canvas canvas, float alpha) { if (alpha == 0) { return; } // Get information about the current scroll state of the parent view. float maxScroll = mParent.computeVerticalScrollRange(); float scrollExtent = mParent.computeVerticalScrollExtent(); if (scrollExtent <= 0 || maxScroll <= scrollExtent) { return; } float currentScroll = Math.max(0, mParent.computeVerticalScrollOffset()); float linearThumbLength = mParent.computeVerticalScrollExtent(); float thumbWidth = mParent.getWidth() * WIDTH_PERCENTAGE; 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 / 2; startAngle = clamp(startAngle, -SCROLLBAR_ANGLE_RANGE / 2, SCROLLBAR_ANGLE_RANGE / 2 - sweepAngle); // Draw the track and the scroll bar. mRect.set( 0 + thumbWidth / 2, 0 + thumbWidth / 2, mParent.getWidth() - thumbWidth / 2, mParent.getHeight() - thumbWidth / 2); canvas.drawArc(mRect, -SCROLLBAR_ANGLE_RANGE / 2, SCROLLBAR_ANGLE_RANGE, false, mTrackPaint); canvas.drawArc(mRect, startAngle, sweepAngle, false, mThumbPaint); } 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); } } } core/java/android/view/View.java +56 −6 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Insets; import android.graphics.Interpolator; import android.graphics.LinearGradient; Loading Loading @@ -3991,6 +3992,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ String mStartActivityRequestWho; @Nullable private RoundScrollbarRenderer mRoundScrollbarRenderer; /** * Simple constructor to use when creating a view from code. * Loading Loading @@ -14800,6 +14804,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, protected final void onDrawScrollBars(Canvas canvas) { // scrollbars are drawn only when the animation is running final ScrollabilityCache cache = mScrollCache; if (cache != null) { int state = cache.state; Loading Loading @@ -14840,7 +14845,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden(); if (drawVerticalScrollBar || drawHorizontalScrollBar) { // Fork out the scroll bar drawing for round wearable devices. if (mRoundScrollbarRenderer != null) { if (drawVerticalScrollBar) { mRoundScrollbarRenderer.drawRoundScrollbars( canvas, (float) cache.scrollBar.getAlpha() / 255f); if (invalidate) { invalidate(); } } // Do not draw horizontal scroll bars for round wearable devices. } else if (drawVerticalScrollBar || drawHorizontalScrollBar) { final ScrollBarDrawable scrollBar = cache.scrollBar; if (drawHorizontalScrollBar) { Loading Loading @@ -17570,6 +17585,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); if (shouldDrawRoundScrollbar()) { if(mRoundScrollbarRenderer == null) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); } } else { mRoundScrollbarRenderer = null; } mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; Loading Loading @@ -22950,7 +22974,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final int[] mInvalidateChildLocation = new int[2]; /** * Global to the view hierarchy used as a temporary for dealng with * Global to the view hierarchy used as a temporary for dealing with * computing absolute on-screen location. */ final int[] mTmpLocation = new int[2]; Loading Loading @@ -23788,4 +23812,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, stream.addProperty("accessibility:labelFor", getLabelFor()); stream.addProperty("accessibility:importantForAccessibility", getImportantForAccessibility()); } /** * Determine if this view is rendered on a round wearable device and is the main view * on the screen. */ private boolean shouldDrawRoundScrollbar() { if (!mResources.getConfiguration().isScreenRound()) { return false; } final View rootView = getRootView(); final WindowInsets insets = getRootWindowInsets(); int height = getHeight(); int width = getWidth(); int displayHeight = rootView.getHeight(); int displayWidth = rootView.getWidth(); if (height != displayHeight || width != displayWidth) { return false; } getLocationOnScreen(mAttachInfo.mTmpLocation); return mAttachInfo.mTmpLocation[0] == insets.getStableInsetLeft() && mAttachInfo.mTmpLocation[1] == insets.getStableInsetTop(); } } Loading
core/java/android/view/RoundScrollbarRenderer.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * 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.RectF; /** * 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 int SCROLLBAR_ANGLE_RANGE = 90; private static final int MAX_SCROLLBAR_ANGLE_SWIPE = 16; private static final int MIN_SCROLLBAR_ANGLE_SWIPE = 6; private static final float WIDTH_PERCENTAGE = 0.02f; private static final int DEFAULT_THUMB_COLOR = 0xFF757575; private static final int DEFAULT_TRACK_COLOR = 0x21FFFFFF; private final Paint mThumbPaint = new Paint(); private final Paint mTrackPaint = new Paint(); private final RectF mRect = new RectF(); private final View mParent; 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; } public void drawRoundScrollbars(Canvas canvas, float alpha) { if (alpha == 0) { return; } // Get information about the current scroll state of the parent view. float maxScroll = mParent.computeVerticalScrollRange(); float scrollExtent = mParent.computeVerticalScrollExtent(); if (scrollExtent <= 0 || maxScroll <= scrollExtent) { return; } float currentScroll = Math.max(0, mParent.computeVerticalScrollOffset()); float linearThumbLength = mParent.computeVerticalScrollExtent(); float thumbWidth = mParent.getWidth() * WIDTH_PERCENTAGE; 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 / 2; startAngle = clamp(startAngle, -SCROLLBAR_ANGLE_RANGE / 2, SCROLLBAR_ANGLE_RANGE / 2 - sweepAngle); // Draw the track and the scroll bar. mRect.set( 0 + thumbWidth / 2, 0 + thumbWidth / 2, mParent.getWidth() - thumbWidth / 2, mParent.getHeight() - thumbWidth / 2); canvas.drawArc(mRect, -SCROLLBAR_ANGLE_RANGE / 2, SCROLLBAR_ANGLE_RANGE, false, mTrackPaint); canvas.drawArc(mRect, startAngle, sweepAngle, false, mThumbPaint); } 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); } } }
core/java/android/view/View.java +56 −6 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Insets; import android.graphics.Interpolator; import android.graphics.LinearGradient; Loading Loading @@ -3991,6 +3992,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ String mStartActivityRequestWho; @Nullable private RoundScrollbarRenderer mRoundScrollbarRenderer; /** * Simple constructor to use when creating a view from code. * Loading Loading @@ -14800,6 +14804,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, protected final void onDrawScrollBars(Canvas canvas) { // scrollbars are drawn only when the animation is running final ScrollabilityCache cache = mScrollCache; if (cache != null) { int state = cache.state; Loading Loading @@ -14840,7 +14845,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden(); if (drawVerticalScrollBar || drawHorizontalScrollBar) { // Fork out the scroll bar drawing for round wearable devices. if (mRoundScrollbarRenderer != null) { if (drawVerticalScrollBar) { mRoundScrollbarRenderer.drawRoundScrollbars( canvas, (float) cache.scrollBar.getAlpha() / 255f); if (invalidate) { invalidate(); } } // Do not draw horizontal scroll bars for round wearable devices. } else if (drawVerticalScrollBar || drawHorizontalScrollBar) { final ScrollBarDrawable scrollBar = cache.scrollBar; if (drawHorizontalScrollBar) { Loading Loading @@ -17570,6 +17585,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); if (shouldDrawRoundScrollbar()) { if(mRoundScrollbarRenderer == null) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); } } else { mRoundScrollbarRenderer = null; } mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; Loading Loading @@ -22950,7 +22974,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final int[] mInvalidateChildLocation = new int[2]; /** * Global to the view hierarchy used as a temporary for dealng with * Global to the view hierarchy used as a temporary for dealing with * computing absolute on-screen location. */ final int[] mTmpLocation = new int[2]; Loading Loading @@ -23788,4 +23812,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, stream.addProperty("accessibility:labelFor", getLabelFor()); stream.addProperty("accessibility:importantForAccessibility", getImportantForAccessibility()); } /** * Determine if this view is rendered on a round wearable device and is the main view * on the screen. */ private boolean shouldDrawRoundScrollbar() { if (!mResources.getConfiguration().isScreenRound()) { return false; } final View rootView = getRootView(); final WindowInsets insets = getRootWindowInsets(); int height = getHeight(); int width = getWidth(); int displayHeight = rootView.getHeight(); int displayWidth = rootView.getWidth(); if (height != displayHeight || width != displayWidth) { return false; } getLocationOnScreen(mAttachInfo.mTmpLocation); return mAttachInfo.mTmpLocation[0] == insets.getStableInsetLeft() && mAttachInfo.mTmpLocation[1] == insets.getStableInsetTop(); } }