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