blob: edfa61b4ddc2eda558ecf828d862adb4dabba0de [file] [log] [blame]
Jorim Jaggibe565df2014-04-28 17:51:23 +02001/*
2 * Copyright (C) 2014 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
17package com.android.systemui.statusbar;
18
19import android.content.Context;
Anthony Chen7acbb772017-04-07 16:45:25 -070020import android.content.res.Resources;
Selim Cinek0fe07392017-11-09 13:26:34 -080021import android.graphics.Canvas;
Jorim Jaggibe565df2014-04-28 17:51:23 +020022import android.graphics.Outline;
Selim Cinek0fe07392017-11-09 13:26:34 -080023import android.graphics.Path;
Chris Craik7b7ca3c2014-07-11 13:32:19 -070024import android.graphics.Rect;
Selim Cinek8efa6dd2014-05-19 16:27:37 +020025import android.graphics.RectF;
Jorim Jaggibe565df2014-04-28 17:51:23 +020026import android.util.AttributeSet;
Chris Craik7b7ca3c2014-07-11 13:32:19 -070027import android.view.View;
28import android.view.ViewOutlineProvider;
Selim Cinek0fe07392017-11-09 13:26:34 -080029
30import com.android.settingslib.Utils;
Anthony Chen7acbb772017-04-07 16:45:25 -070031import com.android.systemui.R;
Selim Cinekd9b7dd42017-11-10 17:53:47 -080032import com.android.systemui.statusbar.notification.AnimatableProperty;
33import com.android.systemui.statusbar.notification.PropertyAnimator;
34import com.android.systemui.statusbar.stack.AnimationProperties;
35import com.android.systemui.statusbar.stack.StackStateAnimator;
Jorim Jaggibe565df2014-04-28 17:51:23 +020036
37/**
38 * Like {@link ExpandableView}, but setting an outline for the height and clipping.
39 */
40public abstract class ExpandableOutlineView extends ExpandableView {
41
Selim Cinekd9b7dd42017-11-10 17:53:47 -080042 private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from(
43 "topRoundness",
44 ExpandableOutlineView::setTopRoundnessInternal,
45 ExpandableOutlineView::getCurrentTopRoundness,
46 R.id.top_roundess_animator_tag,
47 R.id.top_roundess_animator_end_tag,
48 R.id.top_roundess_animator_start_tag);
49 private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from(
50 "bottomRoundness",
51 ExpandableOutlineView::setBottomRoundnessInternal,
52 ExpandableOutlineView::getCurrentBottomRoundness,
53 R.id.bottom_roundess_animator_tag,
54 R.id.bottom_roundess_animator_end_tag,
55 R.id.bottom_roundess_animator_start_tag);
56 private static final AnimationProperties ROUNDNESS_PROPERTIES =
57 new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
Selim Cinek515b2032017-11-15 10:20:19 -080058 private static final Path EMPTY_PATH = new Path();
Selim Cinekd9b7dd42017-11-10 17:53:47 -080059
Chris Craik7b7ca3c2014-07-11 13:32:19 -070060 private final Rect mOutlineRect = new Rect();
Selim Cinekb0ee18f2017-12-21 16:15:53 -080061 private final Path mClipPath = new Path();
Selim Cinek8efa6dd2014-05-19 16:27:37 +020062 private boolean mCustomOutline;
Selim Cinekd35c2792016-01-21 13:20:57 -080063 private float mOutlineAlpha = -1f;
Selim Cinekc25989e2018-02-16 16:42:14 -080064 protected float mOutlineRadius;
Selim Cinek0fe07392017-11-09 13:26:34 -080065 private boolean mAlwaysRoundBothCorners;
66 private Path mTmpPath = new Path();
67 private Path mTmpPath2 = new Path();
Selim Cinekd9b7dd42017-11-10 17:53:47 -080068 private float mCurrentBottomRoundness;
69 private float mCurrentTopRoundness;
70 private float mBottomRoundness;
Selim Cinek0fe07392017-11-09 13:26:34 -080071 private float mTopRoundness;
Selim Cinek515b2032017-11-15 10:20:19 -080072 private int mBackgroundTop;
Jorim Jaggibe565df2014-04-28 17:51:23 +020073
Anthony Chen7acbb772017-04-07 16:45:25 -070074 /**
75 * {@code true} if the children views of the {@link ExpandableOutlineView} are translated when
76 * it is moved. Otherwise, the translation is set on the {@code ExpandableOutlineView} itself.
77 */
78 protected boolean mShouldTranslateContents;
Tony Huangc092c432018-05-18 17:38:54 +080079 private boolean mTopAmountRounded;
Selim Cinekb0ee18f2017-12-21 16:15:53 -080080 private float mDistanceToTopRoundness = -1;
Selim Cinekc25989e2018-02-16 16:42:14 -080081 private float mExtraWidthForClipping;
82 private int mMinimumHeightForClipping = 0;
Anthony Chen7acbb772017-04-07 16:45:25 -070083
84 private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
Mady Mellorb0a82462016-04-30 17:31:02 -070085 @Override
86 public void getOutline(View view, Outline outline) {
Selim Cinek1a891a92017-12-04 17:41:27 +010087 if (!mCustomOutline && mCurrentTopRoundness == 0.0f
Tony Huangc092c432018-05-18 17:38:54 +080088 && mCurrentBottomRoundness == 0.0f && !mAlwaysRoundBothCorners
89 && !mTopAmountRounded) {
Selim Cinek1a891a92017-12-04 17:41:27 +010090 int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
Selim Cinekb95fd182017-12-21 13:03:32 -080091 int left = Math.max(translation, 0);
Selim Cinek1a891a92017-12-04 17:41:27 +010092 int top = mClipTopAmount + mBackgroundTop;
Selim Cinekb95fd182017-12-21 13:03:32 -080093 int right = getWidth() + Math.min(translation, 0);
Selim Cinek1a891a92017-12-04 17:41:27 +010094 int bottom = Math.max(getActualHeight() - mClipBottomAmount, top);
95 outline.setRect(left, top, right, bottom);
96 } else {
97 Path clipPath = getClipPath();
98 if (clipPath != null && clipPath.isConvex()) {
99 // The path might not be convex in border cases where the view is small and
100 // clipped
101 outline.setConvexPath(clipPath);
102 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700103 }
104 outline.setAlpha(mOutlineAlpha);
105 }
106 };
107
Selim Cinek0fe07392017-11-09 13:26:34 -0800108 private Path getClipPath() {
Selim Cinek2871bef2017-11-22 08:40:00 -0800109 return getClipPath(false, /* ignoreTranslation */
110 false /* clipRoundedToBottom */);
Selim Cinek515b2032017-11-15 10:20:19 -0800111 }
112
Selim Cinek2871bef2017-11-22 08:40:00 -0800113 protected Path getClipPath(boolean ignoreTranslation, boolean clipRoundedToBottom) {
Selim Cinek0fe07392017-11-09 13:26:34 -0800114 int left;
115 int top;
116 int right;
117 int bottom;
118 int height;
119 Path intersectPath = null;
120 if (!mCustomOutline) {
Selim Cinek515b2032017-11-15 10:20:19 -0800121 int translation = mShouldTranslateContents && !ignoreTranslation
122 ? (int) getTranslation() : 0;
Selim Cinekb95fd182017-12-21 13:03:32 -0800123 left = Math.max(translation, 0);
Selim Cinek515b2032017-11-15 10:20:19 -0800124 top = mClipTopAmount + mBackgroundTop;
Selim Cinekb95fd182017-12-21 13:03:32 -0800125 right = getWidth() + Math.min(translation, 0);
Selim Cinek0fe07392017-11-09 13:26:34 -0800126 bottom = Math.max(getActualHeight(), top);
127 int intersectBottom = Math.max(getActualHeight() - mClipBottomAmount, top);
128 if (bottom != intersectBottom) {
Selim Cinek2871bef2017-11-22 08:40:00 -0800129 if (clipRoundedToBottom) {
130 bottom = intersectBottom;
131 } else {
132 getRoundedRectPath(left, top, right,
133 intersectBottom, 0.0f,
134 0.0f, mTmpPath2);
135 intersectPath = mTmpPath2;
136 }
Selim Cinek0fe07392017-11-09 13:26:34 -0800137 }
138 } else {
139 left = mOutlineRect.left;
140 top = mOutlineRect.top;
141 right = mOutlineRect.right;
142 bottom = mOutlineRect.bottom;
143 }
144 height = bottom - top;
145 if (height == 0) {
Selim Cinek515b2032017-11-15 10:20:19 -0800146 return EMPTY_PATH;
Selim Cinek0fe07392017-11-09 13:26:34 -0800147 }
148 float topRoundness = mAlwaysRoundBothCorners
Tony Huangc092c432018-05-18 17:38:54 +0800149 ? mOutlineRadius : getCurrentBackgroundRadiusTop();
Selim Cinek0fe07392017-11-09 13:26:34 -0800150 float bottomRoundness = mAlwaysRoundBothCorners
Tony Huangc092c432018-05-18 17:38:54 +0800151 ? mOutlineRadius : getCurrentBackgroundRadiusBottom();
Selim Cinek0fe07392017-11-09 13:26:34 -0800152 if (topRoundness + bottomRoundness > height) {
153 float overShoot = topRoundness + bottomRoundness - height;
Selim Cinekd9b7dd42017-11-10 17:53:47 -0800154 topRoundness -= overShoot * mCurrentTopRoundness
155 / (mCurrentTopRoundness + mCurrentBottomRoundness);
156 bottomRoundness -= overShoot * mCurrentBottomRoundness
157 / (mCurrentTopRoundness + mCurrentBottomRoundness);
Selim Cinek0fe07392017-11-09 13:26:34 -0800158 }
159 getRoundedRectPath(left, top, right, bottom, topRoundness,
160 bottomRoundness, mTmpPath);
161 Path roundedRectPath = mTmpPath;
162 if (intersectPath != null) {
163 roundedRectPath.op(intersectPath, Path.Op.INTERSECT);
164 }
165 return roundedRectPath;
166 }
167
Selim Cinekb0ee18f2017-12-21 16:15:53 -0800168 public static void getRoundedRectPath(int left, int top, int right, int bottom,
169 float topRoundness, float bottomRoundness, Path outPath) {
Selim Cinek0fe07392017-11-09 13:26:34 -0800170 outPath.reset();
171 int width = right - left;
172 float topRoundnessX = topRoundness;
173 float bottomRoundnessX = bottomRoundness;
174 topRoundnessX = Math.min(width / 2, topRoundnessX);
175 bottomRoundnessX = Math.min(width / 2, bottomRoundnessX);
176 if (topRoundness > 0.0f) {
177 outPath.moveTo(left, top + topRoundness);
178 outPath.quadTo(left, top, left + topRoundnessX, top);
179 outPath.lineTo(right - topRoundnessX, top);
180 outPath.quadTo(right, top, right, top + topRoundness);
181 } else {
182 outPath.moveTo(left, top);
183 outPath.lineTo(right, top);
184 }
185 if (bottomRoundness > 0.0f) {
186 outPath.lineTo(right, bottom - bottomRoundness);
187 outPath.quadTo(right, bottom, right - bottomRoundnessX, bottom);
188 outPath.lineTo(left + bottomRoundnessX, bottom);
189 outPath.quadTo(left, bottom, left, bottom - bottomRoundness);
190 } else {
191 outPath.lineTo(right, bottom);
192 outPath.lineTo(left, bottom);
193 }
194 outPath.close();
195 }
196
Jorim Jaggibe565df2014-04-28 17:51:23 +0200197 public ExpandableOutlineView(Context context, AttributeSet attrs) {
198 super(context, attrs);
Mady Mellorb0a82462016-04-30 17:31:02 -0700199 setOutlineProvider(mProvider);
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700200 initDimens();
201 }
202
Selim Cinek0fe07392017-11-09 13:26:34 -0800203 @Override
Selim Cinek515b2032017-11-15 10:20:19 -0800204 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
Selim Cinek0fe07392017-11-09 13:26:34 -0800205 canvas.save();
Selim Cinekb0ee18f2017-12-21 16:15:53 -0800206 Path intersectPath = null;
Tony Huangc092c432018-05-18 17:38:54 +0800207 if (mTopAmountRounded && topAmountNeedsClipping()) {
Selim Cinekc25989e2018-02-16 16:42:14 -0800208 int left = (int) (- mExtraWidthForClipping / 2.0f);
Selim Cinekb0ee18f2017-12-21 16:15:53 -0800209 int top = (int) (mClipTopAmount - mDistanceToTopRoundness);
Selim Cinekc25989e2018-02-16 16:42:14 -0800210 int right = getWidth() + (int) (mExtraWidthForClipping + left);
211 int bottom = (int) Math.max(mMinimumHeightForClipping,
212 Math.max(getActualHeight() - mClipBottomAmount, top + mOutlineRadius));
Selim Cinekb0ee18f2017-12-21 16:15:53 -0800213 ExpandableOutlineView.getRoundedRectPath(left, top, right, bottom, mOutlineRadius,
214 0.0f,
215 mClipPath);
216 intersectPath = mClipPath;
217 }
218 boolean clipped = false;
Selim Cinek515b2032017-11-15 10:20:19 -0800219 if (childNeedsClipping(child)) {
220 Path clipPath = getCustomClipPath(child);
Selim Cinek0fe07392017-11-09 13:26:34 -0800221 if (clipPath == null) {
222 clipPath = getClipPath();
223 }
224 if (clipPath != null) {
Selim Cinekb0ee18f2017-12-21 16:15:53 -0800225 if (intersectPath != null) {
226 clipPath.op(intersectPath, Path.Op.INTERSECT);
227 }
Selim Cinek0fe07392017-11-09 13:26:34 -0800228 canvas.clipPath(clipPath);
Selim Cinekb0ee18f2017-12-21 16:15:53 -0800229 clipped = true;
Selim Cinek0fe07392017-11-09 13:26:34 -0800230 }
231 }
Selim Cinekb0ee18f2017-12-21 16:15:53 -0800232 if (!clipped && intersectPath != null) {
233 canvas.clipPath(intersectPath);
234 }
Selim Cinek515b2032017-11-15 10:20:19 -0800235 boolean result = super.drawChild(canvas, child, drawingTime);
Selim Cinek0fe07392017-11-09 13:26:34 -0800236 canvas.restore();
Selim Cinek515b2032017-11-15 10:20:19 -0800237 return result;
Selim Cinek0fe07392017-11-09 13:26:34 -0800238 }
239
Selim Cinekc25989e2018-02-16 16:42:14 -0800240 public void setExtraWidthForClipping(float extraWidthForClipping) {
241 mExtraWidthForClipping = extraWidthForClipping;
242 }
243
244 public void setMinimumHeightForClipping(int minimumHeightForClipping) {
245 mMinimumHeightForClipping = minimumHeightForClipping;
246 }
247
Selim Cinekb0ee18f2017-12-21 16:15:53 -0800248 @Override
249 public void setDistanceToTopRoundness(float distanceToTopRoundness) {
250 super.setDistanceToTopRoundness(distanceToTopRoundness);
251 if (distanceToTopRoundness != mDistanceToTopRoundness) {
Tony Huangc092c432018-05-18 17:38:54 +0800252 mTopAmountRounded = distanceToTopRoundness >= 0;
Selim Cinekb0ee18f2017-12-21 16:15:53 -0800253 mDistanceToTopRoundness = distanceToTopRoundness;
Tony Huangc092c432018-05-18 17:38:54 +0800254 applyRoundness();
Selim Cinekb0ee18f2017-12-21 16:15:53 -0800255 }
256 }
257
Selim Cinek515b2032017-11-15 10:20:19 -0800258 protected boolean childNeedsClipping(View child) {
Selim Cinek0fe07392017-11-09 13:26:34 -0800259 return false;
260 }
261
Tony Huangc092c432018-05-18 17:38:54 +0800262 public boolean topAmountNeedsClipping() {
263 return true;
264 }
265
Selim Cinek515b2032017-11-15 10:20:19 -0800266 protected boolean isClippingNeeded() {
267 return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ;
Selim Cinek515b2032017-11-15 10:20:19 -0800268 }
269
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700270 private void initDimens() {
Anthony Chen7acbb772017-04-07 16:45:25 -0700271 Resources res = getResources();
272 mShouldTranslateContents =
273 res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe);
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700274 mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
Selim Cinek0fe07392017-11-09 13:26:34 -0800275 mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
276 if (!mAlwaysRoundBothCorners) {
277 mOutlineRadius = res.getDimensionPixelSize(
278 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
279 }
280 setClipToOutline(mAlwaysRoundBothCorners);
281 }
282
Selim Cinek29aab962018-02-27 17:05:45 -0800283 /**
284 * Set the topRoundness of this view.
285 * @return Whether the roundness was changed.
286 */
287 public boolean setTopRoundness(float topRoundness, boolean animate) {
Selim Cinek0fe07392017-11-09 13:26:34 -0800288 if (mTopRoundness != topRoundness) {
289 mTopRoundness = topRoundness;
Selim Cinekd9b7dd42017-11-10 17:53:47 -0800290 PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness,
291 ROUNDNESS_PROPERTIES, animate);
Selim Cinek29aab962018-02-27 17:05:45 -0800292 return true;
Selim Cinek0fe07392017-11-09 13:26:34 -0800293 }
Selim Cinek29aab962018-02-27 17:05:45 -0800294 return false;
Selim Cinek0fe07392017-11-09 13:26:34 -0800295 }
296
297 protected void applyRoundness() {
298 invalidateOutline();
299 invalidate();
300 }
301
Selim Cinek515b2032017-11-15 10:20:19 -0800302 public float getCurrentBackgroundRadiusTop() {
Tony Huangc092c432018-05-18 17:38:54 +0800303 // If this view is top amount notification view, it should always has round corners on top.
304 // It will be applied with applyRoundness()
305 if (mTopAmountRounded) {
306 return mOutlineRadius;
307 }
Selim Cinekd9b7dd42017-11-10 17:53:47 -0800308 return mCurrentTopRoundness * mOutlineRadius;
Selim Cinek0fe07392017-11-09 13:26:34 -0800309 }
310
Selim Cinek515b2032017-11-15 10:20:19 -0800311 public float getCurrentTopRoundness() {
Selim Cinekd9b7dd42017-11-10 17:53:47 -0800312 return mCurrentTopRoundness;
Selim Cinek0fe07392017-11-09 13:26:34 -0800313 }
314
Selim Cinek2871bef2017-11-22 08:40:00 -0800315 public float getCurrentBottomRoundness() {
Selim Cinekd9b7dd42017-11-10 17:53:47 -0800316 return mCurrentBottomRoundness;
Selim Cinek0fe07392017-11-09 13:26:34 -0800317 }
318
Selim Cinekd9b7dd42017-11-10 17:53:47 -0800319 protected float getCurrentBackgroundRadiusBottom() {
320 return mCurrentBottomRoundness * mOutlineRadius;
321 }
322
Selim Cinek29aab962018-02-27 17:05:45 -0800323 /**
324 * Set the bottom roundness of this view.
325 * @return Whether the roundness was changed.
326 */
327 public boolean setBottomRoundness(float bottomRoundness, boolean animate) {
Selim Cinekd9b7dd42017-11-10 17:53:47 -0800328 if (mBottomRoundness != bottomRoundness) {
329 mBottomRoundness = bottomRoundness;
330 PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness,
331 ROUNDNESS_PROPERTIES, animate);
Selim Cinek29aab962018-02-27 17:05:45 -0800332 return true;
Selim Cinek0fe07392017-11-09 13:26:34 -0800333 }
Selim Cinek29aab962018-02-27 17:05:45 -0800334 return false;
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700335 }
336
Selim Cinek515b2032017-11-15 10:20:19 -0800337 protected void setBackgroundTop(int backgroundTop) {
338 if (mBackgroundTop != backgroundTop) {
339 mBackgroundTop = backgroundTop;
340 invalidateOutline();
341 }
342 }
Selim Cinekd9b7dd42017-11-10 17:53:47 -0800343
344 private void setTopRoundnessInternal(float topRoundness) {
345 mCurrentTopRoundness = topRoundness;
346 applyRoundness();
347 }
348
349 private void setBottomRoundnessInternal(float bottomRoundness) {
350 mCurrentBottomRoundness = bottomRoundness;
351 applyRoundness();
352 }
353
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700354 public void onDensityOrFontScaleChanged() {
355 initDimens();
Selim Cinek0fe07392017-11-09 13:26:34 -0800356 applyRoundness();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200357 }
358
359 @Override
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200360 public void setActualHeight(int actualHeight, boolean notifyListeners) {
Selim Cinek0fe07392017-11-09 13:26:34 -0800361 int previousHeight = getActualHeight();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200362 super.setActualHeight(actualHeight, notifyListeners);
Selim Cinek0fe07392017-11-09 13:26:34 -0800363 if (previousHeight != actualHeight) {
364 applyRoundness();
365 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200366 }
367
368 @Override
369 public void setClipTopAmount(int clipTopAmount) {
Selim Cinek0fe07392017-11-09 13:26:34 -0800370 int previousAmount = getClipTopAmount();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200371 super.setClipTopAmount(clipTopAmount);
Selim Cinek0fe07392017-11-09 13:26:34 -0800372 if (previousAmount != clipTopAmount) {
373 applyRoundness();
374 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200375 }
376
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800377 @Override
378 public void setClipBottomAmount(int clipBottomAmount) {
Selim Cinek0fe07392017-11-09 13:26:34 -0800379 int previousAmount = getClipBottomAmount();
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800380 super.setClipBottomAmount(clipBottomAmount);
Selim Cinek0fe07392017-11-09 13:26:34 -0800381 if (previousAmount != clipBottomAmount) {
382 applyRoundness();
383 }
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800384 }
385
Jorim Jaggi83eb6bb2015-08-17 17:38:58 -0700386 protected void setOutlineAlpha(float alpha) {
Selim Cinekd35c2792016-01-21 13:20:57 -0800387 if (alpha != mOutlineAlpha) {
388 mOutlineAlpha = alpha;
Selim Cinek0fe07392017-11-09 13:26:34 -0800389 applyRoundness();
Selim Cinekd35c2792016-01-21 13:20:57 -0800390 }
Jorim Jaggi83eb6bb2015-08-17 17:38:58 -0700391 }
392
Selim Cinek33223572016-02-19 19:32:22 -0800393 @Override
394 public float getOutlineAlpha() {
395 return mOutlineAlpha;
396 }
397
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200398 protected void setOutlineRect(RectF rect) {
399 if (rect != null) {
400 setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
401 } else {
402 mCustomOutline = false;
Selim Cinek0fe07392017-11-09 13:26:34 -0800403 applyRoundness();
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200404 }
405 }
406
Selim Cinek33223572016-02-19 19:32:22 -0800407 @Override
408 public int getOutlineTranslation() {
Mady Mellorb0a82462016-04-30 17:31:02 -0700409 return mCustomOutline ? mOutlineRect.left : (int) getTranslation();
410 }
411
412 public void updateOutline() {
413 if (mCustomOutline) {
414 return;
415 }
Selim Cinekad7fac02016-10-18 17:09:15 -0700416 boolean hasOutline = needsOutline();
Mady Mellorb0a82462016-04-30 17:31:02 -0700417 setOutlineProvider(hasOutline ? mProvider : null);
418 }
419
Selim Cinekad7fac02016-10-18 17:09:15 -0700420 /**
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700421 * @return Whether the view currently needs an outline. This is usually {@code false} in case
422 * it doesn't have a background.
Selim Cinekad7fac02016-10-18 17:09:15 -0700423 */
424 protected boolean needsOutline() {
425 if (isChildInGroup()) {
426 return isGroupExpanded() && !isGroupExpansionChanging();
427 } else if (isSummaryWithChildren()) {
428 return !isGroupExpanded() || isGroupExpansionChanging();
429 }
430 return true;
431 }
432
Mady Mellorb0a82462016-04-30 17:31:02 -0700433 public boolean isOutlineShowing() {
434 ViewOutlineProvider op = getOutlineProvider();
435 return op != null;
Selim Cinek33223572016-02-19 19:32:22 -0800436 }
437
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200438 protected void setOutlineRect(float left, float top, float right, float bottom) {
439 mCustomOutline = true;
440
Chris Craik7b7ca3c2014-07-11 13:32:19 -0700441 mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200442
443 // Outlines need to be at least 1 dp
Selim Cinek3969ffa2014-09-01 23:08:39 +0200444 mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
445 mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);
Selim Cinek0fe07392017-11-09 13:26:34 -0800446 applyRoundness();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200447 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200448
Selim Cinek515b2032017-11-15 10:20:19 -0800449 public Path getCustomClipPath(View child) {
Selim Cinek0fe07392017-11-09 13:26:34 -0800450 return null;
451 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200452}