1 /* 2 * Copyright (C) 2019 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 17 package com.android.launcher3.appprediction; 18 19 import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT; 20 21 import android.annotation.TargetApi; 22 import android.content.Context; 23 import android.graphics.Canvas; 24 import android.graphics.Typeface; 25 import android.os.Build; 26 import android.text.Layout; 27 import android.text.StaticLayout; 28 import android.text.TextPaint; 29 import android.util.AttributeSet; 30 import android.view.View; 31 32 import androidx.annotation.ColorInt; 33 import androidx.core.content.ContextCompat; 34 35 import com.android.launcher3.R; 36 import com.android.launcher3.allapps.FloatingHeaderRow; 37 import com.android.launcher3.allapps.FloatingHeaderView; 38 import com.android.launcher3.util.OnboardingPrefs; 39 import com.android.launcher3.util.Themes; 40 import com.android.launcher3.views.ActivityContext; 41 42 /** 43 * A view which shows a horizontal divider 44 */ 45 @TargetApi(Build.VERSION_CODES.O) 46 public class AppsDividerView extends View implements FloatingHeaderRow { 47 48 public enum DividerType { 49 NONE, 50 LINE, 51 ALL_APPS_LABEL 52 } 53 54 private final TextPaint mPaint = new TextPaint(); 55 private DividerType mDividerType = DividerType.NONE; 56 57 private final @ColorInt int mStrokeColor; 58 private final @ColorInt int mAllAppsLabelTextColor; 59 60 private Layout mAllAppsLabelLayout; 61 private boolean mShowAllAppsLabel; 62 63 private FloatingHeaderView mParent; 64 private boolean mTabsHidden; 65 private FloatingHeaderRow[] mRows = FloatingHeaderRow.NO_ROWS; 66 67 private boolean mIsScrolledOut = false; 68 69 private final int[] mDividerSize; 70 AppsDividerView(Context context)71 public AppsDividerView(Context context) { 72 this(context, null); 73 } 74 AppsDividerView(Context context, AttributeSet attrs)75 public AppsDividerView(Context context, AttributeSet attrs) { 76 this(context, attrs, 0); 77 } 78 AppsDividerView(Context context, AttributeSet attrs, int defStyleAttr)79 public AppsDividerView(Context context, AttributeSet attrs, int defStyleAttr) { 80 super(context, attrs, defStyleAttr); 81 82 boolean isMainColorDark = Themes.getAttrBoolean(context, R.attr.isMainColorDark); 83 mDividerSize = new int[]{ 84 getResources().getDimensionPixelSize(R.dimen.all_apps_divider_width), 85 getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height) 86 }; 87 88 mStrokeColor = ContextCompat.getColor(context, isMainColorDark 89 ? R.color.all_apps_prediction_row_separator_dark 90 : R.color.all_apps_prediction_row_separator); 91 92 mAllAppsLabelTextColor = ContextCompat.getColor(context, isMainColorDark 93 ? R.color.all_apps_label_text_dark 94 : R.color.all_apps_label_text); 95 96 OnboardingPrefs<?> onboardingPrefs = ActivityContext.lookupContext( 97 getContext()).getOnboardingPrefs(); 98 mShowAllAppsLabel = onboardingPrefs == null || !onboardingPrefs.hasReachedMaxCount( 99 ALL_APPS_VISITED_COUNT); 100 } 101 setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden)102 public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) { 103 mParent = parent; 104 mTabsHidden = tabsHidden; 105 mRows = rows; 106 updateDividerType(); 107 } 108 109 /** {@code true} if all apps label should be shown in place of divider. */ setShowAllAppsLabel(boolean showAllAppsLabel)110 public void setShowAllAppsLabel(boolean showAllAppsLabel) { 111 if (showAllAppsLabel != mShowAllAppsLabel) { 112 mShowAllAppsLabel = showAllAppsLabel; 113 updateDividerType(); 114 } 115 } 116 117 @Override getExpectedHeight()118 public int getExpectedHeight() { 119 return getPaddingTop() + getPaddingBottom(); 120 } 121 122 @Override shouldDraw()123 public boolean shouldDraw() { 124 return mDividerType != DividerType.NONE; 125 } 126 127 @Override hasVisibleContent()128 public boolean hasVisibleContent() { 129 return false; 130 } 131 updateDividerType()132 private void updateDividerType() { 133 final DividerType dividerType; 134 if (!mTabsHidden) { 135 dividerType = DividerType.NONE; 136 } else { 137 // Check how many sections above me. 138 int sectionCount = 0; 139 for (FloatingHeaderRow row : mRows) { 140 if (row == this) { 141 break; 142 } else if (row.shouldDraw()) { 143 sectionCount++; 144 } 145 } 146 147 if (mShowAllAppsLabel && sectionCount > 0) { 148 dividerType = DividerType.ALL_APPS_LABEL; 149 } else if (sectionCount == 1) { 150 dividerType = DividerType.LINE; 151 } else { 152 dividerType = DividerType.NONE; 153 } 154 } 155 156 if (mDividerType != dividerType) { 157 mDividerType = dividerType; 158 int topPadding; 159 int bottomPadding; 160 switch (dividerType) { 161 case LINE: 162 topPadding = 0; 163 bottomPadding = getResources() 164 .getDimensionPixelSize(R.dimen.all_apps_prediction_row_divider_height); 165 mPaint.setColor(mStrokeColor); 166 break; 167 case ALL_APPS_LABEL: 168 topPadding = getAllAppsLabelLayout().getHeight() + getResources() 169 .getDimensionPixelSize(R.dimen.all_apps_label_top_padding); 170 bottomPadding = getResources() 171 .getDimensionPixelSize(R.dimen.all_apps_label_bottom_padding); 172 mPaint.setColor(mAllAppsLabelTextColor); 173 break; 174 case NONE: 175 default: 176 topPadding = bottomPadding = 0; 177 break; 178 } 179 setPadding(getPaddingLeft(), topPadding, getPaddingRight(), bottomPadding); 180 updateViewVisibility(); 181 invalidate(); 182 requestLayout(); 183 if (mParent != null) { 184 mParent.onHeightUpdated(); 185 } 186 } 187 } 188 updateViewVisibility()189 private void updateViewVisibility() { 190 setVisibility(mDividerType == DividerType.NONE 191 ? GONE 192 : (mIsScrolledOut ? INVISIBLE : VISIBLE)); 193 } 194 195 @Override onDraw(Canvas canvas)196 protected void onDraw(Canvas canvas) { 197 if (mDividerType == DividerType.LINE) { 198 int l = (getWidth() - mDividerSize[0]) / 2; 199 int t = getHeight() - (getPaddingBottom() / 2); 200 int radius = mDividerSize[1]; 201 canvas.drawRoundRect(l, t, l + mDividerSize[0], t + mDividerSize[1], radius, radius, 202 mPaint); 203 } else if (mDividerType == DividerType.ALL_APPS_LABEL) { 204 Layout textLayout = getAllAppsLabelLayout(); 205 int x = getWidth() / 2 - textLayout.getWidth() / 2; 206 int y = getHeight() - getPaddingBottom() - textLayout.getHeight(); 207 canvas.translate(x, y); 208 textLayout.draw(canvas); 209 canvas.translate(-x, -y); 210 } 211 } 212 getAllAppsLabelLayout()213 private Layout getAllAppsLabelLayout() { 214 if (mAllAppsLabelLayout == null) { 215 mPaint.setAntiAlias(true); 216 mPaint.setTypeface(Typeface.create("google-sans", Typeface.NORMAL)); 217 mPaint.setTextSize( 218 getResources().getDimensionPixelSize(R.dimen.all_apps_label_text_size)); 219 220 CharSequence allAppsLabelText = getResources().getText(R.string.all_apps_label); 221 mAllAppsLabelLayout = StaticLayout.Builder.obtain( 222 allAppsLabelText, 0, allAppsLabelText.length(), mPaint, 223 Math.round(mPaint.measureText(allAppsLabelText.toString()))) 224 .setAlignment(Layout.Alignment.ALIGN_CENTER) 225 .setMaxLines(1) 226 .setIncludePad(true) 227 .build(); 228 } 229 return mAllAppsLabelLayout; 230 } 231 232 @Override hasOverlappingRendering()233 public boolean hasOverlappingRendering() { 234 return false; 235 } 236 onMeasure(int widthMeasureSpec, int heightMeasureSpec)237 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 238 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 239 getPaddingBottom() + getPaddingTop()); 240 } 241 242 @Override setVerticalScroll(int scroll, boolean isScrolledOut)243 public void setVerticalScroll(int scroll, boolean isScrolledOut) { 244 setTranslationY(scroll); 245 mIsScrolledOut = isScrolledOut; 246 updateViewVisibility(); 247 } 248 249 @Override getTypeClass()250 public Class<AppsDividerView> getTypeClass() { 251 return AppsDividerView.class; 252 } 253 254 @Override getFocusedChild()255 public View getFocusedChild() { 256 return null; 257 } 258 } 259