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