• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
onClick(GLView v)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 
startAnimation(CanvasAnimation animation)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).
setVisibility(int visibility)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
getVisibility()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).
attachToRoot(GLRoot root)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).
detachFromRoot()116     public void detachFromRoot() {
117         Utils.assertTrue(mParent == null && mRoot != null);
118         onDetachFromRoot();
119     }
120 
121     // Returns the number of children of the GLView.
getComponentCount()122     public int getComponentCount() {
123         return mComponents == null ? 0 : mComponents.size();
124     }
125 
126     // Returns the children for the given index.
getComponent(int 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.
addComponent(GLView component)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.
removeComponent(GLView component)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.
removeAllComponents()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 
removeOneComponent(GLView component)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 
bounds()182     public Rect bounds() {
183         return mBounds;
184     }
185 
getWidth()186     public int getWidth() {
187         return mBounds.right - mBounds.left;
188     }
189 
getHeight()190     public int getHeight() {
191         return mBounds.bottom - mBounds.top;
192     }
193 
getGLRoot()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.
invalidate()200     public void invalidate() {
201         GLRoot root = getGLRoot();
202         if (root != null) root.requestRender();
203     }
204 
205     // Request re-layout of the view hierarchy.
requestLayout()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 
render(GLCanvas canvas)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 
renderBackground(GLCanvas view)226     protected void renderBackground(GLCanvas view) {
227     }
228 
renderChild(GLCanvas canvas, GLView component)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 
onTouch(MotionEvent event)253     protected boolean onTouch(MotionEvent event) {
254         return false;
255     }
256 
dispatchTouchEvent(MotionEvent event, int x, int y, GLView component, boolean checkBounds)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 
dispatchTouchEvent(MotionEvent event)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 
getPaddings()306     public Rect getPaddings() {
307         return mPaddings;
308     }
309 
layout(int left, int top, int right, int bottom)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 
setBounds(int left, int top, int right, int bottom)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 
measure(int widthSpec, int heightSpec)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 
onMeasure(int widthSpec, int heightSpec)344     protected void onMeasure(int widthSpec, int heightSpec) {
345     }
346 
setMeasuredSize(int width, int height)347     protected void setMeasuredSize(int width, int height) {
348         mViewFlags |= FLAG_SET_MEASURED_SIZE;
349         mMeasuredWidth = width;
350         mMeasuredHeight = height;
351     }
352 
getMeasuredWidth()353     public int getMeasuredWidth() {
354         return mMeasuredWidth;
355     }
356 
getMeasuredHeight()357     public int getMeasuredHeight() {
358         return mMeasuredHeight;
359     }
360 
onLayout( boolean changeSize, int left, int top, int right, int bottom)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      */
getBoundsOf(GLView descendant, Rect out)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 
onVisibilityChanged(int visibility)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 
onAttachToRoot(GLRoot root)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 
onDetachFromRoot()400     protected void onDetachFromRoot() {
401         for (int i = 0, n = getComponentCount(); i < n; ++i) {
402             getComponent(i).onDetachFromRoot();
403         }
404         mRoot = null;
405     }
406 
lockRendering()407     public void lockRendering() {
408         if (mRoot != null) {
409             mRoot.lockRenderThread();
410         }
411     }
412 
unlockRendering()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.
dumpTree(String prefix)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