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