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