Home | History | Annotate | Download | only in ui
      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.camera.ui;
     18 
     19 import static android.view.View.MeasureSpec.makeMeasureSpec;
     20 
     21 import android.content.Context;
     22 import android.graphics.Rect;
     23 import android.os.Handler;
     24 import android.os.Message;
     25 import android.view.GestureDetector;
     26 import android.view.MotionEvent;
     27 import android.view.View.MeasureSpec;
     28 import android.view.animation.AlphaAnimation;
     29 import android.view.animation.Animation;
     30 import android.view.animation.Transformation;
     31 import android.widget.Scroller;
     32 
     33 import com.android.camera.Util;
     34 
     35 import javax.microedition.khronos.opengles.GL11;
     36 
     37 class GLListView extends GLView {
     38     @SuppressWarnings("unused")
     39     private static final String TAG = "GLListView";
     40     private static final int INDEX_NONE = -1;
     41     private static final int SCROLL_BAR_TIMEOUT = 2500;
     42 
     43     private static final int HIDE_SCROLL_BAR = 1;
     44 
     45     private Model mModel;
     46     private Handler mHandler;
     47 
     48     private int mHighlightIndex = INDEX_NONE;
     49     private GLView mHighlightView;
     50 
     51     private Texture mHighLight;
     52     private NinePatchTexture mScrollbar;
     53 
     54     private int mVisibleStart = 0; // inclusive
     55     private int mVisibleEnd = 0; // exclusive
     56 
     57     private boolean mHasMeasured = false;
     58 
     59     private boolean mScrollBarVisible = false;
     60     private Animation mScrollBarAnimation;
     61     private OnItemSelectedListener mOnItemSelectedListener;
     62 
     63     private GestureDetector mGestureDetector;
     64     private final Scroller mScroller;
     65     private boolean mScrollable;
     66     private boolean mIsPressed = false;
     67 
     68     static public interface Model {
     69         public int size();
     70         public GLView getView(int index);
     71         public boolean isSelectable(int index);
     72     }
     73 
     74     static public interface OnItemSelectedListener {
     75         public void onItemSelected(GLView view, int position);
     76     }
     77 
     78     public GLListView(Context context) {
     79         mScroller = new Scroller(context);
     80         mHandler = new Handler() {
     81             @Override
     82             public void handleMessage(Message msg) {
     83                 GLRootView root = getGLRootView();
     84                 if (root != null) {
     85                     synchronized (root) {
     86                         handleMessageLocked(msg);
     87                     }
     88                 } else {
     89                     handleMessageLocked(msg);
     90                 }
     91             }
     92 
     93             private void handleMessageLocked(Message msg) {
     94                 switch(msg.what) {
     95                     case HIDE_SCROLL_BAR:
     96                         setScrollBarVisible(false);
     97                         break;
     98                 }
     99             }
    100         };
    101         mGestureDetector = new GestureDetector(
    102                 context, new MyGestureListener(), mHandler);
    103     }
    104 
    105     @Override
    106     protected void onVisibilityChanged(int visibility) {
    107         super.onVisibilityChanged(visibility);
    108         if (visibility == GLView.VISIBLE && mScrollHeight > getHeight()) {
    109             setScrollBarVisible(true);
    110             mHandler.sendEmptyMessageDelayed(
    111                     HIDE_SCROLL_BAR, SCROLL_BAR_TIMEOUT);
    112         }
    113     }
    114 
    115     private void setScrollBarVisible(boolean visible) {
    116         if (mScrollBarVisible == visible || mScrollbar == null) return;
    117         mScrollBarVisible = visible;
    118         if (!visible) {
    119             mScrollBarAnimation = new AlphaAnimation(1, 0);
    120             mScrollBarAnimation.setDuration(300);
    121             mScrollBarAnimation.start();
    122         } else {
    123             mScrollBarAnimation = null;
    124         }
    125         invalidate();
    126     }
    127 
    128     public void setHighLight(Texture highLight) {
    129         mHighLight = highLight;
    130     }
    131 
    132     public void setDataModel(Model model) {
    133         mModel = model;
    134         mScrollY = 0;
    135         requestLayout();
    136     }
    137 
    138     public void setOnItemSelectedListener(OnItemSelectedListener l) {
    139         mOnItemSelectedListener = l;
    140     }
    141 
    142     private boolean drawWithAnimation(GLRootView root,
    143             Texture texture, int x, int y, int w, int h, Animation anim) {
    144         long now = root.currentAnimationTimeMillis();
    145         Transformation temp = root.obtainTransformation();
    146         boolean more = anim.getTransformation(now, temp);
    147         Transformation transformation = root.pushTransform();
    148         transformation.compose(temp);
    149         texture.draw(root, x, y, w, h);
    150         invalidate();
    151         root.popTransform();
    152         return more;
    153     }
    154 
    155     @Override
    156     protected void render(GLRootView root, GL11 gl) {
    157         root.clipRect(0, 0, getWidth(), getHeight());
    158         if (mHighlightIndex != INDEX_NONE) {
    159             GLView view = mModel.getView(mHighlightIndex);
    160             Rect bounds = view.bounds();
    161             if (mHighLight != null) {
    162                 int width = bounds.width();
    163                 int height = bounds.height();
    164                 mHighLight.draw(root,
    165                         bounds.left - mScrollX, bounds.top - mScrollY,
    166                         width, height);
    167             }
    168         }
    169         super.render(root, gl);
    170         root.clearClip();
    171 
    172         if (mScrollBarAnimation != null || mScrollBarVisible) {
    173             int width = mScrollbar.getWidth();
    174             int height = getHeight() * getHeight() / mScrollHeight;
    175             int yoffset = mScrollY * getHeight() / mScrollHeight;
    176             if (mScrollBarAnimation != null) {
    177                 if (!drawWithAnimation(
    178                         root, mScrollbar, getWidth() - width, yoffset,
    179                         width, height, mScrollBarAnimation)) {
    180                     mScrollBarAnimation = null;
    181                 }
    182             } else {
    183                 mScrollbar.draw(
    184                         root, getWidth() - width, yoffset, width, height);
    185             }
    186         }
    187         if (mScroller.computeScrollOffset()) {
    188             setScrollPosition(mScroller.getCurrY(), false);
    189         }
    190     }
    191 
    192     @Override
    193     protected void onMeasure(int widthSpec, int heightSpec) {
    194         // first get the total height
    195         int height = 0;
    196         int maxWidth = 0;
    197         for (int i = 0, n = mModel.size(); i < n; ++i) {
    198             GLView view = mModel.getView(i);
    199             view.measure(widthSpec, MeasureSpec.UNSPECIFIED);
    200             height += view.getMeasuredHeight();
    201             maxWidth = Math.max(maxWidth, view.getMeasuredWidth());
    202         }
    203         mScrollHeight = height;
    204         mHasMeasured = true;
    205         new MeasureHelper(this)
    206                 .setPreferredContentSize(maxWidth, height)
    207                 .measure(widthSpec, heightSpec);
    208     }
    209 
    210     @Override
    211     public int getComponentCount() {
    212         return mVisibleEnd - mVisibleStart;
    213     }
    214 
    215     @Override
    216     public GLView getComponent(int index) {
    217         if (index < 0 || index >= mVisibleEnd - mVisibleStart) {
    218             throw new ArrayIndexOutOfBoundsException(index);
    219         }
    220         return mModel.getView(mVisibleStart + index);
    221     }
    222 
    223     @Override
    224     public void requestLayout() {
    225         mHasMeasured = false;
    226         super.requestLayout();
    227     }
    228 
    229     @Override
    230     protected void onLayout(
    231             boolean change, int left, int top, int right, int bottom) {
    232 
    233         if (!mHasMeasured || mMeasuredWidth != (right - left)) {
    234             measure(makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
    235                     makeMeasureSpec(bottom - top, MeasureSpec.EXACTLY));
    236         }
    237 
    238         mScrollable = mScrollHeight > (bottom - top);
    239         int width = right - left;
    240         int yoffset = 0;
    241 
    242         for (int i = 0, n = mModel.size(); i < n; ++i) {
    243             GLView item = mModel.getView(i);
    244             item.onAddToParent(this);
    245             int nextOffset = yoffset + item.getMeasuredHeight();
    246             item.layout(0, yoffset, width, nextOffset);
    247             yoffset = nextOffset;
    248         }
    249         setScrollPosition(mScrollY, true);
    250     }
    251 
    252     private void setScrollPosition(int position, boolean force) {
    253         int height = getHeight();
    254 
    255         position = Util.clamp(position, 0, mScrollHeight - height);
    256 
    257         if (!force && position == mScrollY) return;
    258         mScrollY = position;
    259 
    260         int n = mModel.size();
    261 
    262         int start = 0;
    263         int end = 0;
    264         for (start = 0; start < n; ++start) {
    265             if (position < mModel.getView(start).mBounds.bottom) break;
    266         }
    267 
    268         int bottom = position + height;
    269         for (end = start; end < n; ++ end) {
    270             if (bottom <= mModel.getView(end).mBounds.top) break;
    271         }
    272         setVisibleRange(start , end);
    273         invalidate();
    274     }
    275 
    276     private void setVisibleRange(int start, int end) {
    277         if (start == mVisibleStart && end == mVisibleEnd) return;
    278         mVisibleStart = start;
    279         mVisibleEnd = end;
    280     }
    281 
    282     @Override
    283     protected boolean dispatchTouchEvent(MotionEvent event) {
    284         return onTouch(event);
    285     }
    286 
    287     @Override @SuppressWarnings("fallthrough")
    288     protected boolean onTouch(MotionEvent event) {
    289 
    290         mGestureDetector.onTouchEvent(event);
    291 
    292         switch (event.getAction()) {
    293             case MotionEvent.ACTION_DOWN:
    294                 mIsPressed = true;
    295                 mHandler.removeMessages(HIDE_SCROLL_BAR);
    296                 setScrollBarVisible(mScrollHeight > getHeight());
    297 
    298                 // fallthrough: we need to highlight the item which is pressed
    299             case MotionEvent.ACTION_MOVE:
    300                 if (!mScrollable) {
    301                     findAndSetHighlightItem((int) event.getY());
    302                 }
    303                 break;
    304             case MotionEvent.ACTION_UP:
    305                 mIsPressed = false;
    306                 if (mScrollBarVisible) {
    307                     mHandler.removeMessages(HIDE_SCROLL_BAR);
    308                     mHandler.sendEmptyMessageDelayed(
    309                             HIDE_SCROLL_BAR, SCROLL_BAR_TIMEOUT);
    310                 }
    311                 if (!mScrollable && mOnItemSelectedListener != null
    312                         && mHighlightView != null) {
    313                     mOnItemSelectedListener
    314                             .onItemSelected(mHighlightView, mHighlightIndex);
    315                 }
    316             case MotionEvent.ACTION_CANCEL:
    317             case MotionEvent.ACTION_OUTSIDE:
    318                 setHighlightItem(null, INDEX_NONE);
    319         }
    320         return true;
    321     }
    322 
    323     private void findAndSetHighlightItem(int y) {
    324         int position = y + mScrollY;
    325         for (int i = mVisibleStart, n = mVisibleEnd; i < n; ++i) {
    326             GLView child = mModel.getView(i);
    327             if (child.mBounds.bottom > position) {
    328                 if (mModel.isSelectable(i)) {
    329                     setHighlightItem(child, i);
    330                     return;
    331                 }
    332                 break;
    333             }
    334         }
    335         setHighlightItem(null, INDEX_NONE);
    336     }
    337 
    338     private void setHighlightItem(GLView view, int index) {
    339         if (index == mHighlightIndex) return;
    340         mHighlightIndex = index;
    341         mHighlightView = view;
    342         if (mHighLight != null) invalidate();
    343     }
    344 
    345     public void setScroller(NinePatchTexture scrollbar) {
    346         this.mScrollbar = scrollbar;
    347         requestLayout();
    348     }
    349 
    350     private class MyGestureListener
    351             extends GestureDetector.SimpleOnGestureListener {
    352 
    353         @Override
    354         public boolean onFling(MotionEvent e1,
    355                 MotionEvent e2, float velocityX, float velocityY) {
    356             if (!mScrollable) return false;
    357             mScroller.fling(0, mScrollY,
    358                     0, -(int) velocityY, 0, 0, 0, mScrollHeight - getHeight());
    359             invalidate();
    360             return true;
    361         }
    362 
    363         @Override
    364         public boolean onScroll(MotionEvent e1,
    365                 MotionEvent e2, float distanceX, float distanceY) {
    366             if (!mScrollable) return false;
    367             setHighlightItem(null, INDEX_NONE);
    368             setScrollPosition(mScrollY + (int) distanceY, false);
    369             return true;
    370         }
    371 
    372         @Override
    373         public void onShowPress(MotionEvent e) {
    374             if (!mScrollable || !mIsPressed) return;
    375             findAndSetHighlightItem((int) e.getY());
    376         }
    377 
    378         @Override
    379         public boolean onSingleTapUp(MotionEvent e) {
    380             if (!mScrollable) return false;
    381             findAndSetHighlightItem((int) e.getY());
    382             if (mOnItemSelectedListener != null && mHighlightView != null) {
    383                 mOnItemSelectedListener
    384                         .onItemSelected(mHighlightView, mHighlightIndex);
    385             }
    386             setHighlightItem(null, INDEX_NONE);
    387             return true;
    388         }
    389     }
    390 }
    391