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