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