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