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