1 /* 2 * Copyright (C) 2020 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.settings.accessibility; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.content.res.TypedArray; 22 import android.graphics.Color; 23 import android.graphics.drawable.GradientDrawable; 24 import android.graphics.drawable.GradientDrawable.Orientation; 25 import android.util.AttributeSet; 26 import android.util.DisplayMetrics; 27 import android.view.Display; 28 import android.view.LayoutInflater; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.WindowManager; 32 import android.widget.BaseAdapter; 33 import android.widget.ListView; 34 import android.widget.TextView; 35 36 import androidx.annotation.VisibleForTesting; 37 38 import com.android.settings.R; 39 40 import com.google.common.collect.Iterables; 41 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.Collections; 45 import java.util.Comparator; 46 import java.util.List; 47 48 /** 49 * Custom ListView {@link ListView} which displays palette to deploy the color code preview. 50 * 51 * <p>The preview shows gradient from color white to specific color code on each list view item, in 52 * addition, text view adjusts the attribute of width for adapting the text length. 53 * 54 * <p>The text cannot fills the whole view for ensuring the gradient color preview can purely 55 * display also the view background shows the color beside the text variable end point. 56 */ 57 public class PaletteListView extends ListView { 58 private final Context mContext; 59 private final DisplayAdapter mDisplayAdapter; 60 private final LayoutInflater mLayoutInflater; 61 private final String mDefaultGradientColorCodeString; 62 private final int mDefaultGradientColor; 63 private float mTextBound; 64 private static final float LANDSCAPE_MAX_WIDTH_PERCENTAGE = 100f; 65 PaletteListView(Context context)66 public PaletteListView(Context context) { 67 this(context, null); 68 } 69 PaletteListView(Context context, AttributeSet attrs)70 public PaletteListView(Context context, AttributeSet attrs) { 71 this(context, attrs, 0); 72 } 73 PaletteListView(Context context, AttributeSet attrs, int defStyleAttr)74 public PaletteListView(Context context, AttributeSet attrs, int defStyleAttr) { 75 super(context, attrs, defStyleAttr); 76 mContext = context; 77 mDisplayAdapter = new DisplayAdapter(); 78 mLayoutInflater = LayoutInflater.from(context); 79 mDefaultGradientColorCodeString = 80 getResources().getString(R.color.palette_list_gradient_background); 81 mDefaultGradientColor = 82 getResources().getColor(R.color.palette_list_gradient_background, null); 83 mTextBound = 0.0f; 84 init(); 85 } 86 getScreenWidth(WindowManager windowManager)87 private static int getScreenWidth(WindowManager windowManager) { 88 final Display display = windowManager.getDefaultDisplay(); 89 final DisplayMetrics displayMetrics = new DisplayMetrics(); 90 display.getMetrics(displayMetrics); 91 return displayMetrics.widthPixels; 92 } 93 init()94 private void init() { 95 final TypedArray colorNameArray = getResources().obtainTypedArray( 96 R.array.setting_palette_colors); 97 final TypedArray colorCodeArray = getResources().obtainTypedArray( 98 R.array.setting_palette_data); 99 final int colorNameArrayLength = colorNameArray.length(); 100 final List<ColorAttributes> colorList = new ArrayList<>(); 101 computeTextWidthBounds(colorNameArray); 102 103 for (int index = 0; index < colorNameArrayLength; index++) { 104 colorList.add( 105 new ColorAttributes( 106 /* colorName= */ colorNameArray.getString(index), 107 /* colorCode= */ colorCodeArray.getColor(index, mDefaultGradientColor), 108 /* textBound= */ mTextBound, 109 /* gradientDrawable= */ 110 new GradientDrawable(Orientation.LEFT_RIGHT, null))); 111 } 112 113 mDisplayAdapter.setColorList(colorList); 114 setAdapter(mDisplayAdapter); 115 setDividerHeight(/* height= */ 0); 116 } 117 118 /** 119 * Sets string array that required the color name and color code for deploy the new color 120 * preview. 121 * 122 * <p>The parameters not allow null define but two array length inconsistent are acceptable, in 123 * addition, to prevent IndexOutOfBoundsException the algorithm will check array data, and base 124 * on the array size to display data, or fills color code array if length less than other. 125 * 126 * @param colorNames a string array of color name 127 * @param colorCodes a string array of color code 128 * @return true if new array data apply successful 129 */ 130 @VisibleForTesting setPaletteListColors(@onNull String[] colorNames, @NonNull String[] colorCodes)131 boolean setPaletteListColors(@NonNull String[] colorNames, @NonNull String[] colorCodes) { 132 if (colorNames == null || colorCodes == null) { 133 return false; 134 } 135 136 final int colorNameArrayLength = colorNames.length; 137 final int colorCodeArrayLength = colorCodes.length; 138 final List<ColorAttributes> colorList = new ArrayList<>(); 139 final String[] colorCodeArray = fillColorCodeArray(colorCodes, colorNameArrayLength, 140 colorCodeArrayLength); 141 computeTextWidthBounds(colorNames); 142 143 for (int index = 0; index < colorNameArrayLength; index++) { 144 colorList.add( 145 new ColorAttributes( 146 /* colorName= */ colorNames[index], 147 /* colorCode= */ Color.parseColor(colorCodeArray[index]), 148 /* textBound= */ mTextBound, 149 /* gradientDrawable= */ 150 new GradientDrawable(Orientation.LEFT_RIGHT, null))); 151 } 152 153 mDisplayAdapter.setColorList(colorList); 154 mDisplayAdapter.notifyDataSetChanged(); 155 return true; 156 } 157 fillColorCodeArray(String[] colorCodes, int colorNameArrayLength, int colorCodeArrayLength)158 private String[] fillColorCodeArray(String[] colorCodes, int colorNameArrayLength, 159 int colorCodeArrayLength) { 160 if (colorNameArrayLength == colorCodeArrayLength 161 || colorNameArrayLength < colorCodeArrayLength) { 162 return colorCodes; 163 } 164 165 final String[] colorCodeArray = new String[colorNameArrayLength]; 166 for (int index = 0; index < colorNameArrayLength; index++) { 167 if (index < colorCodeArrayLength) { 168 colorCodeArray[index] = colorCodes[index]; 169 } else { 170 colorCodeArray[index] = mDefaultGradientColorCodeString; 171 } 172 } 173 return colorCodeArray; 174 } 175 computeTextWidthBounds(TypedArray colorNameTypedArray)176 private void computeTextWidthBounds(TypedArray colorNameTypedArray) { 177 final int colorNameArrayLength = colorNameTypedArray.length(); 178 final String[] colorNames = new String[colorNameArrayLength]; 179 for (int index = 0; index < colorNameArrayLength; index++) { 180 colorNames[index] = colorNameTypedArray.getString(index); 181 } 182 183 measureBound(colorNames); 184 } 185 computeTextWidthBounds(String[] colorNameArray)186 private void computeTextWidthBounds(String[] colorNameArray) { 187 final int colorNameArrayLength = colorNameArray.length; 188 final String[] colorNames = new String[colorNameArrayLength]; 189 for (int index = 0; index < colorNameArrayLength; index++) { 190 colorNames[index] = colorNameArray[index]; 191 } 192 193 measureBound(colorNames); 194 } 195 measureBound(String[] dataArray)196 private void measureBound(String[] dataArray) { 197 final WindowManager windowManager = (WindowManager) mContext.getSystemService( 198 Context.WINDOW_SERVICE); 199 final View view = mLayoutInflater.inflate(R.layout.palette_listview_item, null); 200 final TextView textView = view.findViewById(R.id.item_textview); 201 final List<String> colorNameList = new ArrayList<>(Arrays.asList(dataArray)); 202 Collections.sort(colorNameList, Comparator.comparing(String::length)); 203 // Gets the last index of list which sort by text length. 204 textView.setText(Iterables.getLast(colorNameList)); 205 206 final float textWidth = textView.getPaint().measureText(textView.getText().toString()); 207 // Computes rate of text width compare to screen width, and measures the round the double 208 // to two decimal places manually. 209 final float textBound = Math.round( 210 textWidth / getScreenWidth(windowManager) * LANDSCAPE_MAX_WIDTH_PERCENTAGE) 211 / LANDSCAPE_MAX_WIDTH_PERCENTAGE; 212 213 // Left padding and right padding with color preview. 214 final float paddingPixel = getResources().getDimension( 215 R.dimen.accessibility_layout_margin_start_end); 216 final float paddingWidth = 217 Math.round(paddingPixel / getScreenWidth(windowManager) 218 * LANDSCAPE_MAX_WIDTH_PERCENTAGE) / LANDSCAPE_MAX_WIDTH_PERCENTAGE; 219 mTextBound = textBound + paddingWidth + paddingWidth; 220 } 221 222 private static class ViewHolder { 223 public TextView textView; 224 } 225 226 /** An adapter that converts color text title and color code to text views. */ 227 private final class DisplayAdapter extends BaseAdapter { 228 229 private List<ColorAttributes> mColorList; 230 231 @Override getCount()232 public int getCount() { 233 return mColorList.size(); 234 } 235 236 @Override getItem(int position)237 public Object getItem(int position) { 238 return mColorList.get(position); 239 } 240 241 @Override getItemId(int position)242 public long getItemId(int position) { 243 return position; 244 } 245 246 @Override getView(int position, View convertView, ViewGroup parent)247 public View getView(int position, View convertView, ViewGroup parent) { 248 final ViewHolder viewHolder; 249 final ColorAttributes paletteAttribute = mColorList.get(position); 250 final String colorName = paletteAttribute.getColorName(); 251 final GradientDrawable gradientDrawable = paletteAttribute.getGradientDrawable(); 252 253 if (convertView == null) { 254 convertView = mLayoutInflater.inflate(R.layout.palette_listview_item, null); 255 viewHolder = new ViewHolder(); 256 viewHolder.textView = convertView.findViewById(R.id.item_textview); 257 convertView.setTag(viewHolder); 258 } else { 259 viewHolder = (ViewHolder) convertView.getTag(); 260 } 261 262 viewHolder.textView.setText(colorName); 263 viewHolder.textView.setBackground(gradientDrawable); 264 return convertView; 265 } 266 setColorList(List<ColorAttributes> colorList)267 protected void setColorList(List<ColorAttributes> colorList) { 268 mColorList = colorList; 269 } 270 } 271 272 private final class ColorAttributes { 273 private final int mColorIndex = 2; // index for inject color. 274 private final int mColorOffsetIndex = 1; // index for offset effect. 275 private final String mColorName; 276 private final GradientDrawable mGradientDrawable; 277 private final int[] mGradientColors = 278 {/* startColor=*/ mDefaultGradientColor, /* centerColor=*/ mDefaultGradientColor, 279 /* endCode= */ 0}; 280 private final float[] mGradientOffsets = 281 {/* starOffset= */ 0.0f, /* centerOffset= */ 0.5f, /* endOffset= */ 1.0f}; 282 ColorAttributes( String colorName, int colorCode, float textBound, GradientDrawable gradientDrawable)283 ColorAttributes( 284 String colorName, int colorCode, float textBound, 285 GradientDrawable gradientDrawable) { 286 mGradientColors[mColorIndex] = colorCode; 287 mGradientOffsets[mColorOffsetIndex] = textBound; 288 gradientDrawable.setColors(mGradientColors, mGradientOffsets); 289 mColorName = colorName; 290 mGradientDrawable = gradientDrawable; 291 } 292 getColorName()293 public String getColorName() { 294 return mColorName; 295 } 296 getGradientDrawable()297 public GradientDrawable getGradientDrawable() { 298 return mGradientDrawable; 299 } 300 } 301 } 302