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