CarouselView.java revision 7cb0068e59dde61ef0e649735199e5ba31c9c6af
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.Bitmap.Config;
25import android.renderscript.FileA3D;
26import android.renderscript.Mesh;
27import android.renderscript.RSSurfaceView;
28import android.renderscript.RenderScriptGL;
29import android.util.AttributeSet;
30import android.util.Log;
31import android.view.MotionEvent;
32import android.view.SurfaceHolder;
33
34/**
35 * <p>
36 * This class represents the basic building block for using a 3D Carousel. The Carousel is
37 * basically a scene of cards and slots.  The spacing between cards is dictated by the number
38 * of slots and the radius. The number of visible cards dictates how far the Carousel can be moved.
39 * If the number of cards exceeds the number of slots, then the Carousel will continue to go
40 * around until the last card can be seen.
41 */
42public abstract class CarouselView extends RSSurfaceView {
43    private static final boolean USE_DEPTH_BUFFER = true;
44    private final int DEFAULT_SLOT_COUNT = 10;
45    private final float DEFAULT_RADIUS = 20.0f;
46    private final float DEFAULT_SWAY_SENSITIVITY = 0.0f;
47    private final float DEFAULT_FRICTION_COEFFICIENT = 10.0f;
48    private final float DEFAULT_DRAG_FACTOR = 0.25f;
49    private static final String TAG = "CarouselView";
50    private static final boolean DBG = false;
51    private CarouselRS mRenderScript;
52    private RenderScriptGL mRS;
53    private Context mContext;
54    private boolean mTracking;
55
56    // These shadow the state of the renderer in case the surface changes so the surface
57    // can be restored to its previous state.
58    private Bitmap mDefaultBitmap;
59    private Bitmap mLoadingBitmap;
60    private Bitmap mBackgroundBitmap;
61    private Bitmap mDefaultLineBitmap = Bitmap.createBitmap(
62            new int[] {0x80ffffff, 0xffffffff, 0x80ffffff}, 0, 3, 3, 1, Bitmap.Config.ARGB_4444);
63    private Mesh mDefaultGeometry;
64    private Mesh mLoadingGeometry;
65    private int mCardCount = 0;
66    private int mVisibleSlots = 0;
67    private float mStartAngle;
68    private float mRadius = DEFAULT_RADIUS;
69    private float mCardRotation = 0.0f;
70    private float mSwaySensitivity = DEFAULT_SWAY_SENSITIVITY;
71    private float mFrictionCoefficient = DEFAULT_FRICTION_COEFFICIENT;
72    private float mDragFactor = DEFAULT_DRAG_FACTOR;
73    private int mSlotCount = DEFAULT_SLOT_COUNT;
74    private float mEye[] = { 20.6829f, 2.77081f, 16.7314f };
75    private float mAt[] = { 14.7255f, -3.40001f, -1.30184f };
76    private float mUp[] = { 0.0f, 1.0f, 0.0f };
77
78    public static class Info {
79        public Info(int _resId) { resId = _resId; }
80        public int resId; // resource for renderscript resource (e.g. R.raw.carousel)
81    }
82
83    public abstract Info getRenderScriptInfo();
84
85    public CarouselView(Context context) {
86        this(context, null);
87    }
88
89    /**
90     * Constructor used when this widget is created from a layout file.
91     */
92    public CarouselView(Context context, AttributeSet attrs) {
93        super(context, attrs);
94        mContext = context;
95        boolean useDepthBuffer = true;
96        ensureRenderScript();
97        // TODO: add parameters to layout
98    }
99
100    private void ensureRenderScript() {
101        mRS = createRenderScript(USE_DEPTH_BUFFER);
102        mRenderScript = new CarouselRS();
103        mRenderScript.init(mRS, getResources(), getRenderScriptInfo().resId);
104    }
105
106    @Override
107    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
108        super.surfaceChanged(holder, format, w, h);
109        //mRS.contextSetSurface(w, h, holder.getSurface());
110        mRenderScript.init(mRS, getResources(), getRenderScriptInfo().resId);
111        setSlotCount(mSlotCount);
112        createCards(mCardCount);
113        setVisibleSlots(mVisibleSlots);
114        setCallback(mCarouselCallback);
115        setDefaultBitmap(mDefaultBitmap);
116        setLoadingBitmap(mLoadingBitmap);
117        setDefaultGeometry(mDefaultGeometry);
118        setLoadingGeometry(mLoadingGeometry);
119        setBackgroundBitmap(mBackgroundBitmap);
120        setDetailLineBitmap(mDefaultLineBitmap);
121        setStartAngle(mStartAngle);
122        setRadius(mRadius);
123        setCardRotation(mCardRotation);
124        setSwaySensitivity(mSwaySensitivity);
125        setFrictionCoefficient(mFrictionCoefficient);
126        setDragFactor(mDragFactor);
127        setLookAt(mEye, mAt, mUp);
128    }
129
130    /**
131     * Loads geometry from a resource id.
132     *
133     * @param resId
134     * @return the loaded mesh or null if it cannot be loaded
135     */
136    public Mesh loadGeometry(int resId) {
137        Resources res = mContext.getResources();
138        FileA3D model = FileA3D.createFromResource(mRS, res, resId);
139        FileA3D.IndexEntry entry = model.getIndexEntry(0);
140        if(entry == null || entry.getClassID() != FileA3D.ClassID.MESH) {
141            return null;
142        }
143        return (Mesh) entry.getObject();
144    }
145
146    /**
147     * Load A3D file from resource.  If resId == 0, will clear geometry for this item.
148     * @param n
149     * @param resId
150     */
151    public void setGeometryForItem(int n, Mesh mesh) {
152        if (mRenderScript != null) {
153            mRenderScript.setGeometry(n, mesh);
154        }
155    }
156
157    /**
158     * Set the number of slots around the Carousel. Basically equivalent to the poles horses
159     * might attach to on a real Carousel.
160     *
161     * @param n the number of slots
162     */
163    public void setSlotCount(int n) {
164        mSlotCount = n;
165        if (mRenderScript != null) {
166            mRenderScript.setSlotCount(n);
167        }
168    }
169
170    /**
171     * Sets the number of visible slots around the Carousel.  This is primarily used as a cheap
172     * form of clipping. The Carousel will never show more than this many cards.
173     * @param n the number of visible slots
174     */
175    public void setVisibleSlots(int n) {
176        mVisibleSlots = n;
177        if (mRenderScript != null) {
178            mRenderScript.setVisibleSlots(n);
179        }
180    }
181
182    /**
183     * This dictates how many cards are in the deck.  If the number of cards is greater than the
184     * number of slots, then the Carousel goes around n / slot_count times.
185     *
186     * Can be called again to increase or decrease the number of cards.
187     *
188     * @param n the number of cards to create.
189     */
190    public void createCards(int n) {
191        mCardCount = n;
192        if (mRenderScript != null) {
193            mRenderScript.createCards(n);
194        }
195    }
196
197    /**
198     * This sets the texture on card n.  It should only be called in response to
199     * {@link CarouselCallback#onRequestTexture(int)}.  Since there's no guarantee
200     * that a given texture is still on the screen, replacing this texture should be done
201     * by first setting it to null and then waiting for the next
202     * {@link CarouselCallback#onRequestTexture(int)} to swap it with the new one.
203     *
204     * @param n the card given by {@link CarouselCallback#onRequestTexture(int)}
205     * @param bitmap the bitmap image to show
206     */
207    public void setTextureForItem(int n, Bitmap bitmap) {
208        // Also check against mRS, to handle the case where the result is being delivered by a
209        // background thread but the sender no longer exists.
210        if (mRenderScript != null && mRS != null) {
211            if (DBG) Log.v(TAG, "setTextureForItem(" + n + ")");
212            mRenderScript.setTexture(n, bitmap);
213            if (DBG) Log.v(TAG, "done");
214        }
215    }
216
217    /**
218     * This sets the detail texture that floats above card n. It should only be called in response
219     * to {@link CarouselCallback#onRequestDetailTexture(int)}.  Since there's no guarantee
220     * that a given texture is still on the screen, replacing this texture should be done
221     * by first setting it to null and then waiting for the next
222     * {@link CarouselCallback#onRequestDetailTexture(int)} to swap it with the new one.
223     *
224     * @param n the card to set the help text
225     * @param offx an optional offset to apply to the texture, in pixel coordinates
226     * @param offy an optional offset to apply to the texture, in pixel coordinates
227     * @param bitmap the bitmap to show as the detail
228     */
229    public void setDetailTextureForItem(int n, float offx, float offy, Bitmap bitmap) {
230        if (mRenderScript != null) {
231            if (DBG) Log.v(TAG, "setDetailTextureForItem(" + n + ")");
232            mRenderScript.setDetailTexture(n, offx, offy, bitmap);
233            if (DBG) Log.v(TAG, "done");
234        }
235    }
236
237    /**
238     * Sets the bitmap to show on a card when the card draws the very first time.
239     * Generally, this bitmap will only be seen during the first few frames of startup
240     * or when the number of cards are changed.  It can be ignored in most cases,
241     * as the cards will generally only be in the loading or loaded state.
242     *
243     * @param bitmap
244     */
245    public void setDefaultBitmap(Bitmap bitmap) {
246        mDefaultBitmap = bitmap;
247        if (mRenderScript != null) {
248            mRenderScript.setDefaultBitmap(bitmap);
249        }
250    }
251
252    /**
253     * Sets the bitmap to show on the card while the texture is loading. It is set to this
254     * value just before {@link CarouselCallback#onRequestTexture(int)} is called and changed
255     * when {@link CarouselView#setTextureForItem(int, Bitmap)} is called. It is shared by all
256     * cards.
257     *
258     * @param bitmap
259     */
260    public void setLoadingBitmap(Bitmap bitmap) {
261        mLoadingBitmap = bitmap;
262        if (mRenderScript != null) {
263            mRenderScript.setLoadingBitmap(bitmap);
264        }
265    }
266
267    /**
268     * Can be used to optionally set the background to a bitmap.
269     *
270     * @param bitmap
271     */
272    public void setBackgroundBitmap(Bitmap bitmap) {
273        mBackgroundBitmap = bitmap;
274        if (mRenderScript != null) {
275            mRenderScript.setBackgroundTexture(bitmap);
276        }
277    }
278
279    /**
280     * This texture is used to draw a line from the card alongside the texture detail. The line
281     * will be as wide as the texture. It can be used to give the line glow effects as well as
282     * allowing other blending effects. It is typically one dimensional, e.g. 3x1.
283     *
284     * @param bitmap
285     */
286    public void setDetailLineBitmap(Bitmap bitmap) {
287        mDefaultLineBitmap = bitmap;
288        if (mRenderScript != null) {
289            mRenderScript.setDetailLineTexture(bitmap);
290        }
291    }
292
293    /**
294     * This geometry will be shown when no geometry has been loaded for a given slot. If not set,
295     * a quad will be drawn in its place. It is shared for all cards.
296     *
297     * @param mesh
298     */
299    public void setDefaultGeometry(Mesh mesh) {
300        mDefaultGeometry = mesh;
301        if (mRenderScript != null) {
302            mRenderScript.setDefaultGeometry(mesh);
303        }
304    }
305
306    /**
307     * This is an intermediate version of the object to show while geometry is loading. If not set,
308     * a quad will be drawn in its place.  It is shared for all cards.
309     *
310     * @param mesh
311     */
312    public void setLoadingGeometry(Mesh mesh) {
313        mLoadingGeometry = mesh;
314        if (mRenderScript != null) {
315            mRenderScript.setLoadingGeometry(mesh);
316        }
317    }
318
319    /**
320     * Sets the callback for receiving events from RenderScript.
321     *
322     * @param callback
323     */
324    public void setCallback(CarouselCallback callback)
325    {
326        mCarouselCallback = callback;
327        if (mRenderScript != null) {
328            mRenderScript.setCallback(callback);
329        }
330    }
331
332    /**
333     * Sets the startAngle for the Carousel. The start angle is the first position of the first
334     * slot draw.  Cards will be drawn from this angle in a counter-clockwise manner around the
335     * Carousel.
336     *
337     * @param angle the angle, in radians.
338     */
339    public void setStartAngle(float angle)
340    {
341        mStartAngle = angle;
342        if (mRenderScript != null) {
343            mRenderScript.setStartAngle(angle);
344        }
345    }
346
347    public void setRadius(float radius) {
348        mRadius = radius;
349        if (mRenderScript != null) {
350            mRenderScript.setRadius(radius);
351        }
352    }
353
354    public void setCardRotation(float cardRotation) {
355        mCardRotation = cardRotation;
356        if (mRenderScript != null) {
357            mRenderScript.setCardRotation(cardRotation);
358        }
359    }
360
361    public void setSwaySensitivity(float swaySensitivity) {
362        mSwaySensitivity = swaySensitivity;
363        if (mRenderScript != null) {
364            mRenderScript.setSwaySensitivity(swaySensitivity);
365        }
366    }
367
368    public void setFrictionCoefficient(float frictionCoefficient) {
369        mFrictionCoefficient = frictionCoefficient;
370        if (mRenderScript != null) {
371            mRenderScript.setFrictionCoefficient(frictionCoefficient);
372        }
373    }
374
375    public void setDragFactor(float dragFactor) {
376        mDragFactor = dragFactor;
377        if (mRenderScript != null) {
378            mRenderScript.setDragFactor(dragFactor);
379        }
380    }
381
382    public void setLookAt(float[] eye, float[] at, float[] up) {
383        mEye = eye;
384        mAt = at;
385        mUp = up;
386        if (mRenderScript != null) {
387            mRenderScript.setLookAt(eye, at, up);
388        }
389    }
390
391    public void requestFirstCardPosition() {
392        if (mRenderScript != null) {
393            mRenderScript.requestFirstCardPosition();
394        }
395    }
396
397    @Override
398    protected void onDetachedFromWindow() {
399        super.onDetachedFromWindow();
400        if(mRS != null) {
401            mRS = null;
402            destroyRenderScript();
403        }
404    }
405
406    @Override
407    protected void onAttachedToWindow() {
408        super.onAttachedToWindow();
409        ensureRenderScript();
410    }
411
412    @Override
413    public boolean onTouchEvent(MotionEvent event) {
414        final int action = event.getAction();
415        final float x = event.getX();
416        final float y = event.getY();
417
418        if (mRenderScript == null) {
419            return true;
420        }
421
422        switch (action) {
423            case MotionEvent.ACTION_DOWN:
424                mTracking = true;
425                mRenderScript.doStart(x, y);
426                break;
427
428            case MotionEvent.ACTION_MOVE:
429                if (mTracking) {
430                    mRenderScript.doMotion(x, y);
431                }
432                break;
433
434            case MotionEvent.ACTION_UP:
435                mRenderScript.doStop(x, y);
436                mTracking = false;
437                break;
438        }
439
440        return true;
441    }
442
443    private final CarouselCallback DEBUG_CALLBACK = new CarouselCallback() {
444        @Override
445        public void onAnimationStarted() {
446            if (DBG) Log.v(TAG, "onAnimationStarted()");
447        }
448
449        @Override
450        public void onAnimationFinished() {
451            if (DBG) Log.v(TAG, "onAnimationFinished()");
452        }
453
454        @Override
455        public void onCardSelected(int n) {
456            if (DBG) Log.v(TAG, "onCardSelected(" + n + ")");
457        }
458
459        @Override
460        public void onRequestGeometry(int n) {
461            if (DBG) Log.v(TAG, "onRequestGeometry(" + n + ")");
462        }
463
464        @Override
465        public void onInvalidateGeometry(int n) {
466            if (DBG) Log.v(TAG, "onInvalidateGeometry(" + n + ")");
467        }
468
469        @Override
470        public void onRequestTexture(int n) {
471            if (DBG) Log.v(TAG, "onRequestTexture(" + n + ")");
472        }
473
474        @Override
475        public void onInvalidateTexture(int n) {
476            if (DBG) Log.v(TAG, "onInvalidateTexture(" + n + ")");
477        }
478
479        public void onRequestDetailTexture(int n) {
480            if (DBG) Log.v(TAG, "onRequestDetailTexture(" + n + ")");
481        }
482
483        public void onInvalidateDetailTexture(int n) {
484            if (DBG) Log.v(TAG, "onInvalidateDetailTexture(" + n + ")");
485        }
486
487        @Override
488        public void onReportFirstCardPosition(int n) {
489            Log.v(TAG, "onReportFirstCardPosition(" + n + ")");
490        }
491    };
492
493    private CarouselCallback mCarouselCallback = DEBUG_CALLBACK;
494
495}
496