CarouselView.java revision 4fe6ea729d1fc44c8126de7a92a710c3885fb2ec
1/* 2 * Copyright (C) 2010 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.ex.carousel; 18 19import android.view.View; 20import com.android.ex.carousel.CarouselRS.CarouselCallback; 21 22import android.content.Context; 23import android.content.res.Resources; 24import android.graphics.Bitmap; 25import android.graphics.PixelFormat; 26import android.graphics.Bitmap.Config; 27import android.renderscript.FileA3D; 28import android.renderscript.Float4; 29import android.renderscript.Mesh; 30import android.renderscript.RSSurfaceView; 31import android.renderscript.RenderScriptGL; 32import android.util.AttributeSet; 33import android.util.Log; 34import android.view.MotionEvent; 35import android.view.SurfaceHolder; 36 37/** 38 * <p> 39 * This class represents the basic building block for using a 3D Carousel. The Carousel is 40 * basically a scene of cards and slots. The spacing between cards is dictated by the number 41 * of slots and the radius. The number of visible cards dictates how far the Carousel can be moved. 42 * If the number of cards exceeds the number of slots, then the Carousel will continue to go 43 * around until the last card can be seen. 44 */ 45public abstract class CarouselView extends RSSurfaceView { 46 private static final boolean USE_DEPTH_BUFFER = true; 47 private final int DEFAULT_SLOT_COUNT = 10; 48 private final float DEFAULT_RADIUS = 20.0f; 49 private final int DEFAULT_VISIBLE_DETAIL_COUNT = 3; 50 private final int DEFAULT_PREFETCH_CARD_COUNT = 2; 51 private final float DEFAULT_SWAY_SENSITIVITY = 0.0f; 52 private final float DEFAULT_FRICTION_COEFFICIENT = 10.0f; 53 private final float DEFAULT_DRAG_FACTOR = 0.25f; 54 private static final String TAG = "CarouselView"; 55 private static final boolean DBG = false; 56 private CarouselRS mRenderScript; 57 private RenderScriptGL mRS; 58 private Context mContext; 59 private boolean mTracking; 60 61 // These shadow the state of the renderer in case the surface changes so the surface 62 // can be restored to its previous state. 63 private Bitmap mDefaultBitmap; 64 private Bitmap mLoadingBitmap; 65 private Bitmap mBackgroundBitmap; 66 private Bitmap mDefaultLineBitmap = Bitmap.createBitmap( 67 new int[] {0x00000000, 0xffffffff, 0x00000000}, 0, 3, 3, 1, Bitmap.Config.ARGB_4444); 68 private Mesh mDefaultGeometry; 69 private Mesh mLoadingGeometry; 70 private int mCardCount = 0; 71 private int mVisibleSlots = 0; 72 private int mVisibleDetails = DEFAULT_VISIBLE_DETAIL_COUNT; 73 private int mPrefetchCardCount = DEFAULT_PREFETCH_CARD_COUNT; 74 private boolean mDrawDetailBelowCard = false; 75 private boolean mDetailTexturesCentered = false; 76 private boolean mDrawRuler = true; 77 private float mStartAngle; 78 private float mRadius = DEFAULT_RADIUS; 79 private float mCardRotation = 0.0f; 80 private boolean mCardsFaceTangent = false; 81 private float mSwaySensitivity = DEFAULT_SWAY_SENSITIVITY; 82 private float mFrictionCoefficient = DEFAULT_FRICTION_COEFFICIENT; 83 private float mDragFactor = DEFAULT_DRAG_FACTOR; 84 private int mSlotCount = DEFAULT_SLOT_COUNT; 85 private float mEye[] = { 20.6829f, 2.77081f, 16.7314f }; 86 private float mAt[] = { 14.7255f, -3.40001f, -1.30184f }; 87 private float mUp[] = { 0.0f, 1.0f, 0.0f }; 88 private Float4 mBackgroundColor = new Float4(0.0f, 0.0f, 0.0f, 1.0f); 89 private CarouselCallback mCarouselCallback; 90 private float mRezInCardCount = 0.0f; 91 private long mFadeInDuration = 250L; 92 private Bitmap mDetailLoadingBitmap = Bitmap.createBitmap( 93 new int[] {0}, 0, 1, 1, 1, Bitmap.Config.ARGB_4444); 94 95 public static class Info { 96 public Info(int _resId) { resId = _resId; } 97 public int resId; // resource for renderscript resource (e.g. R.raw.carousel) 98 } 99 100 public abstract Info getRenderScriptInfo(); 101 102 public CarouselView(Context context) { 103 this(context, null); 104 } 105 106 /** 107 * Constructor used when this widget is created from a layout file. 108 */ 109 public CarouselView(Context context, AttributeSet attrs) { 110 super(context, attrs); 111 mContext = context; 112 boolean useDepthBuffer = true; 113 ensureRenderScript(); 114 // TODO: add parameters to layout 115 116 setOnLongClickListener(new OnLongClickListener() { 117 public boolean onLongClick(View v) { 118 if (interpretLongPressEvents()) { 119 mRenderScript.doLongPress(); 120 return true; 121 } else { 122 return false; 123 } 124 } 125 }); 126 } 127 128 private void ensureRenderScript() { 129 if (mRS == null) { 130 RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig(); 131 sc.setDepth(16, 24); 132 mRS = createRenderScript(sc); 133 } 134 if (mRenderScript == null) { 135 mRenderScript = new CarouselRS(mRS, getResources(), getRenderScriptInfo().resId); 136 mRenderScript.resumeRendering(); 137 } 138 } 139 140 @Override 141 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 142 super.surfaceChanged(holder, format, w, h); 143 //mRS.contextSetSurface(w, h, holder.getSurface()); 144 mRenderScript.resumeRendering(); 145 setSlotCount(mSlotCount); 146 createCards(mCardCount); 147 setVisibleSlots(mVisibleSlots); 148 setVisibleDetails(mVisibleDetails); 149 setPrefetchCardCount(mPrefetchCardCount); 150 setDrawDetailBelowCard(mDrawDetailBelowCard); 151 setDrawRuler(mDrawRuler); 152 setCallback(mCarouselCallback); 153 setDefaultBitmap(mDefaultBitmap); 154 setLoadingBitmap(mLoadingBitmap); 155 setDefaultGeometry(mDefaultGeometry); 156 setLoadingGeometry(mLoadingGeometry); 157 setBackgroundColor(mBackgroundColor.x, mBackgroundColor.y, mBackgroundColor.z, 158 mBackgroundColor.w); 159 setBackgroundBitmap(mBackgroundBitmap); 160 setDetailLineBitmap(mDefaultLineBitmap); 161 setStartAngle(mStartAngle); 162 setRadius(mRadius); 163 setCardRotation(mCardRotation); 164 setCardsFaceTangent(mCardsFaceTangent); 165 setSwaySensitivity(mSwaySensitivity); 166 setFrictionCoefficient(mFrictionCoefficient); 167 setDragFactor(mDragFactor); 168 setLookAt(mEye, mAt, mUp); 169 setRezInCardCount(mRezInCardCount); 170 setFadeInDuration(mFadeInDuration); 171 setDetailLoadingBitmap(mDetailLoadingBitmap); 172 } 173 174 /** 175 * Do I want to interpret the long-press gesture? If so, long-presses will cancel the 176 * current selection and call the appropriate callbacks. Otherwise, a long press will 177 * not be handled any way other than as a continued drag. 178 * 179 * @return True if we interpret long-presses 180 */ 181 public boolean interpretLongPressEvents() { 182 return false; 183 } 184 185 /** 186 * Loads geometry from a resource id. 187 * 188 * @param resId 189 * @return the loaded mesh or null if it cannot be loaded 190 */ 191 public Mesh loadGeometry(int resId) { 192 Resources res = mContext.getResources(); 193 FileA3D model = FileA3D.createFromResource(mRS, res, resId); 194 FileA3D.IndexEntry entry = model.getIndexEntry(0); 195 if(entry == null || entry.getClassID() != FileA3D.ClassID.MESH) { 196 return null; 197 } 198 return (Mesh) entry.getObject(); 199 } 200 201 /** 202 * Load A3D file from resource. If resId == 0, will clear geometry for this item. 203 * @param n 204 * @param resId 205 */ 206 public void setGeometryForItem(int n, Mesh mesh) { 207 if (mRenderScript != null) { 208 mRenderScript.setGeometry(n, mesh); 209 } 210 } 211 212 /** 213 * Set the number of slots around the Carousel. Basically equivalent to the poles horses 214 * might attach to on a real Carousel. 215 * 216 * @param n the number of slots 217 */ 218 public void setSlotCount(int n) { 219 mSlotCount = n; 220 if (mRenderScript != null) { 221 mRenderScript.setSlotCount(n); 222 } 223 } 224 225 /** 226 * Sets the number of visible slots around the Carousel. This is primarily used as a cheap 227 * form of clipping. The Carousel will never show more than this many cards. 228 * @param n the number of visible slots 229 */ 230 public void setVisibleSlots(int n) { 231 mVisibleSlots = n; 232 if (mRenderScript != null) { 233 mRenderScript.setVisibleSlots(n); 234 } 235 } 236 237 /** 238 * Set the number of detail textures that can be visible at one time. 239 * 240 * @param n the number of slots 241 */ 242 public void setVisibleDetails(int n) { 243 mVisibleDetails = n; 244 if (mRenderScript != null) { 245 mRenderScript.setVisibleDetails(n); 246 } 247 } 248 249 /** 250 * Set the number of cards to pre-load that are outside of the visible region, as determined by 251 * setVisibleSlots(). This number gets added to the number of visible slots and used to 252 * determine when resources for cards should be loaded. This number should be small (n <= 4) 253 * for systems with limited texture memory or views that show more than half dozen cards in the 254 * view. 255 * 256 * @param n the number of cards; should be even, so the count is the same on each side 257 */ 258 public void setPrefetchCardCount(int n) { 259 mPrefetchCardCount = n; 260 if (mRenderScript != null) { 261 mRenderScript.setPrefetchCardCount(n); 262 } 263 } 264 265 /** 266 * Set whether to draw the detail texture above or below the card. 267 * 268 * @param below False for above, true for below. 269 */ 270 public void setDrawDetailBelowCard(boolean below) { 271 mDrawDetailBelowCard = below; 272 if (mRenderScript != null) { 273 mRenderScript.setDrawDetailBelowCard(below); 274 } 275 } 276 277 /** 278 * Set whether to align the detail texture center with the card center. 279 * If not, left edges will be aligned instead. 280 * 281 * @param centered True for center-aligned, false for left-aligned. 282 */ 283 public void setDetailTexturesCentered(boolean centered) { 284 mDetailTexturesCentered = centered; 285 if (mRenderScript != null) { 286 mRenderScript.setDetailTexturesCentered(centered); 287 } 288 } 289 290 /** 291 * Set whether to draw a ruler from the card to the detail texture 292 * 293 * @param drawRuler True to draw a ruler, false to draw nothing where the ruler would go. 294 */ 295 public void setDrawRuler(boolean drawRuler) { 296 mDrawRuler = drawRuler; 297 if (mRenderScript != null) { 298 mRenderScript.setDrawRuler(drawRuler); 299 } 300 } 301 302 /** 303 * This dictates how many cards are in the deck. If the number of cards is greater than the 304 * number of slots, then the Carousel goes around n / slot_count times. 305 * 306 * Can be called again to increase or decrease the number of cards. 307 * 308 * @param n the number of cards to create. 309 */ 310 public void createCards(int n) { 311 mCardCount = n; 312 if (mRenderScript != null) { 313 mRenderScript.createCards(n); 314 } 315 } 316 317 public int getCardCount() { 318 return mCardCount; 319 } 320 321 /** 322 * This sets the texture on card n. It should only be called in response to 323 * {@link CarouselCallback#onRequestTexture(int)}. Since there's no guarantee 324 * that a given texture is still on the screen, replacing this texture should be done 325 * by first setting it to null and then waiting for the next 326 * {@link CarouselCallback#onRequestTexture(int)} to swap it with the new one. 327 * 328 * @param n the card given by {@link CarouselCallback#onRequestTexture(int)} 329 * @param bitmap the bitmap image to show 330 */ 331 public void setTextureForItem(int n, Bitmap bitmap) { 332 // Also check against mRS, to handle the case where the result is being delivered by a 333 // background thread but the sender no longer exists. 334 if (mRenderScript != null && mRS != null) { 335 if (DBG) Log.v(TAG, "setTextureForItem(" + n + ")"); 336 mRenderScript.setTexture(n, bitmap); 337 if (DBG) Log.v(TAG, "done"); 338 } 339 } 340 341 /** 342 * This sets the detail texture that floats above card n. It should only be called in response 343 * to {@link CarouselCallback#onRequestDetailTexture(int)}. Since there's no guarantee 344 * that a given texture is still on the screen, replacing this texture should be done 345 * by first setting it to null and then waiting for the next 346 * {@link CarouselCallback#onRequestDetailTexture(int)} to swap it with the new one. 347 * 348 * @param n the card to set detail texture for 349 * @param offx an optional offset to apply to the texture (in pixels) from top of detail line 350 * @param offy an optional offset to apply to the texture (in pixels) from top of detail line 351 * @param loffx an optional offset to apply to the line (in pixels) from left edge of card 352 * @param loffy an optional offset to apply to the line (in pixels) from top of screen 353 * @param bitmap the bitmap to show as the detail 354 */ 355 public void setDetailTextureForItem(int n, float offx, float offy, float loffx, float loffy, 356 Bitmap bitmap) { 357 if (mRenderScript != null) { 358 if (DBG) Log.v(TAG, "setDetailTextureForItem(" + n + ")"); 359 mRenderScript.setDetailTexture(n, offx, offy, loffx, loffy, bitmap); 360 if (DBG) Log.v(TAG, "done"); 361 } 362 } 363 364 /** 365 * Sets the bitmap to show on a card when the card draws the very first time. 366 * Generally, this bitmap will only be seen during the first few frames of startup 367 * or when the number of cards are changed. It can be ignored in most cases, 368 * as the cards will generally only be in the loading or loaded state. 369 * 370 * @param bitmap 371 */ 372 public void setDefaultBitmap(Bitmap bitmap) { 373 mDefaultBitmap = bitmap; 374 if (mRenderScript != null) { 375 mRenderScript.setDefaultBitmap(bitmap); 376 } 377 } 378 379 /** 380 * Sets the bitmap to show on the card while the texture is loading. It is set to this 381 * value just before {@link CarouselCallback#onRequestTexture(int)} is called and changed 382 * when {@link CarouselView#setTextureForItem(int, Bitmap)} is called. It is shared by all 383 * cards. 384 * 385 * @param bitmap 386 */ 387 public void setLoadingBitmap(Bitmap bitmap) { 388 mLoadingBitmap = bitmap; 389 if (mRenderScript != null) { 390 mRenderScript.setLoadingBitmap(bitmap); 391 } 392 } 393 394 /** 395 * Sets background to specified color. If a background texture is specified with 396 * {@link CarouselView#setBackgroundBitmap(Bitmap)}, then this call has no effect. 397 * 398 * @param red the amount of red 399 * @param green the amount of green 400 * @param blue the amount of blue 401 * @param alpha the amount of alpha 402 */ 403 public void setBackgroundColor(float red, float green, float blue, float alpha) { 404 mBackgroundColor = new Float4(red, green, blue, alpha); 405 if (mRenderScript != null) { 406 mRenderScript.setBackgroundColor(mBackgroundColor); 407 } 408 } 409 410 /** 411 * Can be used to optionally set the background to a bitmap. When set to something other than 412 * null, this overrides {@link CarouselView#setBackgroundColor(Float4)}. 413 * 414 * @param bitmap 415 */ 416 public void setBackgroundBitmap(Bitmap bitmap) { 417 mBackgroundBitmap = bitmap; 418 if (mRenderScript != null) { 419 mRenderScript.setBackgroundTexture(bitmap); 420 } 421 } 422 423 /** 424 * Can be used to optionally set a "loading" detail bitmap. Typically, this is just a black 425 * texture with alpha = 0 to allow details to slowly fade in. 426 * 427 * @param bitmap 428 */ 429 public void setDetailLoadingBitmap(Bitmap bitmap) { 430 mDetailLoadingBitmap = bitmap; 431 if (mRenderScript != null) { 432 mRenderScript.setDetailLoadingTexture(bitmap); 433 } 434 } 435 436 /** 437 * This texture is used to draw a line from the card alongside the texture detail. The line 438 * will be as wide as the texture. It can be used to give the line glow effects as well as 439 * allowing other blending effects. It is typically one dimensional, e.g. 3x1. 440 * 441 * @param bitmap 442 */ 443 public void setDetailLineBitmap(Bitmap bitmap) { 444 mDefaultLineBitmap = bitmap; 445 if (mRenderScript != null) { 446 mRenderScript.setDetailLineTexture(bitmap); 447 } 448 } 449 450 /** 451 * This geometry will be shown when no geometry has been loaded for a given slot. If not set, 452 * a quad will be drawn in its place. It is shared for all cards. 453 * 454 * @param mesh 455 */ 456 public void setDefaultGeometry(Mesh mesh) { 457 mDefaultGeometry = mesh; 458 if (mRenderScript != null) { 459 mRenderScript.setDefaultGeometry(mesh); 460 } 461 } 462 463 /** 464 * This is an intermediate version of the object to show while geometry is loading. If not set, 465 * a quad will be drawn in its place. It is shared for all cards. 466 * 467 * @param mesh 468 */ 469 public void setLoadingGeometry(Mesh mesh) { 470 mLoadingGeometry = mesh; 471 if (mRenderScript != null) { 472 mRenderScript.setLoadingGeometry(mesh); 473 } 474 } 475 476 /** 477 * Sets the callback for receiving events from RenderScript. 478 * 479 * @param callback 480 */ 481 public void setCallback(CarouselCallback callback) 482 { 483 mCarouselCallback = callback; 484 if (mRenderScript != null) { 485 mRenderScript.setCallback(callback); 486 } 487 } 488 489 /** 490 * Sets the startAngle for the Carousel. The start angle is the first position of the first 491 * slot draw. Cards will be drawn from this angle in a counter-clockwise manner around the 492 * Carousel. 493 * 494 * @param angle the angle, in radians. 495 */ 496 public void setStartAngle(float angle) 497 { 498 mStartAngle = angle; 499 if (mRenderScript != null) { 500 mRenderScript.setStartAngle(angle); 501 } 502 } 503 504 public void setRadius(float radius) { 505 mRadius = radius; 506 if (mRenderScript != null) { 507 mRenderScript.setRadius(radius); 508 } 509 } 510 511 public void setCardRotation(float cardRotation) { 512 mCardRotation = cardRotation; 513 if (mRenderScript != null) { 514 mRenderScript.setCardRotation(cardRotation); 515 } 516 } 517 518 public void setCardsFaceTangent(boolean faceTangent) { 519 mCardsFaceTangent = faceTangent; 520 if (mRenderScript != null) { 521 mRenderScript.setCardsFaceTangent(faceTangent); 522 } 523 } 524 525 public void setSwaySensitivity(float swaySensitivity) { 526 mSwaySensitivity = swaySensitivity; 527 if (mRenderScript != null) { 528 mRenderScript.setSwaySensitivity(swaySensitivity); 529 } 530 } 531 532 public void setFrictionCoefficient(float frictionCoefficient) { 533 mFrictionCoefficient = frictionCoefficient; 534 if (mRenderScript != null) { 535 mRenderScript.setFrictionCoefficient(frictionCoefficient); 536 } 537 } 538 539 public void setDragFactor(float dragFactor) { 540 mDragFactor = dragFactor; 541 if (mRenderScript != null) { 542 mRenderScript.setDragFactor(dragFactor); 543 } 544 } 545 546 public void setLookAt(float[] eye, float[] at, float[] up) { 547 mEye = eye; 548 mAt = at; 549 mUp = up; 550 if (mRenderScript != null) { 551 mRenderScript.setLookAt(eye, at, up); 552 } 553 } 554 555 public void requestFirstCardPosition() { 556 if (mRenderScript != null) { 557 mRenderScript.requestFirstCardPosition(); 558 } 559 } 560 561 /** 562 * This sets the number of cards in the distance that will be shown "rezzing in". 563 * These alpha values will be faded in from the background to the foreground over 564 * 'n' cards. A floating point value is used to allow subtly changing the rezzing in 565 * position. 566 * 567 * @param n the number of cards to rez in. 568 */ 569 public void setRezInCardCount(float n) { 570 mRezInCardCount = n; 571 if (mRenderScript != null) { 572 mRenderScript.setRezInCardCount(n); 573 } 574 } 575 576 /** 577 * This sets the duration (in ms) that a card takes to fade in when loaded via a call 578 * to {@link CarouselView#setTextureForItem(int, Bitmap)}. The timer starts the 579 * moment {@link CarouselView#setTextureForItem(int, Bitmap)} is called and continues 580 * until all of the cards have faded in. Note: using large values will extend the 581 * animation until all cards have faded in. 582 * 583 * @param t 584 */ 585 public void setFadeInDuration(long t) { 586 mFadeInDuration = t; 587 if (mRenderScript != null) { 588 mRenderScript.setFadeInDuration(t); 589 } 590 } 591 592 @Override 593 protected void onDetachedFromWindow() { 594 super.onDetachedFromWindow(); 595 mRenderScript = null; 596 if (mRS != null) { 597 mRS = null; 598 destroyRenderScript(); 599 } 600 } 601 602 @Override 603 protected void onAttachedToWindow() { 604 super.onAttachedToWindow(); 605 ensureRenderScript(); 606 } 607 608 @Override 609 public boolean onTouchEvent(MotionEvent event) { 610 super.onTouchEvent(event); 611 final int action = event.getAction(); 612 final float x = event.getX(); 613 final float y = event.getY(); 614 615 if (mRenderScript == null) { 616 return true; 617 } 618 619 switch (action) { 620 case MotionEvent.ACTION_DOWN: 621 mTracking = true; 622 mRenderScript.doStart(x, y); 623 break; 624 625 case MotionEvent.ACTION_MOVE: 626 if (mTracking) { 627 mRenderScript.doMotion(x, y); 628 } 629 break; 630 631 case MotionEvent.ACTION_UP: 632 mRenderScript.doStop(x, y); 633 mTracking = false; 634 break; 635 } 636 637 return true; 638 } 639} 640