• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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