SearchOrbView.java revision 4fdd3589c982860b831c0fad63c0082cb9079f47
1/* 2 * Copyright (C) 2014 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 17package android.support.v17.leanback.widget; 18 19import android.animation.ArgbEvaluator; 20import android.animation.ValueAnimator; 21import android.content.Context; 22import android.content.res.Resources; 23import android.content.res.TypedArray; 24import android.graphics.Color; 25import android.graphics.Rect; 26import android.graphics.drawable.Drawable; 27import android.graphics.drawable.GradientDrawable; 28import android.support.v17.leanback.R; 29import android.util.AttributeSet; 30import android.view.LayoutInflater; 31import android.view.View; 32import android.widget.FrameLayout; 33import android.widget.ImageView; 34 35/** 36 * <p>A widget that draws a search affordance, represented by a round background and an icon.</p> 37 * 38 * Background color and icon can be customized 39 */ 40public class SearchOrbView extends FrameLayout implements View.OnClickListener { 41 private OnClickListener mListener; 42 private View mSearchOrbView; 43 private ImageView mIcon; 44 private Drawable mIconDrawable; 45 private Colors mColors; 46 private final float mFocusedZoom; 47 private final int mPulseDurationMs; 48 private final int mScaleDownDurationMs; 49 private ValueAnimator mColorAnimator; 50 51 /** 52 * A set of colors used to display the search orb. 53 */ 54 public static class Colors { 55 private static final float sBrightnessAlpha = 0.15f; 56 57 /** 58 * Constructs a color set using the given color for the search orb. 59 * Other colors are provided by the framework. 60 * 61 * @param color The main search orb color. 62 */ 63 public Colors(int color) { 64 this(color, color); 65 } 66 67 /** 68 * Constructs a color set using the given colors for the search orb. 69 * Other colors are provided by the framework. 70 * 71 * @param color The main search orb color. 72 * @param brightColor A brighter version of the search orb used for animation. 73 */ 74 public Colors(int color, int brightColor) { 75 this(color, brightColor, Color.TRANSPARENT); 76 } 77 78 /** 79 * Constructs a color set using the given colors. 80 * 81 * @param color The main search orb color. 82 * @param brightColor A brighter version of the search orb used for animation. 83 * @param iconColor A color used to tint the search orb icon. 84 */ 85 public Colors(int color, int brightColor, int iconColor) { 86 this.color = color; 87 this.brightColor = brightColor == color ? getBrightColor(color) : brightColor; 88 this.iconColor = iconColor; 89 } 90 91 /** 92 * The main color of the search orb. 93 */ 94 public int color; 95 96 /** 97 * A brighter version of the search orb used for animation. 98 */ 99 public int brightColor; 100 101 /** 102 * A color used to tint the search orb icon. 103 */ 104 public int iconColor; 105 106 /** 107 * Computes a default brighter version of the given color. 108 */ 109 public static int getBrightColor(int color) { 110 final float brightnessValue = 0xff * sBrightnessAlpha; 111 int red = (int)(Color.red(color) * (1 - sBrightnessAlpha) + brightnessValue); 112 int green = (int)(Color.green(color) * (1 - sBrightnessAlpha) + brightnessValue); 113 int blue = (int)(Color.blue(color) * (1 - sBrightnessAlpha) + brightnessValue); 114 int alpha = (int)(Color.alpha(color) * (1 - sBrightnessAlpha) + brightnessValue); 115 return Color.argb(alpha, red, green, blue); 116 } 117 } 118 119 private final ArgbEvaluator mColorEvaluator = new ArgbEvaluator(); 120 121 private final ValueAnimator.AnimatorUpdateListener mUpdateListener = 122 new ValueAnimator.AnimatorUpdateListener() { 123 @Override 124 public void onAnimationUpdate(ValueAnimator animator) { 125 Integer color = (Integer) animator.getAnimatedValue(); 126 setOrbViewColor(color.intValue()); 127 } 128 }; 129 130 private ValueAnimator mShadowFocusAnimator; 131 132 private final ValueAnimator.AnimatorUpdateListener mFocusUpdateListener = 133 new ValueAnimator.AnimatorUpdateListener() { 134 @Override 135 public void onAnimationUpdate(ValueAnimator animation) { 136 ShadowHelper.getInstance().setZ(mSearchOrbView, animation.getAnimatedFraction()); 137 } 138 }; 139 140 public SearchOrbView(Context context) { 141 this(context, null); 142 } 143 144 public SearchOrbView(Context context, AttributeSet attrs) { 145 this(context, attrs, R.attr.searchOrbViewStyle); 146 } 147 148 public SearchOrbView(Context context, AttributeSet attrs, int defStyleAttr) { 149 super(context, attrs, defStyleAttr); 150 151 final Resources res = context.getResources(); 152 153 LayoutInflater inflater = (LayoutInflater) context 154 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 155 View root = inflater.inflate(R.layout.lb_search_orb, this, true); 156 mSearchOrbView = root.findViewById(R.id.search_orb); 157 mIcon = (ImageView)root.findViewById(R.id.icon); 158 159 mFocusedZoom = context.getResources().getFraction( 160 R.fraction.lb_search_orb_focused_zoom, 1, 1); 161 mPulseDurationMs = context.getResources().getInteger( 162 R.integer.lb_search_orb_pulse_duration_ms); 163 mScaleDownDurationMs = context.getResources().getInteger( 164 R.integer.lb_search_orb_scale_down_duration_ms); 165 166 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSearchOrbView, 167 defStyleAttr, 0); 168 169 Drawable img = a.getDrawable(R.styleable.lbSearchOrbView_searchOrbIcon); 170 if (img == null) { 171 img = res.getDrawable(R.drawable.lb_ic_in_app_search); 172 } 173 setOrbIcon(img); 174 175 int defColor = res.getColor(R.color.lb_default_search_color); 176 int color = a.getColor(R.styleable.lbSearchOrbView_searchOrbColor, defColor); 177 int brightColor = a.getColor( 178 R.styleable.lbSearchOrbView_searchOrbBrightColor, color); 179 int iconColor = a.getColor(R.styleable.lbSearchOrbView_searchOrbIconColor, Color.TRANSPARENT); 180 setOrbColors(new Colors(color, brightColor, iconColor)); 181 a.recycle(); 182 183 setFocusable(true); 184 setClipChildren(false); 185 setOnClickListener(this); 186 187 ShadowHelper.getInstance().setZ(mSearchOrbView, 0f); 188 // Icon has no background, but must be on top of the search orb view 189 ShadowHelper.getInstance().setZ(mIcon, 1f); 190 } 191 192 @Override 193 public void onClick(View view) { 194 if (null != mListener) { 195 mListener.onClick(view); 196 } 197 } 198 199 private void startShadowFocusAnimation(boolean gainFocus, int duration) { 200 if (mShadowFocusAnimator == null) { 201 mShadowFocusAnimator = ValueAnimator.ofFloat(0f, 1f); 202 mShadowFocusAnimator.addUpdateListener(mFocusUpdateListener); 203 } 204 if (gainFocus) { 205 mShadowFocusAnimator.start(); 206 } else { 207 mShadowFocusAnimator.reverse(); 208 } 209 mShadowFocusAnimator.setDuration(duration); 210 } 211 212 @Override 213 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 214 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 215 final float zoom = gainFocus ? mFocusedZoom : 1f; 216 final int duration = gainFocus ? mPulseDurationMs : mScaleDownDurationMs; 217 mSearchOrbView.animate().scaleX(zoom).scaleY(zoom).setDuration(duration).start(); 218 startShadowFocusAnimation(gainFocus, duration); 219 enableOrbColorAnimation(gainFocus); 220 } 221 222 /** 223 * Set the orb icon 224 * @param icon the drawable to be used as the icon 225 */ 226 public void setOrbIcon(Drawable icon) { 227 mIconDrawable = icon; 228 mIcon.setImageDrawable(mIconDrawable); 229 } 230 231 /** 232 * Returns the orb icon 233 * @return the drawable used as the icon 234 */ 235 public Drawable getOrbIcon() { 236 return mIconDrawable; 237 } 238 239 /** 240 * Set the on click listener for the orb 241 * @param listener The listener. 242 */ 243 public void setOnOrbClickedListener(OnClickListener listener) { 244 mListener = listener; 245 if (null != listener) { 246 setVisibility(View.VISIBLE); 247 } else { 248 setVisibility(View.INVISIBLE); 249 } 250 } 251 252 /** 253 * Sets the background color of the search orb. 254 * Other colors will be provided by the framework. 255 * 256 * @param color the RGBA color 257 */ 258 public void setOrbColor(int color) { 259 setOrbColors(new Colors(color, color, Color.TRANSPARENT)); 260 } 261 262 /** 263 * Sets the search orb colors. 264 * Other colors are provided by the framework. 265 * @deprecated Use {@link #setOrbColors(Colors)} instead. 266 */ 267 @Deprecated 268 public void setOrbColor(int color, int brightColor) { 269 setOrbColors(new Colors(color, brightColor, Color.TRANSPARENT)); 270 } 271 272 /** 273 * Returns the orb color 274 * @return the RGBA color 275 */ 276 public int getOrbColor() { 277 return mColors.color; 278 } 279 280 /** 281 * Set the {@link Colors} used to display the search orb. 282 */ 283 public void setOrbColors(Colors colors) { 284 mColors = colors; 285 mIcon.setColorFilter(mColors.iconColor); 286 287 if (mColorAnimator == null) { 288 setOrbViewColor(mColors.color); 289 } else { 290 enableOrbColorAnimation(true); 291 } 292 } 293 294 /** 295 * Returns the {@link Colors} used to display the search orb. 296 */ 297 public Colors getOrbColors() { 298 return mColors; 299 } 300 301 private void enableOrbColorAnimation(boolean enable) { 302 if (mColorAnimator != null) { 303 mColorAnimator.end(); 304 mColorAnimator = null; 305 } 306 if (enable) { 307 // TODO: set interpolator (material if available) 308 mColorAnimator = ValueAnimator.ofObject(mColorEvaluator, 309 mColors.color, mColors.brightColor, mColors.color); 310 mColorAnimator.setRepeatCount(ValueAnimator.INFINITE); 311 mColorAnimator.setDuration(mPulseDurationMs * 2); 312 mColorAnimator.addUpdateListener(mUpdateListener); 313 mColorAnimator.start(); 314 } 315 } 316 317 private void setOrbViewColor(int color) { 318 if (mSearchOrbView.getBackground() instanceof GradientDrawable) { 319 ((GradientDrawable) mSearchOrbView.getBackground()).setColor(color); 320 } 321 } 322 323} 324