1 /* 2 * Copyright (C) 2023 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.allapps; 17 18 import android.content.Context; 19 import android.graphics.Canvas; 20 import android.graphics.Paint; 21 import android.graphics.Path; 22 import android.graphics.RectF; 23 import android.graphics.drawable.GradientDrawable; 24 import android.graphics.drawable.InsetDrawable; 25 import android.view.View; 26 27 import androidx.annotation.Nullable; 28 29 import com.android.launcher3.R; 30 31 public class SectionDecorationHandler { 32 33 protected final Path mTmpPath = new Path(); 34 protected final RectF mTmpRect = new RectF(); 35 36 protected final int mCornerGroupRadius; 37 protected final int mCornerResultRadius; 38 protected final RectF mBounds = new RectF(); 39 protected final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 40 41 protected final int mFocusAlpha = 255; // main focused item alpha 42 protected int mFillColor; // grouping color 43 protected int mFocusColor; // main focused item color 44 protected float mFillSpacing; 45 protected int mInlineRadius; 46 protected Context mContext; 47 protected float[] mCorners; 48 protected int mFillAlpha; 49 protected boolean mIsTopLeftRound; 50 protected boolean mIsTopRightRound; 51 protected boolean mIsBottomLeftRound; 52 protected boolean mIsBottomRightRound; 53 protected boolean mIsBottomRound; 54 protected boolean mIsTopRound; 55 SectionDecorationHandler(Context context, int fillAlpha, boolean isTopLeftRound, boolean isTopRightRound, boolean isBottomLeftRound, boolean isBottomRightRound)56 public SectionDecorationHandler(Context context, int fillAlpha, boolean isTopLeftRound, 57 boolean isTopRightRound, boolean isBottomLeftRound, 58 boolean isBottomRightRound) { 59 60 mContext = context; 61 mFillAlpha = fillAlpha; 62 mFocusColor = context.getColor(R.color.materialColorSurfaceBright); // UX recommended 63 mFillColor = context.getColor(R.color.materialColorSurfaceContainerHigh); // UX recommended 64 65 mIsTopLeftRound = isTopLeftRound; 66 mIsTopRightRound = isTopRightRound; 67 mIsBottomLeftRound = isBottomLeftRound; 68 mIsBottomRightRound = isBottomRightRound; 69 mIsBottomRound = mIsBottomLeftRound && mIsBottomRightRound; 70 mIsTopRound = mIsTopLeftRound && mIsTopRightRound; 71 72 mCornerGroupRadius = context.getResources().getDimensionPixelSize( 73 R.dimen.all_apps_recycler_view_decorator_group_radius); 74 mCornerResultRadius = context.getResources().getDimensionPixelSize( 75 R.dimen.all_apps_recycler_view_decorator_result_radius); 76 77 mInlineRadius = 0; 78 mFillSpacing = 0; 79 initCorners(); 80 } 81 initCorners()82 protected void initCorners() { 83 mCorners = new float[]{ 84 mIsTopLeftRound ? mCornerGroupRadius : 0, 85 mIsTopLeftRound ? mCornerGroupRadius : 0, // Top left radius in px 86 mIsTopRightRound ? mCornerGroupRadius : 0, 87 mIsTopRightRound ? mCornerGroupRadius : 0, // Top right radius in px 88 mIsBottomRightRound ? mCornerGroupRadius : 0, 89 mIsBottomRightRound ? mCornerGroupRadius : 0, // Bottom right 90 mIsBottomLeftRound ? mCornerGroupRadius : 0, 91 mIsBottomLeftRound ? mCornerGroupRadius : 0 // Bottom left 92 }; 93 } 94 setFillAlpha(int fillAlpha)95 protected void setFillAlpha(int fillAlpha) { 96 mFillAlpha = fillAlpha; 97 mPaint.setAlpha(mFillAlpha); 98 } 99 onFocusDraw(Canvas canvas, @Nullable View view)100 protected void onFocusDraw(Canvas canvas, @Nullable View view) { 101 if (view == null) { 102 return; 103 } 104 mPaint.setColor(mFillColor); 105 mPaint.setAlpha(mFillAlpha); 106 int scaledHeight = (int) (view.getHeight() * view.getScaleY()); 107 mBounds.set(view.getLeft(), view.getY(), view.getRight(), view.getY() + scaledHeight); 108 onDraw(canvas); 109 } 110 onDraw(Canvas canvas)111 protected void onDraw(Canvas canvas) { 112 mTmpPath.reset(); 113 mTmpRect.set(mBounds.left + mFillSpacing, 114 mBounds.top + mFillSpacing, 115 mBounds.right - mFillSpacing, 116 mBounds.bottom - mFillSpacing); 117 mTmpPath.addRoundRect(mTmpRect, mCorners, Path.Direction.CW); 118 canvas.drawPath(mTmpPath, mPaint); 119 } 120 121 /** Sets the right background drawable to the view based on the give decoration info. */ applyBackground(View view, Context context, @Nullable SectionDecorationInfo decorationInfo, boolean isHighlighted)122 public void applyBackground(View view, Context context, 123 @Nullable SectionDecorationInfo decorationInfo, boolean isHighlighted) { 124 int inset = context.getResources().getDimensionPixelSize( 125 R.dimen.all_apps_recycler_view_decorator_padding); 126 float radiusBottom = (decorationInfo == null || decorationInfo.isBottomRound()) ? 127 mCornerGroupRadius : mCornerResultRadius; 128 float radiusTop = 129 (decorationInfo == null || decorationInfo.isTopRound()) ? 130 mCornerGroupRadius : mCornerResultRadius; 131 int color = isHighlighted ? mFocusColor : mFillColor; 132 133 GradientDrawable shape = new GradientDrawable(); 134 shape.setShape(GradientDrawable.RECTANGLE); 135 shape.setCornerRadii(new float[] { 136 radiusTop, radiusTop, // top-left 137 radiusTop, radiusTop, // top-right 138 radiusBottom, radiusBottom, // bottom-right 139 radiusBottom, radiusBottom // bottom-left 140 }); 141 shape.setColor(color); 142 143 // Setting the background resets the padding, so we cache it and reset it afterwards. 144 int paddingLeft = view.getPaddingLeft(); 145 int paddingTop = view.getPaddingTop(); 146 int paddingRight = view.getPaddingRight(); 147 int paddingBottom = view.getPaddingBottom(); 148 149 view.setBackground(new InsetDrawable(shape, inset)); 150 151 view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); 152 } 153 154 /** 155 * Section decorator that combines views and draws a single block decoration 156 */ 157 public static class UnionDecorationHandler extends SectionDecorationHandler { 158 159 private final int mPaddingLeft; 160 private final int mPaddingRight; 161 UnionDecorationHandler( SectionDecorationHandler decorationHandler, int paddingLeft, int paddingRight)162 public UnionDecorationHandler( 163 SectionDecorationHandler decorationHandler, 164 int paddingLeft, int paddingRight) { 165 super(decorationHandler.mContext, decorationHandler.mFillAlpha, 166 decorationHandler.mIsTopLeftRound, decorationHandler.mIsTopRightRound, 167 decorationHandler.mIsBottomLeftRound, decorationHandler.mIsBottomRightRound); 168 mPaddingLeft = paddingLeft; 169 mPaddingRight = paddingRight; 170 } 171 172 /** 173 * Expands decoration bounds to include child {@link PrivateAppsSectionDecorator} 174 */ addChild(SectionDecorationHandler child, View view)175 public void addChild(SectionDecorationHandler child, View view) { 176 int scaledHeight = (int) (view.getHeight() * view.getScaleY()); 177 mBounds.union(view.getLeft(), view.getY(), 178 view.getRight(), view.getY() + scaledHeight); 179 mIsBottomRound |= child.mIsBottomRound; 180 mIsBottomLeftRound |= child.mIsBottomLeftRound; 181 mIsBottomRightRound |= child.mIsBottomRightRound; 182 mIsTopRound |= child.mIsTopRound; 183 mIsTopLeftRound |= child.mIsTopLeftRound; 184 mIsTopRightRound |= child.mIsTopRightRound; 185 } 186 187 /** 188 * Draws group decoration to canvas 189 */ onGroupDecorate(Canvas canvas)190 public void onGroupDecorate(Canvas canvas) { 191 initCorners(); 192 mBounds.left = mPaddingLeft; 193 mBounds.right = canvas.getWidth() - mPaddingRight; 194 mPaint.setColor(mFillColor); 195 mPaint.setAlpha(mFillAlpha); 196 onDraw(canvas); 197 } 198 } 199 } 200