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