/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.accessibility; import android.annotation.NonNull; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.GradientDrawable.Orientation; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.Display; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import androidx.annotation.VisibleForTesting; import com.android.settingslib.widget.R; import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * Custom ListView {@link ListView} which displays palette to deploy the color code preview. * *

The preview shows gradient from color white to specific color code on each list view item, in * addition, text view adjusts the attribute of width for adapting the text length. * *

The text cannot fills the whole view for ensuring the gradient color preview can purely * display also the view background shows the color beside the text variable end point. */ public class PaletteListView extends ListView { private static final float VIEW_PITCH = 0.05f; private final Context mContext; private final DisplayAdapter mDisplayAdapter; private final LayoutInflater mLayoutInflater; private final String mDefaultGradientColorCodeString; private final int mDefaultGradientColor; private float mTextBound; public PaletteListView(Context context) { this(context, null); } public PaletteListView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public PaletteListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; mDisplayAdapter = new DisplayAdapter(); mLayoutInflater = LayoutInflater.from(context); mDefaultGradientColorCodeString = getResources().getString(R.color.palette_list_gradient_background); mDefaultGradientColor = getResources().getColor(R.color.palette_list_gradient_background, null); mTextBound = 0.0f; init(); } private static int getScreenWidth(WindowManager windowManager) { final Display display = windowManager.getDefaultDisplay(); final DisplayMetrics displayMetrics = new DisplayMetrics(); display.getMetrics(displayMetrics); return displayMetrics.widthPixels; } private void init() { final TypedArray colorNameArray = getResources().obtainTypedArray( R.array.setting_palette_colors); final TypedArray colorCodeArray = getResources().obtainTypedArray( R.array.setting_palette_data); final int colorNameArrayLength = colorNameArray.length(); final List colorList = new ArrayList<>(); computeTextWidthBounds(colorNameArray); for (int index = 0; index < colorNameArrayLength; index++) { colorList.add( new ColorAttributes( /* colorName= */ colorNameArray.getString(index), /* colorCode= */ colorCodeArray.getColor(index, mDefaultGradientColor), /* textBound= */ mTextBound, /* gradientDrawable= */ new GradientDrawable(Orientation.LEFT_RIGHT, null))); } mDisplayAdapter.setColorList(colorList); setAdapter(mDisplayAdapter); setDividerHeight(/* height= */ 0); } /** * Sets string array that required the color name and color code for deploy the new color * preview. * *

The parameters not allow null define but two array length inconsistent are acceptable, in * addition, to prevent IndexOutOfBoundsException the algorithm will check array data, and base * on the array size to display data, or fills color code array if length less than other. * * @param colorNames a string array of color name * @param colorCodes a string array of color code * @return true if new array data apply successful */ @VisibleForTesting boolean setPaletteListColors(@NonNull String[] colorNames, @NonNull String[] colorCodes) { if (colorNames == null || colorCodes == null) { return false; } final int colorNameArrayLength = colorNames.length; final int colorCodeArrayLength = colorCodes.length; final List colorList = new ArrayList<>(); final String[] colorCodeArray = fillColorCodeArray(colorCodes, colorNameArrayLength, colorCodeArrayLength); computeTextWidthBounds(colorNames); for (int index = 0; index < colorNameArrayLength; index++) { colorList.add( new ColorAttributes( /* colorName= */ colorNames[index], /* colorCode= */ Color.parseColor(colorCodeArray[index]), /* textBound= */ mTextBound, /* gradientDrawable= */ new GradientDrawable(Orientation.LEFT_RIGHT, null))); } mDisplayAdapter.setColorList(colorList); mDisplayAdapter.notifyDataSetChanged(); return true; } private String[] fillColorCodeArray(String[] colorCodes, int colorNameArrayLength, int colorCodeArrayLength) { if (colorNameArrayLength == colorCodeArrayLength || colorNameArrayLength < colorCodeArrayLength) { return colorCodes; } final String[] colorCodeArray = new String[colorNameArrayLength]; for (int index = 0; index < colorNameArrayLength; index++) { if (index < colorCodeArrayLength) { colorCodeArray[index] = colorCodes[index]; } else { colorCodeArray[index] = mDefaultGradientColorCodeString; } } return colorCodeArray; } private void computeTextWidthBounds(TypedArray colorNameTypedArray) { final int colorNameArrayLength = colorNameTypedArray.length(); final String[] colorNames = new String[colorNameArrayLength]; for (int index = 0; index < colorNameArrayLength; index++) { colorNames[index] = colorNameTypedArray.getString(index); } measureBound(colorNames); } private void computeTextWidthBounds(String[] colorNameArray) { final int colorNameArrayLength = colorNameArray.length; final String[] colorNames = new String[colorNameArrayLength]; for (int index = 0; index < colorNameArrayLength; index++) { colorNames[index] = colorNameArray[index]; } measureBound(colorNames); } private void measureBound(String[] dataArray) { final WindowManager windowManager = (WindowManager) mContext.getSystemService( Context.WINDOW_SERVICE); final View view = mLayoutInflater.inflate(R.layout.palette_listview_item, null); final TextView textView = view.findViewById(R.id.item_textview); final List colorNameList = new ArrayList<>(Arrays.asList(dataArray)); Collections.sort(colorNameList, Comparator.comparing(String::length)); // Gets the last index of list which sort by text length. textView.setText(Iterables.getLast(colorNameList)); final float textWidth = textView.getPaint().measureText(textView.getText().toString()); // Computes rate of text width compare to screen width, and measures the round the double // to two decimal places manually. final float textBound = Math.round(textWidth / getScreenWidth(windowManager) * 100) / 100f; mTextBound = textBound + VIEW_PITCH; } private static class ViewHolder { public TextView textView; } /** An adapter that converts color text title and color code to text views. */ private final class DisplayAdapter extends BaseAdapter { private List mColorList; @Override public int getCount() { return mColorList.size(); } @Override public Object getItem(int position) { return mColorList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { final ViewHolder viewHolder; final ColorAttributes paletteAttribute = mColorList.get(position); final String colorName = paletteAttribute.getColorName(); final GradientDrawable gradientDrawable = paletteAttribute.getGradientDrawable(); if (convertView == null) { convertView = mLayoutInflater.inflate(R.layout.palette_listview_item, null); viewHolder = new ViewHolder(); viewHolder.textView = convertView.findViewById(R.id.item_textview); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.textView.setText(colorName); viewHolder.textView.setBackground(gradientDrawable); return convertView; } protected void setColorList(List colorList) { mColorList = colorList; } } private final class ColorAttributes { private final int mColorIndex = 2; // index for inject color. private final int mColorOffsetIndex = 1; // index for offset effect. private final String mColorName; private final GradientDrawable mGradientDrawable; private final int[] mGradientColors = {/* startColor=*/ mDefaultGradientColor, /* centerColor=*/ mDefaultGradientColor, /* endCode= */ 0}; private final float[] mGradientOffsets = {/* starOffset= */ 0.0f, /* centerOffset= */ 0.5f, /* endOffset= */ 1.0f}; ColorAttributes( String colorName, int colorCode, float textBound, GradientDrawable gradientDrawable) { mGradientColors[mColorIndex] = colorCode; mGradientOffsets[mColorOffsetIndex] = textBound; gradientDrawable.setColors(mGradientColors, mGradientOffsets); mColorName = colorName; mGradientDrawable = gradientDrawable; } public String getColorName() { return mColorName; } public GradientDrawable getGradientDrawable() { return mGradientDrawable; } } }