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