CarouselView.java revision b378af500b36226635b6343b1d5009ee9af44fc1
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.ex.carousel;
18
19import com.android.ex.carousel.CarouselRS.CarouselCallback;
20
21import android.content.Context;
22import android.content.res.Resources;
23import android.graphics.Bitmap;
24import android.graphics.PixelFormat;
25import android.graphics.Bitmap.Config;
26import android.renderscript.FileA3D;
27import android.renderscript.Float4;
28import android.renderscript.Mesh;
29import android.renderscript.RSSurfaceView;
30import android.renderscript.RenderScriptGL;
31import android.util.AttributeSet;
32import android.util.Log;
33import android.view.MotionEvent;
34import android.view.SurfaceHolder;
35
36/**
37 * <p>
38 * This class represents the basic building block for using a 3D Carousel. The Carousel is
39 * basically a scene of cards and slots.  The spacing between cards is dictated by the number
40 * of slots and the radius. The number of visible cards dictates how far the Carousel can be moved.
41 * If the number of cards exceeds the number of slots, then the Carousel will continue to go
42 * around until the last card can be seen.
43 */
44public abstract class CarouselView extends RSSurfaceView {
45    private static final boolean USE_DEPTH_BUFFER = true;
46    private final int DEFAULT_SLOT_COUNT = 10;
47    private final float DEFAULT_RADIUS = 20.0f;
48    private final int DEFAULT_VISIBLE_DETAIL_COUNT = 3;
49    private final float DEFAULT_SWAY_SENSITIVITY = 0.0f;
50    private final float DEFAULT_FRICTION_COEFFICIENT = 10.0f;
51    private final float DEFAULT_DRAG_FACTOR = 0.25f;
52    private static final String TAG = "CarouselView";
53    private static final boolean DBG = false;
54    private CarouselRS mRenderScript;
55    private RenderScriptGL mRS;
56    private Context mContext;
57    private boolean mTracking;
58
59    // These shadow the state of the renderer in case the surface changes so the surface
60    // can be restored to its previous state.
61    private Bitmap mDefaultBitmap;
62    private Bitmap mLoadingBitmap;
63    private Bitmap mBackgroundBitmap;
64    private Bitmap mDefaultLineBitmap = Bitmap.createBitmap(
65            new int[] {0x80ffffff, 0xffffffff, 0x80ffffff}, 0, 3, 3, 1, Bitmap.Config.ARGB_4444);
66    private Mesh mDefaultGeometry;
67    private Mesh mLoadingGeometry;
68    private int mCardCount = 0;
69    private int mVisibleSlots = 0;
70    private int mVisibleDetails = DEFAULT_VISIBLE_DETAIL_COUNT;
71    private boolean mDrawDetailBelowCard = false;
72    private boolean mDrawRuler = true;
73    private float mStartAngle;
74    private float mRadius = DEFAULT_RADIUS;
75    private float mCardRotation = 0.0f;
76    private float mSwaySensitivity = DEFAULT_SWAY_SENSITIVITY;
77    private float mFrictionCoefficient = DEFAULT_FRICTION_COEFFICIENT;
78    private float mDragFactor = DEFAULT_DRAG_FACTOR;
79    private int mSlotCount = DEFAULT_SLOT_COUNT;
80    private float mEye[] = { 20.6829f, 2.77081f, 16.7314f };
81    private float mAt[] = { 14.7255f, -3.40001f, -1.30184f };
82    private float mUp[] = { 0.0f, 1.0f, 0.0f };
83    private Float4 mBackgroundColor = new Float4(0.0f, 0.0f, 0.0f, 1.0f);
84    private CarouselCallback mCarouselCallback;
85    private float mRezInCardCount = 0.0f;
86    private long mFadeInDuration = 250L;
87    private Bitmap mDetailLoadingBitmap = Bitmap.createBitmap(
88            new int[] {0}, 0, 1, 1, 1, Bitmap.Config.ARGB_4444);
89
90    public static class Info {
91        public Info(int _resId) { resId = _resId; }
92        public int resId; // resource for renderscript resource (e.g. R.raw.carousel)
93    }
94
95    public abstract Info getRenderScriptInfo();
96
97    public CarouselView(Context context) {
98        this(context, null);
99    }
100
101    /**
102     * Constructor used when this widget is created from a layout file.
103     */
104    public CarouselView(Context context, AttributeSet attrs) {
105        super(context, attrs);
106        mContext = context;
107        boolean useDepthBuffer = true;
108        ensureRenderScript();
109        // TODO: add parameters to layout
110    }
111
112    private void ensureRenderScript() {
113        if (mRS == null) {
114            mRS = createRenderScript(USE_DEPTH_BUFFER);
115        }
116        mRenderScript = new CarouselRS();
117        mRenderScript.init(mRS, getResources(), getRenderScriptInfo().resId);
118    }
119
120    @Override
121    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
122        super.surfaceChanged(holder, format, w, h);
123        //mRS.contextSetSurface(w, h, holder.getSurface());
124        mRenderScript.init(mRS, getResources(), getRenderScriptInfo().resId);
125        setSlotCount(mSlotCount);
126        createCards(mCardCount);
127        setVisibleSlots(mVisibleSlots);
128        setVisibleDetails(mVisibleDetails);
129        setDrawDetailBelowCard(mDrawDetailBelowCard);
130        setDrawRuler(mDrawRuler);
131        setCallback(mCarouselCallback);
132        setDefaultBitmap(mDefaultBitmap);
133        setLoadingBitmap(mLoadingBitmap);
134        setDefaultGeometry(mDefaultGeometry);
135        setLoadingGeometry(mLoadingGeometry);
136        setBackgroundColor(mBackgroundColor.x, mBackgroundColor.y, mBackgroundColor.z,
137                mBackgroundColor.w);
138        setBackgroundBitmap(mBackgroundBitmap);
139        setDetailLineBitmap(mDefaultLineBitmap);
140        setStartAngle(mStartAngle);
141        setRadius(mRadius);
142        setCardRotation(mCardRotation);
143        setSwaySensitivity(mSwaySensitivity);
144        setFrictionCoefficient(mFrictionCoefficient);
145        setDragFactor(mDragFactor);
146        setLookAt(mEye, mAt, mUp);
147        setRezInCardCount(mRezInCardCount);
148        setFadeInDuration(mFadeInDuration);
149        setDetailLoadingBitmap(mDetailLoadingBitmap);
150    }
151
152    /**
153     * Loads geometry from a resource id.
154     *
155     * @param resId
156     * @return the loaded mesh or null if it cannot be loaded
157     */
158    public Mesh loadGeometry(int resId) {
159        Resources res = mContext.getResources();
160        FileA3D model = FileA3D.createFromResource(mRS, res, resId);
161        FileA3D.IndexEntry entry = model.getIndexEntry(0);
162        if(entry == null || entry.getClassID() != FileA3D.ClassID.MESH) {
163            return null;
164        }
165        return (Mesh) entry.getObject();
166    }
167
168    /**
169     * Load A3D file from resource.  If resId == 0, will clear geometry for this item.
170     * @param n
171     * @param resId
172     */
173    public void setGeometryForItem(int n, Mesh mesh) {
174        if (mRenderScript != null) {
175            mRenderScript.setGeometry(n, mesh);
176        }
177    }
178
179    /**
180     * Set the number of slots around the Carousel. Basically equivalent to the poles horses
181     * might attach to on a real Carousel.
182     *
183     * @param n the number of slots
184     */
185    public void setSlotCount(int n) {
186        mSlotCount = n;
187        if (mRenderScript != null) {
188            mRenderScript.setSlotCount(n);
189        }
190    }
191
192    /**
193     * Sets the number of visible slots around the Carousel.  This is primarily used as a cheap
194     * form of clipping. The Carousel will never show more than this many cards.
195     * @param n the number of visible slots
196     */
197    public void setVisibleSlots(int n) {
198        mVisibleSlots = n;
199        if (mRenderScript != null) {
200            mRenderScript.setVisibleSlots(n);
201        }
202    }
203
204    /**
205     * Set the number of detail textures that can be visible at one time.
206     *
207     * @param n the number of slots
208     */
209    public void setVisibleDetails(int n) {
210        mVisibleDetails = n;
211        if (mRenderScript != null) {
212            mRenderScript.setVisibleDetails(n);
213        }
214    }
215
216    /**
217     * Set whether to draw the detail texture above or below the card.
218     *
219     * @param below False for above, true for below.
220     */
221    public void setDrawDetailBelowCard(boolean below) {
222        mDrawDetailBelowCard = below;
223        if (mRenderScript != null) {
224            mRenderScript.setDrawDetailBelowCard(below);
225        }
226    }
227
228    /**
229     * Set whether to draw a ruler from the card to the detail texture
230     *
231     * @param drawRuler True to draw a ruler, false to draw nothing where the ruler would go.
232     */
233    public void setDrawRuler(boolean drawRuler) {
234        mDrawRuler = drawRuler;
235        if (mRenderScript != null) {
236            mRenderScript.setDrawRuler(drawRuler);
237        }
238    }
239
240    /**
241     * This dictates how many cards are in the deck.  If the number of cards is greater than the
242     * number of slots, then the Carousel goes around n / slot_count times.
243     *
244     * Can be called again to increase or decrease the number of cards.
245     *
246     * @param n the number of cards to create.
247     */
248    public void createCards(int n) {
249        mCardCount = n;
250        if (mRenderScript != null) {
251            mRenderScript.createCards(n);
252        }
253    }
254
255    public int getCardCount() {
256        return mCardCount;
257    }
258
259    /**
260     * This sets the texture on card n.  It should only be called in response to
261     * {@link CarouselCallback#onRequestTexture(int)}.  Since there's no guarantee
262     * that a given texture is still on the screen, replacing this texture should be done
263     * by first setting it to null and then waiting for the next
264     * {@link CarouselCallback#onRequestTexture(int)} to swap it with the new one.
265     *
266     * @param n the card given by {@link CarouselCallback#onRequestTexture(int)}
267     * @param bitmap the bitmap image to show
268     */
269    public void setTextureForItem(int n, Bitmap bitmap) {
270        // Also check against mRS, to handle the case where the result is being delivered by a
271        // background thread but the sender no longer exists.
272        if (mRenderScript != null && mRS != null) {
273            if (DBG) Log.v(TAG, "setTextureForItem(" + n + ")");
274            mRenderScript.setTexture(n, bitmap);
275            if (DBG) Log.v(TAG, "done");
276        }
277    }
278
279    /**
280     * This sets the detail texture that floats above card n. It should only be called in response
281     * to {@link CarouselCallback#onRequestDetailTexture(int)}.  Since there's no guarantee
282     * that a given texture is still on the screen, replacing this texture should be done
283     * by first setting it to null and then waiting for the next
284     * {@link CarouselCallback#onRequestDetailTexture(int)} to swap it with the new one.
285     *
286     * @param n the card to set detail texture for
287     * @param offx an optional offset to apply to the texture (in pixels) from top of detail line
288     * @param offy an optional offset to apply to the texture (in pixels) from top of detail line
289     * @param loffx an optional offset to apply to the line (in pixels) from left edge of card
290     * @param loffy an optional offset to apply to the line (in pixels) from top of screen
291     * @param bitmap the bitmap to show as the detail
292     */
293    public void setDetailTextureForItem(int n, float offx, float offy, float loffx, float loffy,
294            Bitmap bitmap) {
295        if (mRenderScript != null) {
296            if (DBG) Log.v(TAG, "setDetailTextureForItem(" + n + ")");
297            mRenderScript.setDetailTexture(n, offx, offy, loffx, loffy, bitmap);
298            if (DBG) Log.v(TAG, "done");
299        }
300    }
301
302    /**
303     * Sets the bitmap to show on a card when the card draws the very first time.
304     * Generally, this bitmap will only be seen during the first few frames of startup
305     * or when the number of cards are changed.  It can be ignored in most cases,
306     * as the cards will generally only be in the loading or loaded state.
307     *
308     * @param bitmap
309     */
310    public void setDefaultBitmap(Bitmap bitmap) {
311        mDefaultBitmap = bitmap;
312        if (mRenderScript != null) {
313            mRenderScript.setDefaultBitmap(bitmap);
314        }
315    }
316
317    /**
318     * Sets the bitmap to show on the card while the texture is loading. It is set to this
319     * value just before {@link CarouselCallback#onRequestTexture(int)} is called and changed
320     * when {@link CarouselView#setTextureForItem(int, Bitmap)} is called. It is shared by all
321     * cards.
322     *
323     * @param bitmap
324     */
325    public void setLoadingBitmap(Bitmap bitmap) {
326        mLoadingBitmap = bitmap;
327        if (mRenderScript != null) {
328            mRenderScript.setLoadingBitmap(bitmap);
329        }
330    }
331
332    /**
333     * Sets background to specified color.  If a background texture is specified with
334     * {@link CarouselView#setBackgroundBitmap(Bitmap)}, then this call has no effect.
335     *
336     * @param red the amount of red
337     * @param green the amount of green
338     * @param blue the amount of blue
339     * @param alpha the amount of alpha
340     */
341    public void setBackgroundColor(float red, float green, float blue, float alpha) {
342        mBackgroundColor = new Float4(red, green, blue, alpha);
343        if (mRenderScript != null) {
344            mRenderScript.setBackgroundColor(mBackgroundColor);
345        }
346    }
347
348    /**
349     * Can be used to optionally set the background to a bitmap. When set to something other than
350     * null, this overrides {@link CarouselView#setBackgroundColor(Float4)}.
351     *
352     * @param bitmap
353     */
354    public void setBackgroundBitmap(Bitmap bitmap) {
355        mBackgroundBitmap = bitmap;
356        if (mRenderScript != null) {
357            mRenderScript.setBackgroundTexture(bitmap);
358        }
359    }
360
361    /**
362     * Can be used to optionally set a "loading" detail bitmap. Typically, this is just a black
363     * texture with alpha = 0 to allow details to slowly fade in.
364     *
365     * @param bitmap
366     */
367    public void setDetailLoadingBitmap(Bitmap bitmap) {
368        mDetailLoadingBitmap = bitmap;
369        if (mRenderScript != null) {
370            mRenderScript.setDetailLoadingTexture(bitmap);
371        }
372    }
373
374    /**
375     * This texture is used to draw a line from the card alongside the texture detail. The line
376     * will be as wide as the texture. It can be used to give the line glow effects as well as
377     * allowing other blending effects. It is typically one dimensional, e.g. 3x1.
378     *
379     * @param bitmap
380     */
381    public void setDetailLineBitmap(Bitmap bitmap) {
382        mDefaultLineBitmap = bitmap;
383        if (mRenderScript != null) {
384            mRenderScript.setDetailLineTexture(bitmap);
385        }
386    }
387
388    /**
389     * This geometry will be shown when no geometry has been loaded for a given slot. If not set,
390     * a quad will be drawn in its place. It is shared for all cards.
391     *
392     * @param mesh
393     */
394    public void setDefaultGeometry(Mesh mesh) {
395        mDefaultGeometry = mesh;
396        if (mRenderScript != null) {
397            mRenderScript.setDefaultGeometry(mesh);
398        }
399    }
400
401    /**
402     * This is an intermediate version of the object to show while geometry is loading. If not set,
403     * a quad will be drawn in its place.  It is shared for all cards.
404     *
405     * @param mesh
406     */
407    public void setLoadingGeometry(Mesh mesh) {
408        mLoadingGeometry = mesh;
409        if (mRenderScript != null) {
410            mRenderScript.setLoadingGeometry(mesh);
411        }
412    }
413
414    /**
415     * Sets the callback for receiving events from RenderScript.
416     *
417     * @param callback
418     */
419    public void setCallback(CarouselCallback callback)
420    {
421        mCarouselCallback = callback;
422        if (mRenderScript != null) {
423            mRenderScript.setCallback(callback);
424        }
425    }
426
427    /**
428     * Sets the startAngle for the Carousel. The start angle is the first position of the first
429     * slot draw.  Cards will be drawn from this angle in a counter-clockwise manner around the
430     * Carousel.
431     *
432     * @param angle the angle, in radians.
433     */
434    public void setStartAngle(float angle)
435    {
436        mStartAngle = angle;
437        if (mRenderScript != null) {
438            mRenderScript.setStartAngle(angle);
439        }
440    }
441
442    public void setRadius(float radius) {
443        mRadius = radius;
444        if (mRenderScript != null) {
445            mRenderScript.setRadius(radius);
446        }
447    }
448
449    public void setCardRotation(float cardRotation) {
450        mCardRotation = cardRotation;
451        if (mRenderScript != null) {
452            mRenderScript.setCardRotation(cardRotation);
453        }
454    }
455
456    public void setSwaySensitivity(float swaySensitivity) {
457        mSwaySensitivity = swaySensitivity;
458        if (mRenderScript != null) {
459            mRenderScript.setSwaySensitivity(swaySensitivity);
460        }
461    }
462
463    public void setFrictionCoefficient(float frictionCoefficient) {
464        mFrictionCoefficient = frictionCoefficient;
465        if (mRenderScript != null) {
466            mRenderScript.setFrictionCoefficient(frictionCoefficient);
467        }
468    }
469
470    public void setDragFactor(float dragFactor) {
471        mDragFactor = dragFactor;
472        if (mRenderScript != null) {
473            mRenderScript.setDragFactor(dragFactor);
474        }
475    }
476
477    public void setLookAt(float[] eye, float[] at, float[] up) {
478        mEye = eye;
479        mAt = at;
480        mUp = up;
481        if (mRenderScript != null) {
482            mRenderScript.setLookAt(eye, at, up);
483        }
484    }
485
486    public void requestFirstCardPosition() {
487        if (mRenderScript != null) {
488            mRenderScript.requestFirstCardPosition();
489        }
490    }
491
492    /**
493     * This sets the number of cards in the distance that will be shown "rezzing in".
494     * These alpha values will be faded in from the background to the foreground over
495     * 'n' cards.  A floating point value is used to allow subtly changing the rezzing in
496     * position.
497     *
498     * @param n the number of cards to rez in.
499     */
500    public void setRezInCardCount(float n) {
501        mRezInCardCount = n;
502        if (mRenderScript != null) {
503            mRenderScript.setRezInCardCount(n);
504        }
505    }
506
507    /**
508     * This sets the duration (in ms) that a card takes to fade in when loaded via a call
509     * to {@link CarouselView#setTextureForItem(int, Bitmap)}. The timer starts the
510     * moment {@link CarouselView#setTextureForItem(int, Bitmap)} is called and continues
511     * until all of the cards have faded in.  Note: using large values will extend the
512     * animation until all cards have faded in.
513     *
514     * @param t
515     */
516    public void setFadeInDuration(long t) {
517        mFadeInDuration = t;
518        if (mRenderScript != null) {
519            mRenderScript.setFadeInDuration(t);
520        }
521    }
522
523    @Override
524    protected void onDetachedFromWindow() {
525        super.onDetachedFromWindow();
526        mRenderScript = null;
527        if (mRS != null) {
528            mRS = null;
529            destroyRenderScript();
530        }
531    }
532
533    @Override
534    protected void onAttachedToWindow() {
535        super.onAttachedToWindow();
536        ensureRenderScript();
537    }
538
539    @Override
540    public boolean onTouchEvent(MotionEvent event) {
541        final int action = event.getAction();
542        final float x = event.getX();
543        final float y = event.getY();
544
545        if (mRenderScript == null) {
546            return true;
547        }
548
549        switch (action) {
550            case MotionEvent.ACTION_DOWN:
551                mTracking = true;
552                mRenderScript.doStart(x, y);
553                break;
554
555            case MotionEvent.ACTION_MOVE:
556                if (mTracking) {
557                    mRenderScript.doMotion(x, y);
558                }
559                break;
560
561            case MotionEvent.ACTION_UP:
562                mRenderScript.doStop(x, y);
563                mTracking = false;
564                break;
565        }
566
567        return true;
568    }
569}
570