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