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 17 package com.android.ex.carousel; 18 19 import android.view.View; 20 import com.android.ex.carousel.CarouselRS.CarouselCallback; 21 22 import android.content.Context; 23 import android.graphics.Bitmap; 24 import android.renderscript.Float4; 25 import android.renderscript.Mesh; 26 import android.renderscript.RSSurfaceView; 27 import android.renderscript.RenderScriptGL; 28 import android.util.AttributeSet; 29 import android.view.MotionEvent; 30 import android.view.SurfaceHolder; 31 32 /** 33 * <p> 34 * This class represents the basic building block for using a 3D Carousel. The Carousel is 35 * basically a scene of cards and slots. The spacing between cards is dictated by the number 36 * of slots and the radius. The number of visible cards dictates how far the Carousel can be moved. 37 * If the number of cards exceeds the number of slots, then the Carousel will continue to go 38 * around until the last card can be seen. 39 */ 40 public abstract class CarouselView extends RSSurfaceView { 41 private static final boolean USE_DEPTH_BUFFER = true; 42 private static final String TAG = "CarouselView"; 43 private CarouselRS mRenderScript; 44 private RenderScriptGL mRS; 45 private Context mContext; 46 private boolean mTracking; 47 48 CarouselController mController; 49 50 // Drag relative to x coordinate of motion on screen 51 public static final int DRAG_MODEL_SCREEN_DELTA = CarouselRS.DRAG_MODEL_SCREEN_DELTA; 52 // Drag relative to projected point on plane of carousel 53 public static final int DRAG_MODEL_PLANE = CarouselRS.DRAG_MODEL_PLANE; 54 // Drag relative to projected point on inside (far point) of cylinder centered around carousel 55 public static final int DRAG_MODEL_CYLINDER_INSIDE = CarouselRS.DRAG_MODEL_CYLINDER_INSIDE; 56 // Drag relative to projected point on outside (near point) of cylinder centered around carousel 57 public static final int DRAG_MODEL_CYLINDER_OUTSIDE = CarouselRS.DRAG_MODEL_CYLINDER_OUTSIDE; 58 59 // Draw cards counterclockwise around the carousel 60 public static final int FILL_DIRECTION_CCW = CarouselRS.FILL_DIRECTION_CCW; 61 // Draw cards clockwise around the carousel 62 public static final int FILL_DIRECTION_CW = CarouselRS.FILL_DIRECTION_CW; 63 64 // Note: remember to update carousel.rs when changing the values below 65 public static class InterpolationMode { 66 /** y= x **/ 67 public static final int LINEAR = 0; 68 /** The quadratic curve y= 1 - (1 - x)^2 moves quickly towards the target 69 * while decelerating constantly. **/ 70 public static final int DECELERATE_QUADRATIC = 1; 71 /** The cubic curve y= (3-2x)*x^2 gradually accelerates at the origin, 72 * and decelerates near the target. **/ 73 public static final int ACCELERATE_DECELERATE_CUBIC = 2; 74 } 75 76 // Note: remember to update carousel.rs when changing the values below 77 public static class DetailAlignment { 78 /** Detail is centered vertically with respect to the card **/ 79 public static final int CENTER_VERTICAL = 1; 80 /** Detail is aligned with the top edge of the carousel view **/ 81 public static final int VIEW_TOP = 1 << 1; 82 /** Detail is aligned with the bottom edge of the carousel view (not yet implemented) **/ 83 public static final int VIEW_BOTTOM = 1 << 2; 84 /** Detail is positioned above the card (not yet implemented) **/ 85 public static final int ABOVE = 1 << 3; 86 /** Detail is positioned below the card **/ 87 public static final int BELOW = 1 << 4; 88 /** Mask that selects those bits that control vertical alignment **/ 89 public static final int VERTICAL_ALIGNMENT_MASK = 0xff; 90 91 /** 92 * Detail is centered horizontally with respect to either the top or bottom 93 * extent of the card, depending on whether the detail is above or below the card. 94 */ 95 public static final int CENTER_HORIZONTAL = 1 << 8; 96 /** 97 * Detail is aligned with the left edge of either the top or the bottom of 98 * the card, depending on whether the detail is above or below the card. 99 */ 100 public static final int LEFT = 1 << 9; 101 /** 102 * Detail is aligned with the right edge of either the top or the bottom of 103 * the card, depending on whether the detail is above or below the card. 104 * (not yet implemented) 105 */ 106 public static final int RIGHT = 1 << 10; 107 /** Mask that selects those bits that control horizontal alignment **/ 108 public static final int HORIZONTAL_ALIGNMENT_MASK = 0xff00; 109 } 110 111 public static class Info { 112 public Info(int _resId) { resId = _resId; } 113 public int resId; // resource for renderscript resource (e.g. R.raw.carousel) 114 } 115 116 public abstract Info getRenderScriptInfo(); 117 118 public CarouselView(Context context) { 119 this(context, new CarouselController()); 120 } 121 122 public CarouselView(Context context, CarouselController controller) { 123 this(context, null, controller); 124 } 125 126 /** 127 * Constructor used when this widget is created from a layout file. 128 */ 129 public CarouselView(Context context, AttributeSet attrs) { 130 this(context, attrs, new CarouselController()); 131 } 132 133 public CarouselView(Context context, AttributeSet attrs, CarouselController controller) { 134 super(context, attrs); 135 mContext = context; 136 mController = controller; 137 boolean useDepthBuffer = true; 138 ensureRenderScript(); 139 // TODO: add parameters to layout 140 141 setOnLongClickListener(new View.OnLongClickListener() { 142 public boolean onLongClick(View v) { 143 if (interpretLongPressEvents()) { 144 mController.onLongPress(); 145 return true; 146 } else { 147 return false; 148 } 149 } 150 }); 151 } 152 153 private void ensureRenderScript() { 154 if (mRS == null) { 155 RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig(); 156 if (USE_DEPTH_BUFFER) { 157 sc.setDepth(16, 24); 158 } 159 mRS = createRenderScriptGL(sc); 160 } 161 if (mRenderScript == null) { 162 mRenderScript = new CarouselRS(mRS, mContext.getResources(), 163 getRenderScriptInfo().resId); 164 mRenderScript.resumeRendering(); 165 } 166 mController.setRS(mRS, mRenderScript); 167 } 168 169 @Override 170 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 171 super.surfaceChanged(holder, format, w, h); 172 // setZOrderOnTop(true); 173 mController.onSurfaceChanged(); 174 } 175 176 public CarouselController getController() { 177 return mController; 178 } 179 180 public void setController(CarouselController controller) { 181 mController = controller; 182 mController.setRS(mRS, mRenderScript); 183 } 184 185 /** 186 * Do I want to interpret the long-press gesture? If so, long-presses will cancel the 187 * current selection and call the appropriate callbacks. Otherwise, a long press will 188 * not be handled any way other than as a continued drag. 189 * 190 * @return True if we interpret long-presses 191 */ 192 public boolean interpretLongPressEvents() { 193 return false; 194 } 195 196 /** 197 * Loads geometry from a resource id. 198 * 199 * @param resId 200 * @return the loaded mesh or null if it cannot be loaded 201 */ 202 public Mesh loadGeometry(int resId) { 203 return mController.loadGeometry(resId); 204 } 205 206 /** 207 * Set the geometry for a given item. 208 * @param n 209 * @param mesh 210 */ 211 public void setGeometryForItem(int n, Mesh mesh) { 212 mController.setGeometryForItem(n, mesh); 213 } 214 215 /** 216 * Set the matrix for a given item. 217 * @param n 218 * @param matrix the requested matrix; null to just use the default 219 */ 220 public void setMatrixForItem(int n, float[] matrix) { 221 mController.setMatrixForItem(n, matrix); 222 } 223 224 /** 225 * Set the number of slots around the Carousel. Basically equivalent to the poles horses 226 * might attach to on a real Carousel. 227 * 228 * @param n the number of slots 229 */ 230 public void setSlotCount(int n) { 231 mController.setSlotCount(n); 232 } 233 234 /** 235 * Sets the number of visible slots around the Carousel. This is primarily used as a cheap 236 * form of clipping. The Carousel will never show more than this many cards. 237 * @param n the number of visible slots 238 */ 239 public void setVisibleSlots(int n) { 240 mController.setVisibleSlots(n); 241 } 242 243 /** 244 * Set the number of cards to pre-load that are outside of the visible region, as determined by 245 * setVisibleSlots(). This number gets added to the number of visible slots and used to 246 * determine when resources for cards should be loaded. This number should be small (n <= 4) 247 * for systems with limited texture memory or views that show more than half dozen cards in the 248 * view. 249 * 250 * @param n the number of cards; should be even, so the count is the same on each side 251 */ 252 public void setPrefetchCardCount(int n) { 253 mController.setPrefetchCardCount(n); 254 } 255 256 /** 257 * Sets the number of rows of cards to show in each slot. 258 */ 259 public void setRowCount(int n) { 260 mController.setRowCount(n); 261 } 262 263 /** 264 * Sets the spacing between each row of cards when rowCount > 1. 265 */ 266 public void setRowSpacing(float s) { 267 mController.setRowSpacing(s); 268 } 269 270 /** 271 * Sets the position of the first card when rowCount > 1. 272 */ 273 public void setFirstCardTop(boolean f) { 274 mController.setFirstCardTop(f); 275 } 276 277 /** 278 * Sets the amount of allowed overscroll (in slots) 279 */ 280 public void setOverscrollSlots(float slots) { 281 mController.setOverscrollSlots(slots); 282 } 283 284 /** 285 * Set the number of detail textures that can be visible at one time. 286 * 287 * @param n the number of slots 288 */ 289 public void setVisibleDetails(int n) { 290 mController.setVisibleDetails(n); 291 } 292 293 /** 294 * Sets how detail textures are aligned with respect to the card. 295 * 296 * @param alignment a bitmask of DetailAlignment flags. 297 */ 298 public void setDetailTextureAlignment(int alignment) { 299 mController.setDetailTextureAlignment(alignment); 300 } 301 302 /** 303 * Set whether depth is enabled while blending. Generally, this is discouraged because 304 * it causes bad artifacts. Careful attention to geometry and alpha transparency of 305 * textures can mitigate much of this. For example, geometry for an item must be drawn 306 * back-to-front if any edges overlap. 307 * 308 * @param enabled True to enable depth while blending, and false to disable it. 309 */ 310 public void setForceBlendCardsWithZ(boolean enabled) { 311 mController.setForceBlendCardsWithZ(enabled); 312 } 313 314 /** 315 * Set whether to draw a ruler from the card to the detail texture 316 * 317 * @param drawRuler True to draw a ruler, false to draw nothing where the ruler would go. 318 */ 319 public void setDrawRuler(boolean drawRuler) { 320 mController.setDrawRuler(drawRuler); 321 } 322 323 /** 324 * This dictates how many cards are in the deck. If the number of cards is greater than the 325 * number of slots, then the Carousel goes around n / slot_count times. 326 * 327 * Can be called again to increase or decrease the number of cards. 328 * 329 * @param n the number of cards to create. 330 */ 331 public void createCards(int n) { 332 mController.createCards(n); 333 } 334 335 public int getCardCount() { 336 return mController.getCardCount(); 337 } 338 339 /** 340 * This sets the texture on card n. It should only be called in response to 341 * {@link CarouselCallback#onRequestTexture(int)}. Since there's no guarantee 342 * that a given texture is still on the screen, replacing this texture should be done 343 * by first setting it to null and then waiting for the next 344 * {@link CarouselCallback#onRequestTexture(int)} to swap it with the new one. 345 * 346 * @param n the card given by {@link CarouselCallback#onRequestTexture(int)} 347 * @param bitmap the bitmap image to show 348 */ 349 public void setTextureForItem(int n, Bitmap bitmap) { 350 mController.setTextureForItem(n, bitmap); 351 } 352 353 /** 354 * This sets the detail texture that floats above card n. It should only be called in response 355 * to {@link CarouselCallback#onRequestDetailTexture(int)}. Since there's no guarantee 356 * that a given texture is still on the screen, replacing this texture should be done 357 * by first setting it to null and then waiting for the next 358 * {@link CarouselCallback#onRequestDetailTexture(int)} to swap it with the new one. 359 * 360 * @param n the card to set detail texture for 361 * @param offx an optional offset to apply to the texture (in pixels) from top of detail line 362 * @param offy an optional offset to apply to the texture (in pixels) from top of detail line 363 * @param loffx an optional offset to apply to the line (in pixels) from left edge of card 364 * @param loffy an optional offset to apply to the line (in pixels) from top of screen 365 * @param bitmap the bitmap to show as the detail 366 */ 367 public void setDetailTextureForItem(int n, float offx, float offy, float loffx, float loffy, 368 Bitmap bitmap) { 369 mController.setDetailTextureForItem(n, offx, offy, loffx, loffy, bitmap); 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 mController.setDefaultBitmap(bitmap); 382 } 383 384 /** 385 * Sets the bitmap to show on the card while the texture is loading. It is set to this 386 * value just before {@link CarouselCallback#onRequestTexture(int)} is called and changed 387 * when {@link CarouselView#setTextureForItem(int, Bitmap)} is called. It is shared by all 388 * cards. 389 * 390 * @param bitmap 391 */ 392 public void setLoadingBitmap(Bitmap bitmap) { 393 mController.setLoadingBitmap(bitmap); 394 } 395 396 /** 397 * Sets background to specified color. If a background texture is specified with 398 * {@link CarouselView#setBackgroundBitmap(Bitmap)}, then this call has no effect. 399 * 400 * @param red the amount of red 401 * @param green the amount of green 402 * @param blue the amount of blue 403 * @param alpha the amount of alpha 404 */ 405 public void setBackgroundColor(float red, float green, float blue, float alpha) { 406 mController.setBackgroundColor(red, green, blue, alpha); 407 } 408 409 /** 410 * Can be used to optionally set the background to a bitmap. When set to something other than 411 * null, this overrides {@link CarouselView#setBackgroundColor(Float4)}. 412 * 413 * @param bitmap 414 */ 415 public void setBackgroundBitmap(Bitmap bitmap) { 416 mController.setBackgroundBitmap(bitmap); 417 } 418 419 /** 420 * Can be used to optionally set a "loading" detail bitmap. Typically, this is just a black 421 * texture with alpha = 0 to allow details to slowly fade in. 422 * 423 * @param bitmap 424 */ 425 public void setDetailLoadingBitmap(Bitmap bitmap) { 426 mController.setDetailLoadingBitmap(bitmap); 427 } 428 429 /** 430 * This texture is used to draw a line from the card alongside the texture detail. The line 431 * will be as wide as the texture. It can be used to give the line glow effects as well as 432 * allowing other blending effects. It is typically one dimensional, e.g. 3x1. 433 * 434 * @param bitmap 435 */ 436 public void setDetailLineBitmap(Bitmap bitmap) { 437 mController.setDetailLineBitmap(bitmap); 438 } 439 440 /** 441 * This geometry will be shown when no geometry has been loaded for a given slot. If not set, 442 * a quad will be drawn in its place. It is shared for all cards. If something other than 443 * simple planar geometry is used, consider enabling depth test with 444 * {@link CarouselView#setForceBlendCardsWithZ(boolean)} 445 * 446 * @param resId 447 */ 448 public void setDefaultGeometry(int resId) { 449 mController.setDefaultGeometry(resId); 450 } 451 452 /** 453 * Sets the matrix used to transform card geometries. By default, this 454 * is the identity matrix, but you can specify a different matrix if you 455 * want to scale, translate and / or rotate the card before drawing. 456 * 457 * @param matrix array of 9 or 16 floats representing a 3x3 or 4x4 matrix, 458 * or null as a shortcut for an identity matrix. 459 */ 460 public void setDefaultCardMatrix(float[] matrix) { 461 mController.setDefaultCardMatrix(matrix); 462 } 463 464 /** 465 * This is an intermediate version of the object to show while geometry is loading. If not set, 466 * a quad will be drawn in its place. It is shared for all cards. If something other than 467 * simple planar geometry is used, consider enabling depth test with 468 * {@link CarouselView#setForceBlendCardsWithZ(boolean)} 469 * 470 * @param resId 471 */ 472 public void setLoadingGeometry(int resId) { 473 mController.setLoadingGeometry(resId); 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 mController.setCallback(callback); 484 } 485 486 /** 487 * Sets the startAngle for the Carousel. The start angle is the first position of the first 488 * slot draw. Cards will be drawn from this angle in a counter-clockwise manner around the 489 * Carousel. 490 * 491 * @param angle the angle, in radians. 492 */ 493 public void setStartAngle(float angle) 494 { 495 mController.setStartAngle(angle); 496 } 497 498 public void setRadius(float radius) { 499 mController.setRadius(radius); 500 } 501 502 public void setCardRotation(float cardRotation) { 503 mController.setCardRotation(cardRotation); 504 } 505 506 public void setCardsFaceTangent(boolean faceTangent) { 507 mController.setCardsFaceTangent(faceTangent); 508 } 509 510 public void setSwaySensitivity(float swaySensitivity) { 511 mController.setSwaySensitivity(swaySensitivity); 512 } 513 514 public void setFrictionCoefficient(float frictionCoefficient) { 515 mController.setFrictionCoefficient(frictionCoefficient); 516 } 517 518 public void setDragFactor(float dragFactor) { 519 mController.setDragFactor(dragFactor); 520 } 521 522 public void setDragModel(int model) { 523 mController.setDragModel(model); 524 } 525 526 public void setLookAt(float[] eye, float[] at, float[] up) { 527 mController.setLookAt(eye, at, up); 528 } 529 530 /** 531 * This sets the number of cards in the distance that will be shown "rezzing in". 532 * These alpha values will be faded in from the background to the foreground over 533 * 'n' cards. A floating point value is used to allow subtly changing the rezzing in 534 * position. 535 * 536 * @param n the number of cards to rez in. 537 */ 538 public void setRezInCardCount(float n) { 539 mController.setRezInCardCount(n); 540 } 541 542 /** 543 * This sets the duration (in ms) that a card takes to fade in when loaded via a call 544 * to {@link CarouselView#setTextureForItem(int, Bitmap)}. The timer starts the 545 * moment {@link CarouselView#setTextureForItem(int, Bitmap)} is called and continues 546 * until all of the cards have faded in. Note: using large values will extend the 547 * animation until all cards have faded in. 548 * 549 * @param t 550 */ 551 public void setFadeInDuration(long t) { 552 mController.setFadeInDuration(t); 553 } 554 555 @Override 556 protected void onDetachedFromWindow() { 557 super.onDetachedFromWindow(); 558 mRenderScript = null; 559 if (mRS != null) { 560 mRS = null; 561 destroyRenderScriptGL(); 562 } 563 mController.setRS(mRS, mRenderScript); 564 } 565 566 @Override 567 protected void onAttachedToWindow() { 568 super.onAttachedToWindow(); 569 ensureRenderScript(); 570 } 571 572 @Override 573 public boolean onTouchEvent(MotionEvent event) { 574 super.onTouchEvent(event); 575 final int action = event.getAction(); 576 577 if (mRenderScript == null) { 578 return true; 579 } 580 581 switch (action) { 582 case MotionEvent.ACTION_DOWN: 583 mTracking = true; 584 mController.onTouchStarted(event.getX(), event.getY(), event.getEventTime()); 585 break; 586 587 case MotionEvent.ACTION_MOVE: 588 if (mTracking) { 589 for (int i = 0; i < event.getHistorySize(); i++) { 590 mController.onTouchMoved(event.getHistoricalX(i), event.getHistoricalY(i), 591 event.getHistoricalEventTime(i)); 592 } 593 mController.onTouchMoved(event.getX(), event.getY(), event.getEventTime()); 594 } 595 break; 596 597 case MotionEvent.ACTION_UP: 598 mController.onTouchStopped(event.getX(), event.getY(), event.getEventTime()); 599 mTracking = false; 600 break; 601 } 602 603 return true; 604 } 605 } 606