DessertCaseView.java revision 8475a74bdd9d6e1520fe66cdffb7b07c19c736e0
1/* 2 * Copyright (C) 2013 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 com.android.systemui; 18 19import android.animation.Animator; 20import android.animation.AnimatorSet; 21import android.animation.ObjectAnimator; 22import android.content.Context; 23import android.content.res.Resources; 24import android.graphics.*; 25import android.graphics.drawable.BitmapDrawable; 26import android.graphics.drawable.Drawable; 27import android.os.Handler; 28import android.util.AttributeSet; 29import android.util.Log; 30import android.util.SparseArray; 31import android.view.View; 32import android.view.animation.AccelerateInterpolator; 33import android.view.animation.AnticipateOvershootInterpolator; 34import android.view.animation.DecelerateInterpolator; 35import android.widget.FrameLayout; 36import android.widget.ImageView; 37 38import java.util.HashSet; 39import java.util.Set; 40 41public class DessertCaseView extends FrameLayout { 42 private static final String TAG = DessertCaseView.class.getSimpleName(); 43 44 private static final boolean DEBUG = false; 45 46 static final int START_DELAY = 5000; 47 static final int DELAY = 2000; 48 static final int DURATION = 500; 49 50 private static final int TAG_POS = 0x2000001; 51 private static final int TAG_SPAN = 0x2000002; 52 53 private static final int[] PASTRIES = { 54 R.drawable.dessert_kitkat, // used with permission 55 R.drawable.dessert_android, // thx irina 56 }; 57 58 private static final int[] RARE_PASTRIES = { 59 R.drawable.dessert_cupcake, // 2009 60 R.drawable.dessert_donut, // 2009 61 R.drawable.dessert_eclair, // 2009 62 R.drawable.dessert_froyo, // 2010 63 R.drawable.dessert_gingerbread, // 2010 64 R.drawable.dessert_honeycomb, // 2011 65 R.drawable.dessert_ics, // 2011 66 R.drawable.dessert_jellybean, // 2012 67 }; 68 69 private static final int[] XRARE_PASTRIES = { 70 R.drawable.dessert_petitfour, // the original and still delicious 71 72 R.drawable.dessert_donutburger, // remember kids, this was long before cronuts 73 74 R.drawable.dessert_flan, // sholes final approach 75 // landing gear punted to flan 76 // runway foam glistens 77 // -- mcleron 78 79 R.drawable.dessert_keylimepie, // from an alternative timeline 80 }; 81 private static final int[] XXRARE_PASTRIES = { 82 R.drawable.dessert_zombiegingerbread, // thx hackbod 83 R.drawable.dessert_dandroid, // thx morrildl 84 R.drawable.dessert_jandycane, // thx nes 85 }; 86 87 private static final int NUM_PASTRIES = PASTRIES.length + RARE_PASTRIES.length 88 + XRARE_PASTRIES.length + XXRARE_PASTRIES.length; 89 90 private SparseArray<Drawable> mDrawables = new SparseArray<Drawable>(NUM_PASTRIES); 91 92 private static final float[] MASK = { 93 0f, 0f, 0f, 0f, 255f, 94 0f, 0f, 0f, 0f, 255f, 95 0f, 0f, 0f, 0f, 255f, 96 1f, 0f, 0f, 0f, 0f 97 }; 98 99 private static final float[] WHITE_MASK = { 100 0f, 0f, 0f, 0f, 255f, 101 0f, 0f, 0f, 0f, 255f, 102 0f, 0f, 0f, 0f, 255f, 103 -1f, 0f, 0f, 0f, 255f 104 }; 105 106 public static final float SCALE = 0.25f; // natural display size will be SCALE*mCellSize 107 108 private static final float PROB_2X = 0.33f; 109 private static final float PROB_3X = 0.1f; 110 private static final float PROB_4X = 0.01f; 111 112 private boolean mStarted; 113 114 private int mCellSize; 115 private int mWidth, mHeight; 116 private int mRows, mColumns; 117 private View[] mCells; 118 119 private final Set<Point> mFreeList = new HashSet<Point>(); 120 121 private final Handler mHandler = new Handler(); 122 123 private final Runnable mJuggle = new Runnable() { 124 @Override 125 public void run() { 126 final int N = getChildCount(); 127 128 final int K = 1; //irand(1,3); 129 for (int i=0; i<K; i++) { 130 final View child = getChildAt((int) (Math.random() * N)); 131 place(child, true); 132 } 133 134 fillFreeList(); 135 136 if (mStarted) { 137 mHandler.postDelayed(mJuggle, DELAY); 138 } 139 } 140 }; 141 142 public DessertCaseView(Context context) { 143 this(context, null); 144 } 145 146 public DessertCaseView(Context context, AttributeSet attrs) { 147 this(context, attrs, 0); 148 } 149 150 public DessertCaseView(Context context, AttributeSet attrs, int defStyle) { 151 super(context, attrs, defStyle); 152 153 final Resources res = getResources(); 154 155 mStarted = false; 156 157 mCellSize = res.getDimensionPixelSize(R.dimen.dessert_case_cell_size); 158 final BitmapFactory.Options opts = new BitmapFactory.Options(); 159 if (mCellSize < 512) { // assuming 512x512 images 160 opts.inSampleSize = 2; 161 } 162 for (int[] list : new int[][] { PASTRIES, RARE_PASTRIES, XRARE_PASTRIES, XXRARE_PASTRIES }) { 163 for (int resid : list) { 164 final BitmapDrawable d = new BitmapDrawable(res, 165 BitmapFactory.decodeResource(res, resid, opts)); 166 d.setColorFilter(new ColorMatrixColorFilter(MASK)); 167 d.setBounds(0, 0, mCellSize, mCellSize); 168 mDrawables.append(resid, d); 169 } 170 } 171 if (DEBUG) setWillNotDraw(false); 172 } 173 174 public void start() { 175 if (!mStarted) { 176 mStarted = true; 177 fillFreeList(DURATION * 4); 178 } 179 mHandler.postDelayed(mJuggle, START_DELAY); 180 } 181 182 public void stop() { 183 mStarted = false; 184 mHandler.removeCallbacks(mJuggle); 185 } 186 187 int pick(int[] a) { 188 return a[(int)(Math.random()*a.length)]; 189 } 190 191 <T> T pick(T[] a) { 192 return a[(int)(Math.random()*a.length)]; 193 } 194 195 <T> T pick(SparseArray<T> sa) { 196 return sa.valueAt((int)(Math.random()*sa.size())); 197 } 198 199 float[] hsv = new float[] { 0, 1f, .85f }; 200 int random_color() { 201// return 0xFF000000 | (int) (Math.random() * (float) 0xFFFFFF); // totally random 202 final int COLORS = 12; 203 hsv[0] = irand(0,COLORS) * (360f/COLORS); 204 return Color.HSVToColor(hsv); 205 } 206 207 @Override 208 protected synchronized void onSizeChanged (int w, int h, int oldw, int oldh) { 209 super.onSizeChanged(w, h, oldw, oldh); 210 if (mWidth == w && mHeight == h) return; 211 212 final boolean wasStarted = mStarted; 213 if (wasStarted) { 214 stop(); 215 } 216 217 mWidth = w; 218 mHeight = h; 219 220 mCells = null; 221 removeAllViewsInLayout(); 222 mFreeList.clear(); 223 224 mRows = mHeight / mCellSize; 225 mColumns = mWidth / mCellSize; 226 227 mCells = new View[mRows * mColumns]; 228 229 if (DEBUG) Log.v(TAG, String.format("New dimensions: %dx%d", mColumns, mRows)); 230 231 setScaleX(SCALE); 232 setScaleY(SCALE); 233 setTranslationX(0.5f * (mWidth - mCellSize * mColumns) * SCALE); 234 setTranslationY(0.5f * (mHeight - mCellSize * mRows) * SCALE); 235 236 for (int j=0; j<mRows; j++) { 237 for (int i=0; i<mColumns; i++) { 238 mFreeList.add(new Point(i,j)); 239 } 240 } 241 242 if (wasStarted) { 243 start(); 244 } 245 } 246 247 public void fillFreeList() { 248 fillFreeList(DURATION); 249 } 250 251 public synchronized void fillFreeList(int animationLen) { 252 final Context ctx = getContext(); 253 final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(mCellSize, mCellSize); 254 255 while (! mFreeList.isEmpty()) { 256 Point pt = mFreeList.iterator().next(); 257 mFreeList.remove(pt); 258 final int i=pt.x; 259 final int j=pt.y; 260 261 if (mCells[j*mColumns+i] != null) continue; 262 final ImageView v = new ImageView(ctx); 263 v.setOnClickListener(new OnClickListener() { 264 @Override 265 public void onClick(View view) { 266 place(v, true); 267 postDelayed(new Runnable() { public void run() { fillFreeList(); } }, DURATION/2); 268 } 269 }); 270 271 final int c = random_color(); 272 v.setBackgroundColor(c); 273 274 final float which = frand(); 275 final Drawable d; 276 if (which < 0.001f) { 277 d = mDrawables.get(pick(XXRARE_PASTRIES)); 278 } else if (which < 0.01f) { 279 d = mDrawables.get(pick(XRARE_PASTRIES)); 280 } else if (which < 0.5f) { 281 d = mDrawables.get(pick(RARE_PASTRIES)); 282 } else if (which < 0.7f) { 283 d = mDrawables.get(pick(PASTRIES)); 284 } else { 285 d = null; 286 } 287 if (d != null) { 288 v.getOverlay().add(d); 289 } 290 291 final Paint paint = new Paint(); 292 v.setLayerType(View.LAYER_TYPE_HARDWARE, paint); 293 294 lp.width = lp.height = mCellSize; 295 addView(v, lp); 296 place(v, pt, false); 297 if (animationLen > 0) { 298 final float s = (Integer) v.getTag(TAG_SPAN); 299 v.setScaleX(0.5f * s); 300 v.setScaleY(0.5f * s); 301 v.setAlpha(0f); 302 v.animate().scaleX(s).scaleY(s).alpha(1f).setDuration(animationLen); 303 } 304 } 305 } 306 307 public void place(View v, boolean animate) { 308 place(v, new Point(irand(0, mColumns), irand(0, mRows)), animate); 309 } 310 311 private final HashSet<View> tmpSet = new HashSet<View>(); 312 public synchronized void place(View v, Point pt, boolean animate) { 313 final int i = pt.x; 314 final int j = pt.y; 315 final float rnd = frand(); 316 if (v.getTag(TAG_POS) != null) { 317 for (final Point oc : getOccupied(v)) { 318 mFreeList.add(oc); 319 mCells[oc.y*mColumns + oc.x] = null; 320 } 321 } 322 int scale = 1; 323 if (rnd < PROB_4X) { 324 if (!(i >= mColumns-3 || j >= mRows-3)) { 325 scale = 4; 326 } 327 } else if (rnd < PROB_3X) { 328 if (!(i >= mColumns-2 || j >= mRows-2)) { 329 scale = 3; 330 } 331 } else if (rnd < PROB_2X) { 332 if (!(i == mColumns-1 || j == mRows-1)) { 333 scale = 2; 334 } 335 } 336 337 v.setTag(TAG_POS, pt); 338 v.setTag(TAG_SPAN, scale); 339 340 tmpSet.clear(); 341 342 final Point[] occupied = getOccupied(v); 343 for (final Point oc : occupied) { 344 final View squatter = mCells[oc.y*mColumns + oc.x]; 345 if (squatter != null) { 346 tmpSet.add(squatter); 347 } 348 } 349 350 for (final View squatter : tmpSet) { 351 for (final Point sq : getOccupied(squatter)) { 352 mFreeList.add(sq); 353 mCells[sq.y*mColumns + sq.x] = null; 354 } 355 if (squatter != v) { 356 squatter.setTag(TAG_POS, null); 357 if (animate) { 358 squatter.animate().scaleX(0.5f).scaleY(0.5f).alpha(0) 359 .setDuration(DURATION) 360 .setInterpolator(new AccelerateInterpolator()) 361 .setListener(new Animator.AnimatorListener() { 362 public void onAnimationStart(Animator animator) { } 363 public void onAnimationEnd(Animator animator) { 364 removeView(squatter); 365 } 366 public void onAnimationCancel(Animator animator) { } 367 public void onAnimationRepeat(Animator animator) { } 368 }) 369 .start(); 370 } else { 371 removeView(squatter); 372 } 373 } 374 } 375 376 for (final Point oc : occupied) { 377 mCells[oc.y*mColumns + oc.x] = v; 378 mFreeList.remove(oc); 379 } 380 381 final float rot = (float)irand(0, 4) * 90f; 382 383 if (animate) { 384 v.bringToFront(); 385 AnimatorSet set1 = new AnimatorSet(); 386 set1.playTogether( 387 ObjectAnimator.ofFloat(v, View.SCALE_X, (float) scale), 388 ObjectAnimator.ofFloat(v, View.SCALE_Y, (float) scale) 389 ); 390 set1.setInterpolator(new AnticipateOvershootInterpolator()); 391 set1.setDuration(DURATION); 392 set1.start(); 393 394 AnimatorSet set2 = new AnimatorSet(); 395 set2.playTogether( 396 ObjectAnimator.ofFloat(v, View.ROTATION, rot), 397 ObjectAnimator.ofFloat(v, View.X, i* mCellSize + (scale-1) * mCellSize /2), 398 ObjectAnimator.ofFloat(v, View.Y, j* mCellSize + (scale-1) * mCellSize /2) 399 ); 400 set2.setInterpolator(new DecelerateInterpolator()); 401 set2.setDuration(DURATION); 402 set2.start(); 403 } else { 404 v.setX(i * mCellSize + (scale-1) * mCellSize /2); 405 v.setY(j * mCellSize + (scale-1) * mCellSize /2); 406 v.setScaleX((float) scale); 407 v.setScaleY((float) scale); 408 v.setRotation(rot); 409 } 410 } 411 412 private Point[] getOccupied(View v) { 413 final int scale = (Integer) v.getTag(TAG_SPAN); 414 final Point pt = (Point)v.getTag(TAG_POS); 415 if (pt == null || scale == 0) return new Point[0]; 416 417 final Point[] result = new Point[scale * scale]; 418 int p=0; 419 for (int i=0; i<scale; i++) { 420 for (int j=0; j<scale; j++) { 421 result[p++] = new Point(pt.x + i, pt.y + j); 422 } 423 } 424 return result; 425 } 426 427 static float frand() { 428 return (float)(Math.random()); 429 } 430 431 static float frand(float a, float b) { 432 return (frand() * (b-a) + a); 433 } 434 435 static int irand(int a, int b) { 436 return (int)(frand(a, b)); 437 } 438 439 @Override 440 public void onDraw(Canvas c) { 441 super.onDraw(c); 442 if (!DEBUG) return; 443 444 Paint pt = new Paint(); 445 pt.setStyle(Paint.Style.STROKE); 446 pt.setColor(0xFFCCCCCC); 447 pt.setStrokeWidth(2.0f); 448 449 final Rect check = new Rect(); 450 final int N = getChildCount(); 451 for (int i = 0; i < N; i++) { 452 View stone = getChildAt(i); 453 454 stone.getHitRect(check); 455 456 c.drawRect(check, pt); 457 } 458 } 459 460 public static class RescalingContainer extends FrameLayout { 461 private static final int SYSTEM_UI_MODE_800 = 0x00000800; 462 private DessertCaseView mView; 463 private float mDarkness; 464 465 public RescalingContainer(Context context) { 466 super(context); 467 468 setSystemUiVisibility(0 469 | View.SYSTEM_UI_FLAG_FULLSCREEN 470 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 471 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 472 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 473 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 474 | SYSTEM_UI_MODE_800 475 ); 476 } 477 478 public void setView(DessertCaseView v) { 479 addView(v); 480 mView = v; 481 } 482 483 @Override 484 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 485 final float w = right-left; 486 final float h = bottom-top; 487 final int w2 = (int) (w / mView.SCALE / 2); 488 final int h2 = (int) (h / mView.SCALE / 2); 489 final int cx = (int) (left + w * 0.5f); 490 final int cy = (int) (top + h * 0.5f); 491 mView.layout(cx - w2, cy - h2, cx + w2, cy + h2); 492 } 493 494 public void setDarkness(float p) { 495 mDarkness = p; 496 getDarkness(); 497 final int x = (int) (p * 0xff); 498 setBackgroundColor(x << 24 & 0xFF000000); 499 } 500 501 public float getDarkness() { 502 return mDarkness; 503 } 504 } 505} 506