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