GLView.java revision 7d19f7f4281f232b9dceee4a5df390c03e2bd16b
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
17package com.android.gallery3d.ui;
18
19import com.android.gallery3d.anim.CanvasAnimation;
20import com.android.gallery3d.common.Utils;
21
22import android.graphics.Rect;
23import android.os.SystemClock;
24import android.view.MotionEvent;
25
26import 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//
43public 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);
233
234        CanvasAnimation anim = component.mAnimation;
235        if (anim != null) {
236            canvas.save(anim.getCanvasSaveFlags());
237            if (anim.calculate(AnimationTime.get())) {
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);
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