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.gallery3d.ui;
     18 
     19 import android.graphics.Rect;
     20 import android.os.SystemClock;
     21 import android.view.MotionEvent;
     22 
     23 import com.android.gallery3d.anim.CanvasAnimation;
     24 import com.android.gallery3d.common.Utils;
     25 
     26 import java.util.ArrayList;
     27 
     28 // GLView is a UI component. It can render to a GLCanvas and accept touch
     29 // events. A GLView may have zero or more child GLView and they form a tree
     30 // structure. The rendering and event handling will pass through the tree
     31 // structure.
     32 //
     33 // A GLView tree should be attached to a GLRoot before event dispatching and
     34 // rendering happens. GLView asks GLRoot to re-render or re-layout the
     35 // GLView hierarchy using requestRender() and requestLayoutContentPane().
     36 //
     37 // The render() method is called in a separate thread. Before calling
     38 // dispatchTouchEvent() and layout(), GLRoot acquires a lock to avoid the
     39 // rendering thread running at the same time. If there are other entry points
     40 // from main thread (like a Handler) in your GLView, you need to call
     41 // lockRendering() if the rendering thread should not run at the same time.
     42 //
     43 public class GLView {
     44     private static final String TAG = "GLView";
     45 
     46     public static final int VISIBLE = 0;
     47     public static final int INVISIBLE = 1;
     48 
     49     private static final int FLAG_INVISIBLE = 1;
     50     private static final int FLAG_SET_MEASURED_SIZE = 2;
     51     private static final int FLAG_LAYOUT_REQUESTED = 4;
     52 
     53     public interface OnClickListener {
     54         void onClick(GLView v);
     55     }
     56 
     57     protected final Rect mBounds = new Rect();
     58     protected final Rect mPaddings = new Rect();
     59 
     60     private GLRoot mRoot;
     61     protected GLView mParent;
     62     private ArrayList<GLView> mComponents;
     63     private GLView mMotionTarget;
     64 
     65     private CanvasAnimation mAnimation;
     66 
     67     private int mViewFlags = 0;
     68 
     69     protected int mMeasuredWidth = 0;
     70     protected int mMeasuredHeight = 0;
     71 
     72     private int mLastWidthSpec = -1;
     73     private int mLastHeightSpec = -1;
     74 
     75     protected int mScrollY = 0;
     76     protected int mScrollX = 0;
     77     protected int mScrollHeight = 0;
     78     protected int mScrollWidth = 0;
     79 
     80     public void startAnimation(CanvasAnimation animation) {
     81         GLRoot root = getGLRoot();
     82         if (root == null) throw new IllegalStateException();
     83         mAnimation = animation;
     84         if (mAnimation != null) {
     85             mAnimation.start();
     86             root.registerLaunchedAnimation(mAnimation);
     87         }
     88         invalidate();
     89     }
     90 
     91     // Sets the visiblity of this GLView (either GLView.VISIBLE or
     92     // GLView.INVISIBLE).
     93     public void setVisibility(int visibility) {
     94         if (visibility == getVisibility()) return;
     95         if (visibility == VISIBLE) {
     96             mViewFlags &= ~FLAG_INVISIBLE;
     97         } else {
     98             mViewFlags |= FLAG_INVISIBLE;
     99         }
    100         onVisibilityChanged(visibility);
    101         invalidate();
    102     }
    103 
    104     // Returns GLView.VISIBLE or GLView.INVISIBLE
    105     public int getVisibility() {
    106         return (mViewFlags & FLAG_INVISIBLE) == 0 ? VISIBLE : INVISIBLE;
    107     }
    108 
    109     // This should only be called on the content pane (the topmost GLView).
    110     public void attachToRoot(GLRoot root) {
    111         Utils.assertTrue(mParent == null && mRoot == null);
    112         onAttachToRoot(root);
    113     }
    114 
    115     // This should only be called on the content pane (the topmost GLView).
    116     public void detachFromRoot() {
    117         Utils.assertTrue(mParent == null && mRoot != null);
    118         onDetachFromRoot();
    119     }
    120 
    121     // Returns the number of children of the GLView.
    122     public int getComponentCount() {
    123         return mComponents == null ? 0 : mComponents.size();
    124     }
    125 
    126     // Returns the children for the given index.
    127     public GLView getComponent(int index) {
    128         if (mComponents == null) {
    129             throw new ArrayIndexOutOfBoundsException(index);
    130         }
    131         return mComponents.get(index);
    132     }
    133 
    134     // Adds a child to this GLView.
    135     public void addComponent(GLView component) {
    136         // Make sure the component doesn't have a parent currently.
    137         if (component.mParent != null) throw new IllegalStateException();
    138 
    139         // Build parent-child links
    140         if (mComponents == null) {
    141             mComponents = new ArrayList<GLView>();
    142         }
    143         mComponents.add(component);
    144         component.mParent = this;
    145 
    146         // If this is added after we have a root, tell the component.
    147         if (mRoot != null) {
    148             component.onAttachToRoot(mRoot);
    149         }
    150     }
    151 
    152     // Removes a child from this GLView.
    153     public boolean removeComponent(GLView component) {
    154         if (mComponents == null) return false;
    155         if (mComponents.remove(component)) {
    156             removeOneComponent(component);
    157             return true;
    158         }
    159         return false;
    160     }
    161 
    162     // Removes all children of this GLView.
    163     public void removeAllComponents() {
    164         for (int i = 0, n = mComponents.size(); i < n; ++i) {
    165             removeOneComponent(mComponents.get(i));
    166         }
    167         mComponents.clear();
    168     }
    169 
    170     private void removeOneComponent(GLView component) {
    171         if (mMotionTarget == component) {
    172             long now = SystemClock.uptimeMillis();
    173             MotionEvent cancelEvent = MotionEvent.obtain(
    174                     now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
    175             dispatchTouchEvent(cancelEvent);
    176             cancelEvent.recycle();
    177         }
    178         component.onDetachFromRoot();
    179         component.mParent = null;
    180     }
    181 
    182     public Rect bounds() {
    183         return mBounds;
    184     }
    185 
    186     public int getWidth() {
    187         return mBounds.right - mBounds.left;
    188     }
    189 
    190     public int getHeight() {
    191         return mBounds.bottom - mBounds.top;
    192     }
    193 
    194     public GLRoot getGLRoot() {
    195         return mRoot;
    196     }
    197 
    198     // Request re-rendering of the view hierarchy.
    199     // This is used for animation or when the contents changed.
    200     public void invalidate() {
    201         GLRoot root = getGLRoot();
    202         if (root != null) root.requestRender();
    203     }
    204 
    205     // Request re-layout of the view hierarchy.
    206     public void requestLayout() {
    207         mViewFlags |= FLAG_LAYOUT_REQUESTED;
    208         mLastHeightSpec = -1;
    209         mLastWidthSpec = -1;
    210         if (mParent != null) {
    211             mParent.requestLayout();
    212         } else {
    213             // Is this a content pane ?
    214             GLRoot root = getGLRoot();
    215             if (root != null) root.requestLayoutContentPane();
    216         }
    217     }
    218 
    219     protected void render(GLCanvas canvas) {
    220         renderBackground(canvas);
    221         for (int i = 0, n = getComponentCount(); i < n; ++i) {
    222             renderChild(canvas, getComponent(i));
    223         }
    224     }
    225 
    226     protected void renderBackground(GLCanvas view) {
    227     }
    228 
    229     protected void renderChild(GLCanvas canvas, GLView component) {
    230         if (component.getVisibility() != GLView.VISIBLE
    231                 && component.mAnimation == null) return;
    232 
    233         int xoffset = component.mBounds.left - mScrollX;
    234         int yoffset = component.mBounds.top - mScrollY;
    235 
    236         canvas.translate(xoffset, yoffset);
    237 
    238         CanvasAnimation anim = component.mAnimation;
    239         if (anim != null) {
    240             canvas.save(anim.getCanvasSaveFlags());
    241             if (anim.calculate(AnimationTime.get())) {
    242                 invalidate();
    243             } else {
    244                 component.mAnimation = null;
    245             }
    246             anim.apply(canvas);
    247         }
    248         component.render(canvas);
    249         if (anim != null) canvas.restore();
    250         canvas.translate(-xoffset, -yoffset);
    251     }
    252 
    253     protected boolean onTouch(MotionEvent event) {
    254         return false;
    255     }
    256 
    257     protected boolean dispatchTouchEvent(MotionEvent event,
    258             int x, int y, GLView component, boolean checkBounds) {
    259         Rect rect = component.mBounds;
    260         int left = rect.left;
    261         int top = rect.top;
    262         if (!checkBounds || rect.contains(x, y)) {
    263             event.offsetLocation(-left, -top);
    264             if (component.dispatchTouchEvent(event)) {
    265                 event.offsetLocation(left, top);
    266                 return true;
    267             }
    268             event.offsetLocation(left, top);
    269         }
    270         return false;
    271     }
    272 
    273     protected boolean dispatchTouchEvent(MotionEvent event) {
    274         int x = (int) event.getX();
    275         int y = (int) event.getY();
    276         int action = event.getAction();
    277         if (mMotionTarget != null) {
    278             if (action == MotionEvent.ACTION_DOWN) {
    279                 MotionEvent cancel = MotionEvent.obtain(event);
    280                 cancel.setAction(MotionEvent.ACTION_CANCEL);
    281                 dispatchTouchEvent(cancel, x, y, mMotionTarget, false);
    282                 mMotionTarget = null;
    283             } else {
    284                 dispatchTouchEvent(event, x, y, mMotionTarget, false);
    285                 if (action == MotionEvent.ACTION_CANCEL
    286                         || action == MotionEvent.ACTION_UP) {
    287                     mMotionTarget = null;
    288                 }
    289                 return true;
    290             }
    291         }
    292         if (action == MotionEvent.ACTION_DOWN) {
    293             // in the reverse rendering order
    294             for (int i = getComponentCount() - 1; i >= 0; --i) {
    295                 GLView component = getComponent(i);
    296                 if (component.getVisibility() != GLView.VISIBLE) continue;
    297                 if (dispatchTouchEvent(event, x, y, component, true)) {
    298                     mMotionTarget = component;
    299                     return true;
    300                 }
    301             }
    302         }
    303         return onTouch(event);
    304     }
    305 
    306     public Rect getPaddings() {
    307         return mPaddings;
    308     }
    309 
    310     public void layout(int left, int top, int right, int bottom) {
    311         boolean sizeChanged = setBounds(left, top, right, bottom);
    312         mViewFlags &= ~FLAG_LAYOUT_REQUESTED;
    313         // We call onLayout no matter sizeChanged is true or not because the
    314         // orientation may change without changing the size of the View (for
    315         // example, rotate the device by 180 degrees), and we want to handle
    316         // orientation change in onLayout.
    317         onLayout(sizeChanged, left, top, right, bottom);
    318     }
    319 
    320     private boolean setBounds(int left, int top, int right, int bottom) {
    321         boolean sizeChanged = (right - left) != (mBounds.right - mBounds.left)
    322                 || (bottom - top) != (mBounds.bottom - mBounds.top);
    323         mBounds.set(left, top, right, bottom);
    324         return sizeChanged;
    325     }
    326 
    327     public void measure(int widthSpec, int heightSpec) {
    328         if (widthSpec == mLastWidthSpec && heightSpec == mLastHeightSpec
    329                 && (mViewFlags & FLAG_LAYOUT_REQUESTED) == 0) {
    330             return;
    331         }
    332 
    333         mLastWidthSpec = widthSpec;
    334         mLastHeightSpec = heightSpec;
    335 
    336         mViewFlags &= ~FLAG_SET_MEASURED_SIZE;
    337         onMeasure(widthSpec, heightSpec);
    338         if ((mViewFlags & FLAG_SET_MEASURED_SIZE) == 0) {
    339             throw new IllegalStateException(getClass().getName()
    340                     + " should call setMeasuredSize() in onMeasure()");
    341         }
    342     }
    343 
    344     protected void onMeasure(int widthSpec, int heightSpec) {
    345     }
    346 
    347     protected void setMeasuredSize(int width, int height) {
    348         mViewFlags |= FLAG_SET_MEASURED_SIZE;
    349         mMeasuredWidth = width;
    350         mMeasuredHeight = height;
    351     }
    352 
    353     public int getMeasuredWidth() {
    354         return mMeasuredWidth;
    355     }
    356 
    357     public int getMeasuredHeight() {
    358         return mMeasuredHeight;
    359     }
    360 
    361     protected void onLayout(
    362             boolean changeSize, int left, int top, int right, int bottom) {
    363     }
    364 
    365     /**
    366      * Gets the bounds of the given descendant that relative to this view.
    367      */
    368     public boolean getBoundsOf(GLView descendant, Rect out) {
    369         int xoffset = 0;
    370         int yoffset = 0;
    371         GLView view = descendant;
    372         while (view != this) {
    373             if (view == null) return false;
    374             Rect bounds = view.mBounds;
    375             xoffset += bounds.left;
    376             yoffset += bounds.top;
    377             view = view.mParent;
    378         }
    379         out.set(xoffset, yoffset, xoffset + descendant.getWidth(),
    380                 yoffset + descendant.getHeight());
    381         return true;
    382     }
    383 
    384     protected void onVisibilityChanged(int visibility) {
    385         for (int i = 0, n = getComponentCount(); i < n; ++i) {
    386             GLView child = getComponent(i);
    387             if (child.getVisibility() == GLView.VISIBLE) {
    388                 child.onVisibilityChanged(visibility);
    389             }
    390         }
    391     }
    392 
    393     protected void onAttachToRoot(GLRoot root) {
    394         mRoot = root;
    395         for (int i = 0, n = getComponentCount(); i < n; ++i) {
    396             getComponent(i).onAttachToRoot(root);
    397         }
    398     }
    399 
    400     protected void onDetachFromRoot() {
    401         for (int i = 0, n = getComponentCount(); i < n; ++i) {
    402             getComponent(i).onDetachFromRoot();
    403         }
    404         mRoot = null;
    405     }
    406 
    407     public void lockRendering() {
    408         if (mRoot != null) {
    409             mRoot.lockRenderThread();
    410         }
    411     }
    412 
    413     public void unlockRendering() {
    414         if (mRoot != null) {
    415             mRoot.unlockRenderThread();
    416         }
    417     }
    418 
    419     // This is for debugging only.
    420     // Dump the view hierarchy into log.
    421     void dumpTree(String prefix) {
    422         Log.d(TAG, prefix + getClass().getSimpleName());
    423         for (int i = 0, n = getComponentCount(); i < n; ++i) {
    424             getComponent(i).dumpTree(prefix + "....");
    425         }
    426     }
    427 }
    428