CarouselView.java revision 7cb0068e59dde61ef0e649735199e5ba31c9c6af
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 com.android.ex.carousel.CarouselRS.CarouselCallback; 20 21import android.content.Context; 22import android.content.res.Resources; 23import android.graphics.Bitmap; 24import android.graphics.Bitmap.Config; 25import android.renderscript.FileA3D; 26import android.renderscript.Mesh; 27import android.renderscript.RSSurfaceView; 28import android.renderscript.RenderScriptGL; 29import android.util.AttributeSet; 30import android.util.Log; 31import android.view.MotionEvent; 32import android.view.SurfaceHolder; 33 34/** 35 * <p> 36 * This class represents the basic building block for using a 3D Carousel. The Carousel is 37 * basically a scene of cards and slots. The spacing between cards is dictated by the number 38 * of slots and the radius. The number of visible cards dictates how far the Carousel can be moved. 39 * If the number of cards exceeds the number of slots, then the Carousel will continue to go 40 * around until the last card can be seen. 41 */ 42public abstract class CarouselView extends RSSurfaceView { 43 private static final boolean USE_DEPTH_BUFFER = true; 44 private final int DEFAULT_SLOT_COUNT = 10; 45 private final float DEFAULT_RADIUS = 20.0f; 46 private final float DEFAULT_SWAY_SENSITIVITY = 0.0f; 47 private final float DEFAULT_FRICTION_COEFFICIENT = 10.0f; 48 private final float DEFAULT_DRAG_FACTOR = 0.25f; 49 private static final String TAG = "CarouselView"; 50 private static final boolean DBG = false; 51 private CarouselRS mRenderScript; 52 private RenderScriptGL mRS; 53 private Context mContext; 54 private boolean mTracking; 55 56 // These shadow the state of the renderer in case the surface changes so the surface 57 // can be restored to its previous state. 58 private Bitmap mDefaultBitmap; 59 private Bitmap mLoadingBitmap; 60 private Bitmap mBackgroundBitmap; 61 private Bitmap mDefaultLineBitmap = Bitmap.createBitmap( 62 new int[] {0x80ffffff, 0xffffffff, 0x80ffffff}, 0, 3, 3, 1, Bitmap.Config.ARGB_4444); 63 private Mesh mDefaultGeometry; 64 private Mesh mLoadingGeometry; 65 private int mCardCount = 0; 66 private int mVisibleSlots = 0; 67 private float mStartAngle; 68 private float mRadius = DEFAULT_RADIUS; 69 private float mCardRotation = 0.0f; 70 private float mSwaySensitivity = DEFAULT_SWAY_SENSITIVITY; 71 private float mFrictionCoefficient = DEFAULT_FRICTION_COEFFICIENT; 72 private float mDragFactor = DEFAULT_DRAG_FACTOR; 73 private int mSlotCount = DEFAULT_SLOT_COUNT; 74 private float mEye[] = { 20.6829f, 2.77081f, 16.7314f }; 75 private float mAt[] = { 14.7255f, -3.40001f, -1.30184f }; 76 private float mUp[] = { 0.0f, 1.0f, 0.0f }; 77 78 public static class Info { 79 public Info(int _resId) { resId = _resId; } 80 public int resId; // resource for renderscript resource (e.g. R.raw.carousel) 81 } 82 83 public abstract Info getRenderScriptInfo(); 84 85 public CarouselView(Context context) { 86 this(context, null); 87 } 88 89 /** 90 * Constructor used when this widget is created from a layout file. 91 */ 92 public CarouselView(Context context, AttributeSet attrs) { 93 super(context, attrs); 94 mContext = context; 95 boolean useDepthBuffer = true; 96 ensureRenderScript(); 97 // TODO: add parameters to layout 98 } 99 100 private void ensureRenderScript() { 101 mRS = createRenderScript(USE_DEPTH_BUFFER); 102 mRenderScript = new CarouselRS(); 103 mRenderScript.init(mRS, getResources(), getRenderScriptInfo().resId); 104 } 105 106 @Override 107 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 108 super.surfaceChanged(holder, format, w, h); 109 //mRS.contextSetSurface(w, h, holder.getSurface()); 110 mRenderScript.init(mRS, getResources(), getRenderScriptInfo().resId); 111 setSlotCount(mSlotCount); 112 createCards(mCardCount); 113 setVisibleSlots(mVisibleSlots); 114 setCallback(mCarouselCallback); 115 setDefaultBitmap(mDefaultBitmap); 116 setLoadingBitmap(mLoadingBitmap); 117 setDefaultGeometry(mDefaultGeometry); 118 setLoadingGeometry(mLoadingGeometry); 119 setBackgroundBitmap(mBackgroundBitmap); 120 setDetailLineBitmap(mDefaultLineBitmap); 121 setStartAngle(mStartAngle); 122 setRadius(mRadius); 123 setCardRotation(mCardRotation); 124 setSwaySensitivity(mSwaySensitivity); 125 setFrictionCoefficient(mFrictionCoefficient); 126 setDragFactor(mDragFactor); 127 setLookAt(mEye, mAt, mUp); 128 } 129 130 /** 131 * Loads geometry from a resource id. 132 * 133 * @param resId 134 * @return the loaded mesh or null if it cannot be loaded 135 */ 136 public Mesh loadGeometry(int resId) { 137 Resources res = mContext.getResources(); 138 FileA3D model = FileA3D.createFromResource(mRS, res, resId); 139 FileA3D.IndexEntry entry = model.getIndexEntry(0); 140 if(entry == null || entry.getClassID() != FileA3D.ClassID.MESH) { 141 return null; 142 } 143 return (Mesh) entry.getObject(); 144 } 145 146 /** 147 * Load A3D file from resource. If resId == 0, will clear geometry for this item. 148 * @param n 149 * @param resId 150 */ 151 public void setGeometryForItem(int n, Mesh mesh) { 152 if (mRenderScript != null) { 153 mRenderScript.setGeometry(n, mesh); 154 } 155 } 156 157 /** 158 * Set the number of slots around the Carousel. Basically equivalent to the poles horses 159 * might attach to on a real Carousel. 160 * 161 * @param n the number of slots 162 */ 163 public void setSlotCount(int n) { 164 mSlotCount = n; 165 if (mRenderScript != null) { 166 mRenderScript.setSlotCount(n); 167 } 168 } 169 170 /** 171 * Sets the number of visible slots around the Carousel. This is primarily used as a cheap 172 * form of clipping. The Carousel will never show more than this many cards. 173 * @param n the number of visible slots 174 */ 175 public void setVisibleSlots(int n) { 176 mVisibleSlots = n; 177 if (mRenderScript != null) { 178 mRenderScript.setVisibleSlots(n); 179 } 180 } 181 182 /** 183 * This dictates how many cards are in the deck. If the number of cards is greater than the 184 * number of slots, then the Carousel goes around n / slot_count times. 185 * 186 * Can be called again to increase or decrease the number of cards. 187 * 188 * @param n the number of cards to create. 189 */ 190 public void createCards(int n) { 191 mCardCount = n; 192 if (mRenderScript != null) { 193 mRenderScript.createCards(n); 194 } 195 } 196 197 /** 198 * This sets the texture on card n. It should only be called in response to 199 * {@link CarouselCallback#onRequestTexture(int)}. Since there's no guarantee 200 * that a given texture is still on the screen, replacing this texture should be done 201 * by first setting it to null and then waiting for the next 202 * {@link CarouselCallback#onRequestTexture(int)} to swap it with the new one. 203 * 204 * @param n the card given by {@link CarouselCallback#onRequestTexture(int)} 205 * @param bitmap the bitmap image to show 206 */ 207 public void setTextureForItem(int n, Bitmap bitmap) { 208 // Also check against mRS, to handle the case where the result is being delivered by a 209 // background thread but the sender no longer exists. 210 if (mRenderScript != null && mRS != null) { 211 if (DBG) Log.v(TAG, "setTextureForItem(" + n + ")"); 212 mRenderScript.setTexture(n, bitmap); 213 if (DBG) Log.v(TAG, "done"); 214 } 215 } 216 217 /** 218 * This sets the detail texture that floats above card n. It should only be called in response 219 * to {@link CarouselCallback#onRequestDetailTexture(int)}. Since there's no guarantee 220 * that a given texture is still on the screen, replacing this texture should be done 221 * by first setting it to null and then waiting for the next 222 * {@link CarouselCallback#onRequestDetailTexture(int)} to swap it with the new one. 223 * 224 * @param n the card to set the help text 225 * @param offx an optional offset to apply to the texture, in pixel coordinates 226 * @param offy an optional offset to apply to the texture, in pixel coordinates 227 * @param bitmap the bitmap to show as the detail 228 */ 229 public void setDetailTextureForItem(int n, float offx, float offy, Bitmap bitmap) { 230 if (mRenderScript != null) { 231 if (DBG) Log.v(TAG, "setDetailTextureForItem(" + n + ")"); 232 mRenderScript.setDetailTexture(n, offx, offy, bitmap); 233 if (DBG) Log.v(TAG, "done"); 234 } 235 } 236 237 /** 238 * Sets the bitmap to show on a card when the card draws the very first time. 239 * Generally, this bitmap will only be seen during the first few frames of startup 240 * or when the number of cards are changed. It can be ignored in most cases, 241 * as the cards will generally only be in the loading or loaded state. 242 * 243 * @param bitmap 244 */ 245 public void setDefaultBitmap(Bitmap bitmap) { 246 mDefaultBitmap = bitmap; 247 if (mRenderScript != null) { 248 mRenderScript.setDefaultBitmap(bitmap); 249 } 250 } 251 252 /** 253 * Sets the bitmap to show on the card while the texture is loading. It is set to this 254 * value just before {@link CarouselCallback#onRequestTexture(int)} is called and changed 255 * when {@link CarouselView#setTextureForItem(int, Bitmap)} is called. It is shared by all 256 * cards. 257 * 258 * @param bitmap 259 */ 260 public void setLoadingBitmap(Bitmap bitmap) { 261 mLoadingBitmap = bitmap; 262 if (mRenderScript != null) { 263 mRenderScript.setLoadingBitmap(bitmap); 264 } 265 } 266 267 /** 268 * Can be used to optionally set the background to a bitmap. 269 * 270 * @param bitmap 271 */ 272 public void setBackgroundBitmap(Bitmap bitmap) { 273 mBackgroundBitmap = bitmap; 274 if (mRenderScript != null) { 275 mRenderScript.setBackgroundTexture(bitmap); 276 } 277 } 278 279 /** 280 * This texture is used to draw a line from the card alongside the texture detail. The line 281 * will be as wide as the texture. It can be used to give the line glow effects as well as 282 * allowing other blending effects. It is typically one dimensional, e.g. 3x1. 283 * 284 * @param bitmap 285 */ 286 public void setDetailLineBitmap(Bitmap bitmap) { 287 mDefaultLineBitmap = bitmap; 288 if (mRenderScript != null) { 289 mRenderScript.setDetailLineTexture(bitmap); 290 } 291 } 292 293 /** 294 * This geometry will be shown when no geometry has been loaded for a given slot. If not set, 295 * a quad will be drawn in its place. It is shared for all cards. 296 * 297 * @param mesh 298 */ 299 public void setDefaultGeometry(Mesh mesh) { 300 mDefaultGeometry = mesh; 301 if (mRenderScript != null) { 302 mRenderScript.setDefaultGeometry(mesh); 303 } 304 } 305 306 /** 307 * This is an intermediate version of the object to show while geometry is loading. If not set, 308 * a quad will be drawn in its place. It is shared for all cards. 309 * 310 * @param mesh 311 */ 312 public void setLoadingGeometry(Mesh mesh) { 313 mLoadingGeometry = mesh; 314 if (mRenderScript != null) { 315 mRenderScript.setLoadingGeometry(mesh); 316 } 317 } 318 319 /** 320 * Sets the callback for receiving events from RenderScript. 321 * 322 * @param callback 323 */ 324 public void setCallback(CarouselCallback callback) 325 { 326 mCarouselCallback = callback; 327 if (mRenderScript != null) { 328 mRenderScript.setCallback(callback); 329 } 330 } 331 332 /** 333 * Sets the startAngle for the Carousel. The start angle is the first position of the first 334 * slot draw. Cards will be drawn from this angle in a counter-clockwise manner around the 335 * Carousel. 336 * 337 * @param angle the angle, in radians. 338 */ 339 public void setStartAngle(float angle) 340 { 341 mStartAngle = angle; 342 if (mRenderScript != null) { 343 mRenderScript.setStartAngle(angle); 344 } 345 } 346 347 public void setRadius(float radius) { 348 mRadius = radius; 349 if (mRenderScript != null) { 350 mRenderScript.setRadius(radius); 351 } 352 } 353 354 public void setCardRotation(float cardRotation) { 355 mCardRotation = cardRotation; 356 if (mRenderScript != null) { 357 mRenderScript.setCardRotation(cardRotation); 358 } 359 } 360 361 public void setSwaySensitivity(float swaySensitivity) { 362 mSwaySensitivity = swaySensitivity; 363 if (mRenderScript != null) { 364 mRenderScript.setSwaySensitivity(swaySensitivity); 365 } 366 } 367 368 public void setFrictionCoefficient(float frictionCoefficient) { 369 mFrictionCoefficient = frictionCoefficient; 370 if (mRenderScript != null) { 371 mRenderScript.setFrictionCoefficient(frictionCoefficient); 372 } 373 } 374 375 public void setDragFactor(float dragFactor) { 376 mDragFactor = dragFactor; 377 if (mRenderScript != null) { 378 mRenderScript.setDragFactor(dragFactor); 379 } 380 } 381 382 public void setLookAt(float[] eye, float[] at, float[] up) { 383 mEye = eye; 384 mAt = at; 385 mUp = up; 386 if (mRenderScript != null) { 387 mRenderScript.setLookAt(eye, at, up); 388 } 389 } 390 391 public void requestFirstCardPosition() { 392 if (mRenderScript != null) { 393 mRenderScript.requestFirstCardPosition(); 394 } 395 } 396 397 @Override 398 protected void onDetachedFromWindow() { 399 super.onDetachedFromWindow(); 400 if(mRS != null) { 401 mRS = null; 402 destroyRenderScript(); 403 } 404 } 405 406 @Override 407 protected void onAttachedToWindow() { 408 super.onAttachedToWindow(); 409 ensureRenderScript(); 410 } 411 412 @Override 413 public boolean onTouchEvent(MotionEvent event) { 414 final int action = event.getAction(); 415 final float x = event.getX(); 416 final float y = event.getY(); 417 418 if (mRenderScript == null) { 419 return true; 420 } 421 422 switch (action) { 423 case MotionEvent.ACTION_DOWN: 424 mTracking = true; 425 mRenderScript.doStart(x, y); 426 break; 427 428 case MotionEvent.ACTION_MOVE: 429 if (mTracking) { 430 mRenderScript.doMotion(x, y); 431 } 432 break; 433 434 case MotionEvent.ACTION_UP: 435 mRenderScript.doStop(x, y); 436 mTracking = false; 437 break; 438 } 439 440 return true; 441 } 442 443 private final CarouselCallback DEBUG_CALLBACK = new CarouselCallback() { 444 @Override 445 public void onAnimationStarted() { 446 if (DBG) Log.v(TAG, "onAnimationStarted()"); 447 } 448 449 @Override 450 public void onAnimationFinished() { 451 if (DBG) Log.v(TAG, "onAnimationFinished()"); 452 } 453 454 @Override 455 public void onCardSelected(int n) { 456 if (DBG) Log.v(TAG, "onCardSelected(" + n + ")"); 457 } 458 459 @Override 460 public void onRequestGeometry(int n) { 461 if (DBG) Log.v(TAG, "onRequestGeometry(" + n + ")"); 462 } 463 464 @Override 465 public void onInvalidateGeometry(int n) { 466 if (DBG) Log.v(TAG, "onInvalidateGeometry(" + n + ")"); 467 } 468 469 @Override 470 public void onRequestTexture(int n) { 471 if (DBG) Log.v(TAG, "onRequestTexture(" + n + ")"); 472 } 473 474 @Override 475 public void onInvalidateTexture(int n) { 476 if (DBG) Log.v(TAG, "onInvalidateTexture(" + n + ")"); 477 } 478 479 public void onRequestDetailTexture(int n) { 480 if (DBG) Log.v(TAG, "onRequestDetailTexture(" + n + ")"); 481 } 482 483 public void onInvalidateDetailTexture(int n) { 484 if (DBG) Log.v(TAG, "onInvalidateDetailTexture(" + n + ")"); 485 } 486 487 @Override 488 public void onReportFirstCardPosition(int n) { 489 Log.v(TAG, "onReportFirstCardPosition(" + n + ")"); 490 } 491 }; 492 493 private CarouselCallback mCarouselCallback = DEBUG_CALLBACK; 494 495} 496