CarouselView.java revision 1a5b4d109397ea175b5cbaa7490ca18e78eb040f
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.graphics.Bitmap;
24import android.renderscript.Float4;
25import android.renderscript.Mesh;
26import android.renderscript.RSSurfaceView;
27import android.renderscript.RenderScriptGL;
28import android.util.AttributeSet;
29import android.view.MotionEvent;
30import android.view.SurfaceHolder;
31
32/**
33 * <p>
34 * This class represents the basic building block for using a 3D Carousel. The Carousel is
35 * basically a scene of cards and slots.  The spacing between cards is dictated by the number
36 * of slots and the radius. The number of visible cards dictates how far the Carousel can be moved.
37 * If the number of cards exceeds the number of slots, then the Carousel will continue to go
38 * around until the last card can be seen.
39 */
40public abstract class CarouselView extends RSSurfaceView {
41    private static final boolean USE_DEPTH_BUFFER = true;
42    private static final String TAG = "CarouselView";
43    private CarouselRS mRenderScript;
44    private RenderScriptGL mRS;
45    private Context mContext;
46    private boolean mTracking;
47
48    CarouselController mController;
49
50    // Drag relative to x coordinate of motion on screen
51    public static final int DRAG_MODEL_SCREEN_DELTA = CarouselRS.DRAG_MODEL_SCREEN_DELTA;
52    // Drag relative to projected point on plane of carousel
53    public static final int DRAG_MODEL_PLANE = CarouselRS.DRAG_MODEL_PLANE;
54    // Drag relative to projected point on inside (far point) of cylinder centered around carousel
55    public static final int DRAG_MODEL_CYLINDER_INSIDE = CarouselRS.DRAG_MODEL_CYLINDER_INSIDE;
56    // Drag relative to projected point on outside (near point) of cylinder centered around carousel
57    public static final int DRAG_MODEL_CYLINDER_OUTSIDE = CarouselRS.DRAG_MODEL_CYLINDER_OUTSIDE;
58
59    // Draw cards counterclockwise around the carousel
60    public static final int FILL_DIRECTION_CCW = CarouselRS.FILL_DIRECTION_CCW;
61    // Draw cards clockwise around the carousel
62    public static final int FILL_DIRECTION_CW = CarouselRS.FILL_DIRECTION_CW;
63
64    // Note: remember to update carousel.rs when changing the values below
65    public static class DetailAlignment {
66        /** Detail is centered vertically with respect to the card **/
67        public static final int CENTER_VERTICAL = 1;
68        /** Detail is aligned with the top edge of the carousel view **/
69        public static final int VIEW_TOP = 1 << 1;
70        /** Detail is aligned with the bottom edge of the carousel view (not yet implemented) **/
71        public static final int VIEW_BOTTOM = 1 << 2;
72        /** Detail is positioned above the card (not yet implemented) **/
73        public static final int ABOVE = 1 << 3;
74        /** Detail is positioned below the card **/
75        public static final int BELOW = 1 << 4;
76        /** Mask that selects those bits that control vertical alignment **/
77        public static final int VERTICAL_ALIGNMENT_MASK = 0xff;
78
79        /**
80         * Detail is centered horizontally with respect to either the top or bottom
81         * extent of the card, depending on whether the detail is above or below the card.
82         */
83        public static final int CENTER_HORIZONTAL = 1 << 8;
84        /**
85         * Detail is aligned with the left edge of either the top or the bottom of
86         * the card, depending on whether the detail is above or below the card.
87         */
88        public static final int LEFT = 1 << 9;
89        /**
90         * Detail is aligned with the right edge of either the top or the bottom of
91         * the card, depending on whether the detail is above or below the card.
92         * (not yet implemented)
93         */
94        public static final int RIGHT = 1 << 10;
95        /** Mask that selects those bits that control horizontal alignment **/
96        public static final int HORIZONTAL_ALIGNMENT_MASK = 0xff00;
97    }
98
99    public static class Info {
100        public Info(int _resId) { resId = _resId; }
101        public int resId; // resource for renderscript resource (e.g. R.raw.carousel)
102    }
103
104    public abstract Info getRenderScriptInfo();
105
106    public CarouselView(Context context) {
107        this(context, new CarouselController());
108    }
109
110    public CarouselView(Context context, CarouselController controller) {
111        this(context, null, controller);
112    }
113
114    /**
115     * Constructor used when this widget is created from a layout file.
116     */
117    public CarouselView(Context context, AttributeSet attrs) {
118        this(context, attrs, new CarouselController());
119    }
120
121    public CarouselView(Context context, AttributeSet attrs, CarouselController controller) {
122        super(context, attrs);
123        mContext = context;
124        mController = controller;
125        boolean useDepthBuffer = true;
126        ensureRenderScript();
127        // TODO: add parameters to layout
128
129        setOnLongClickListener(new View.OnLongClickListener() {
130            public boolean onLongClick(View v) {
131                if (interpretLongPressEvents()) {
132                    mController.onLongPress();
133                    return true;
134                } else {
135                    return false;
136                }
137            }
138        });
139    }
140
141    private void ensureRenderScript() {
142        if (mRS == null) {
143            RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
144            if (USE_DEPTH_BUFFER) {
145                sc.setDepth(16, 24);
146            }
147            mRS = createRenderScriptGL(sc);
148        }
149        if (mRenderScript == null) {
150            mRenderScript = new CarouselRS(mRS, mContext.getResources(),
151                    getRenderScriptInfo().resId);
152            mRenderScript.resumeRendering();
153        }
154        mController.setRS(mRS, mRenderScript);
155    }
156
157    @Override
158    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
159        super.surfaceChanged(holder, format, w, h);
160        // setZOrderOnTop(true);
161        mController.onSurfaceChanged();
162    }
163
164    public CarouselController getController() {
165        return mController;
166    }
167
168    public void setController(CarouselController controller) {
169        mController = controller;
170        mController.setRS(mRS, mRenderScript);
171    }
172
173    /**
174     * Do I want to interpret the long-press gesture? If so, long-presses will cancel the
175     * current selection and call the appropriate callbacks. Otherwise, a long press will
176     * not be handled any way other than as a continued drag.
177     *
178     * @return True if we interpret long-presses
179     */
180    public boolean interpretLongPressEvents() {
181        return false;
182    }
183
184    /**
185     * Loads geometry from a resource id.
186     *
187     * @param resId
188     * @return the loaded mesh or null if it cannot be loaded
189     */
190    public Mesh loadGeometry(int resId) {
191        return mController.loadGeometry(resId);
192    }
193
194    /**
195     * Set the geometry for a given item.
196     * @param n
197     * @param mesh
198     */
199    public void setGeometryForItem(int n, Mesh mesh) {
200        mController.setGeometryForItem(n, mesh);
201    }
202
203    /**
204     * Set the number of slots around the Carousel. Basically equivalent to the poles horses
205     * might attach to on a real Carousel.
206     *
207     * @param n the number of slots
208     */
209    public void setSlotCount(int n) {
210        mController.setSlotCount(n);
211    }
212
213    /**
214     * Sets the number of visible slots around the Carousel.  This is primarily used as a cheap
215     * form of clipping. The Carousel will never show more than this many cards.
216     * @param n the number of visible slots
217     */
218    public void setVisibleSlots(int n) {
219        mController.setVisibleSlots(n);
220    }
221
222    /**
223     * Set the number of cards to pre-load that are outside of the visible region, as determined by
224     * setVisibleSlots(). This number gets added to the number of visible slots and used to
225     * determine when resources for cards should be loaded. This number should be small (n <= 4)
226     * for systems with limited texture memory or views that show more than half dozen cards in the
227     * view.
228     *
229     * @param n the number of cards; should be even, so the count is the same on each side
230     */
231    public void setPrefetchCardCount(int n) {
232        mController.setPrefetchCardCount(n);
233    }
234
235    /**
236     * Sets the number of rows of cards to show in each slot.
237     */
238    public void setRowCount(int n) {
239        mController.setRowCount(n);
240    }
241
242    /**
243     * Sets the spacing between each row of cards when rowCount > 1.
244     */
245    public void setRowSpacing(float s) {
246        mController.setRowSpacing(s);
247    }
248
249    /**
250     * Sets the position of the first card when rowCount > 1.
251     */
252    public void setFirstCardTop(boolean f) {
253        mController.setFirstCardTop(f);
254    }
255
256    /**
257     * Set the number of detail textures that can be visible at one time.
258     *
259     * @param n the number of slots
260     */
261    public void setVisibleDetails(int n) {
262        mController.setVisibleDetails(n);
263    }
264
265    /**
266     * Sets how detail textures are aligned with respect to the card.
267     *
268     * @param alignment a bitmask of DetailAlignment flags.
269     */
270    public void setDetailTextureAlignment(int alignment) {
271        mController.setDetailTextureAlignment(alignment);
272    }
273
274    /**
275     * Set whether depth is enabled while blending. Generally, this is discouraged because
276     * it causes bad artifacts. Careful attention to geometry and alpha transparency of
277     * textures can mitigate much of this. For example, geometry for an item must be drawn
278     * back-to-front if any edges overlap.
279     *
280     * @param enabled True to enable depth while blending, and false to disable it.
281     */
282    public void setForceBlendCardsWithZ(boolean enabled) {
283        mController.setForceBlendCardsWithZ(enabled);
284    }
285
286    /**
287     * Set whether to draw a ruler from the card to the detail texture
288     *
289     * @param drawRuler True to draw a ruler, false to draw nothing where the ruler would go.
290     */
291    public void setDrawRuler(boolean drawRuler) {
292        mController.setDrawRuler(drawRuler);
293    }
294
295    /**
296     * This dictates how many cards are in the deck.  If the number of cards is greater than the
297     * number of slots, then the Carousel goes around n / slot_count times.
298     *
299     * Can be called again to increase or decrease the number of cards.
300     *
301     * @param n the number of cards to create.
302     */
303    public void createCards(int n) {
304        mController.createCards(n);
305    }
306
307    public int getCardCount() {
308        return mController.getCardCount();
309    }
310
311    /**
312     * This sets the texture on card n.  It should only be called in response to
313     * {@link CarouselCallback#onRequestTexture(int)}.  Since there's no guarantee
314     * that a given texture is still on the screen, replacing this texture should be done
315     * by first setting it to null and then waiting for the next
316     * {@link CarouselCallback#onRequestTexture(int)} to swap it with the new one.
317     *
318     * @param n the card given by {@link CarouselCallback#onRequestTexture(int)}
319     * @param bitmap the bitmap image to show
320     */
321    public void setTextureForItem(int n, Bitmap bitmap) {
322        mController.setTextureForItem(n, bitmap);
323    }
324
325    /**
326     * This sets the detail texture that floats above card n. It should only be called in response
327     * to {@link CarouselCallback#onRequestDetailTexture(int)}.  Since there's no guarantee
328     * that a given texture is still on the screen, replacing this texture should be done
329     * by first setting it to null and then waiting for the next
330     * {@link CarouselCallback#onRequestDetailTexture(int)} to swap it with the new one.
331     *
332     * @param n the card to set detail texture for
333     * @param offx an optional offset to apply to the texture (in pixels) from top of detail line
334     * @param offy an optional offset to apply to the texture (in pixels) from top of detail line
335     * @param loffx an optional offset to apply to the line (in pixels) from left edge of card
336     * @param loffy an optional offset to apply to the line (in pixels) from top of screen
337     * @param bitmap the bitmap to show as the detail
338     */
339    public void setDetailTextureForItem(int n, float offx, float offy, float loffx, float loffy,
340            Bitmap bitmap) {
341        mController.setDetailTextureForItem(n, offx, offy, loffx, loffy, bitmap);
342    }
343
344    /**
345     * Sets the bitmap to show on a card when the card draws the very first time.
346     * Generally, this bitmap will only be seen during the first few frames of startup
347     * or when the number of cards are changed.  It can be ignored in most cases,
348     * as the cards will generally only be in the loading or loaded state.
349     *
350     * @param bitmap
351     */
352    public void setDefaultBitmap(Bitmap bitmap) {
353        mController.setDefaultBitmap(bitmap);
354    }
355
356    /**
357     * Sets the bitmap to show on the card while the texture is loading. It is set to this
358     * value just before {@link CarouselCallback#onRequestTexture(int)} is called and changed
359     * when {@link CarouselView#setTextureForItem(int, Bitmap)} is called. It is shared by all
360     * cards.
361     *
362     * @param bitmap
363     */
364    public void setLoadingBitmap(Bitmap bitmap) {
365        mController.setLoadingBitmap(bitmap);
366    }
367
368    /**
369     * Sets background to specified color.  If a background texture is specified with
370     * {@link CarouselView#setBackgroundBitmap(Bitmap)}, then this call has no effect.
371     *
372     * @param red the amount of red
373     * @param green the amount of green
374     * @param blue the amount of blue
375     * @param alpha the amount of alpha
376     */
377    public void setBackgroundColor(float red, float green, float blue, float alpha) {
378        mController.setBackgroundColor(red, green, blue, alpha);
379    }
380
381    /**
382     * Can be used to optionally set the background to a bitmap. When set to something other than
383     * null, this overrides {@link CarouselView#setBackgroundColor(Float4)}.
384     *
385     * @param bitmap
386     */
387    public void setBackgroundBitmap(Bitmap bitmap) {
388        mController.setBackgroundBitmap(bitmap);
389    }
390
391    /**
392     * Can be used to optionally set a "loading" detail bitmap. Typically, this is just a black
393     * texture with alpha = 0 to allow details to slowly fade in.
394     *
395     * @param bitmap
396     */
397    public void setDetailLoadingBitmap(Bitmap bitmap) {
398        mController.setDetailLoadingBitmap(bitmap);
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        mController.setDetailLineBitmap(bitmap);
410    }
411
412    /**
413     * This geometry will be shown when no geometry has been loaded for a given slot. If not set,
414     * a quad will be drawn in its place. It is shared for all cards. If something other than
415     * simple planar geometry is used, consider enabling depth test with
416     * {@link CarouselView#setForceBlendCardsWithZ(boolean)}
417     *
418     * @param resId
419     */
420    public void setDefaultGeometry(int resId) {
421        mController.setDefaultGeometry(resId);
422    }
423
424    /**
425     * Sets the matrix used to transform card geometries.  By default, this
426     * is the identity matrix, but you can specify a different matrix if you
427     * want to scale, translate and / or rotate the card before drawing.
428     *
429     * @param matrix array of 9 or 16 floats representing a 3x3 or 4x4 matrix,
430     * or null as a shortcut for an identity matrix.
431     */
432    public void setDefaultCardMatrix(float[] matrix) {
433        mController.setDefaultCardMatrix(matrix);
434    }
435
436    /**
437     * This is an intermediate version of the object to show while geometry is loading. If not set,
438     * a quad will be drawn in its place.  It is shared for all cards. If something other than
439     * simple planar geometry is used, consider enabling depth test with
440     * {@link CarouselView#setForceBlendCardsWithZ(boolean)}
441     *
442     * @param resId
443     */
444    public void setLoadingGeometry(int resId) {
445        mController.setLoadingGeometry(resId);
446    }
447
448    /**
449     * Sets the callback for receiving events from RenderScript.
450     *
451     * @param callback
452     */
453    public void setCallback(CarouselCallback callback)
454    {
455        mController.setCallback(callback);
456    }
457
458    /**
459     * Sets the startAngle for the Carousel. The start angle is the first position of the first
460     * slot draw.  Cards will be drawn from this angle in a counter-clockwise manner around the
461     * Carousel.
462     *
463     * @param angle the angle, in radians.
464     */
465    public void setStartAngle(float angle)
466    {
467        mController.setStartAngle(angle);
468    }
469
470    public void setRadius(float radius) {
471        mController.setRadius(radius);
472    }
473
474    public void setCardRotation(float cardRotation) {
475        mController.setCardRotation(cardRotation);
476    }
477
478    public void setCardsFaceTangent(boolean faceTangent) {
479        mController.setCardsFaceTangent(faceTangent);
480    }
481
482    public void setSwaySensitivity(float swaySensitivity) {
483        mController.setSwaySensitivity(swaySensitivity);
484    }
485
486    public void setFrictionCoefficient(float frictionCoefficient) {
487        mController.setFrictionCoefficient(frictionCoefficient);
488    }
489
490    public void setDragFactor(float dragFactor) {
491        mController.setDragFactor(dragFactor);
492    }
493
494    public void setDragModel(int model) {
495        mController.setDragModel(model);
496    }
497
498    public void setLookAt(float[] eye, float[] at, float[] up) {
499        mController.setLookAt(eye, at, up);
500    }
501
502    /**
503     * This sets the number of cards in the distance that will be shown "rezzing in".
504     * These alpha values will be faded in from the background to the foreground over
505     * 'n' cards.  A floating point value is used to allow subtly changing the rezzing in
506     * position.
507     *
508     * @param n the number of cards to rez in.
509     */
510    public void setRezInCardCount(float n) {
511        mController.setRezInCardCount(n);
512    }
513
514    /**
515     * This sets the duration (in ms) that a card takes to fade in when loaded via a call
516     * to {@link CarouselView#setTextureForItem(int, Bitmap)}. The timer starts the
517     * moment {@link CarouselView#setTextureForItem(int, Bitmap)} is called and continues
518     * until all of the cards have faded in.  Note: using large values will extend the
519     * animation until all cards have faded in.
520     *
521     * @param t
522     */
523    public void setFadeInDuration(long t) {
524        mController.setFadeInDuration(t);
525    }
526
527    @Override
528    protected void onDetachedFromWindow() {
529        super.onDetachedFromWindow();
530        mRenderScript = null;
531        if (mRS != null) {
532            mRS = null;
533            destroyRenderScriptGL();
534        }
535        mController.setRS(mRS, mRenderScript);
536    }
537
538    @Override
539    protected void onAttachedToWindow() {
540        super.onAttachedToWindow();
541        ensureRenderScript();
542    }
543
544    @Override
545    public boolean onTouchEvent(MotionEvent event) {
546        super.onTouchEvent(event);
547        final int action = event.getAction();
548
549        if (mRenderScript == null) {
550            return true;
551        }
552
553        switch (action) {
554            case MotionEvent.ACTION_DOWN:
555                mTracking = true;
556                mController.onTouchStarted(event.getX(), event.getY(), event.getEventTime());
557                break;
558
559            case MotionEvent.ACTION_MOVE:
560                if (mTracking) {
561                    for (int i = 0; i < event.getHistorySize(); i++) {
562                        mController.onTouchMoved(event.getHistoricalX(i), event.getHistoricalY(i),
563                                event.getHistoricalEventTime(i));
564                    }
565                    mController.onTouchMoved(event.getX(), event.getY(), event.getEventTime());
566                }
567                break;
568
569            case MotionEvent.ACTION_UP:
570                mController.onTouchStopped(event.getX(), event.getY(), event.getEventTime());
571                mTracking = false;
572                break;
573        }
574
575        return true;
576    }
577}
578