blob: 45471f91082aad86c7ac37250b3dfb39d818e832 [file] [log] [blame]
Owen Lina2fba682011-08-17 22:07:43 +08001/*
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
Owen Lina2fba682011-08-17 22:07:43 +080019import android.graphics.Rect;
20import android.os.SystemClock;
21import android.view.MotionEvent;
22
Owen Lin73a04ff2012-03-14 17:27:24 +080023import com.android.gallery3d.anim.CanvasAnimation;
24import com.android.gallery3d.common.Utils;
25
Owen Lina2fba682011-08-17 22:07:43 +080026import 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();
Owen Lina2fba682011-08-17 22:07:43 +080079 mAnimation = animation;
Owen Linace280a2011-08-30 10:38:59 +080080 if (mAnimation != null) {
81 mAnimation.start();
82 root.registerLaunchedAnimation(mAnimation);
83 }
Owen Lina2fba682011-08-17 22:07:43 +080084 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
Chih-Chung Chang3f43ecb2012-02-16 07:27:03 +0800232 canvas.translate(xoffset, yoffset);
Owen Lina2fba682011-08-17 22:07:43 +0800233
234 CanvasAnimation anim = component.mAnimation;
235 if (anim != null) {
236 canvas.save(anim.getCanvasSaveFlags());
Chih-Chung Changb3d01962012-02-17 10:02:27 +0800237 if (anim.calculate(AnimationTime.get())) {
Owen Lina2fba682011-08-17 22:07:43 +0800238 invalidate();
239 } else {
240 component.mAnimation = null;
241 }
242 anim.apply(canvas);
243 }
244 component.render(canvas);
245 if (anim != null) canvas.restore();
Chih-Chung Chang3f43ecb2012-02-16 07:27:03 +0800246 canvas.translate(-xoffset, -yoffset);
Owen Lina2fba682011-08-17 22:07:43 +0800247 }
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
Owen Lina2fba682011-08-17 22:07:43 +0800306 public void layout(int left, int top, int right, int bottom) {
307 boolean sizeChanged = setBounds(left, top, right, bottom);
308 if (sizeChanged) {
309 mViewFlags &= ~FLAG_LAYOUT_REQUESTED;
310 onLayout(true, left, top, right, bottom);
311 } else if ((mViewFlags & FLAG_LAYOUT_REQUESTED)!= 0) {
312 mViewFlags &= ~FLAG_LAYOUT_REQUESTED;
313 onLayout(false, left, top, right, bottom);
314 }
315 }
316
317 private boolean setBounds(int left, int top, int right, int bottom) {
318 boolean sizeChanged = (right - left) != (mBounds.right - mBounds.left)
319 || (bottom - top) != (mBounds.bottom - mBounds.top);
320 mBounds.set(left, top, right, bottom);
321 return sizeChanged;
322 }
323
324 public void measure(int widthSpec, int heightSpec) {
325 if (widthSpec == mLastWidthSpec && heightSpec == mLastHeightSpec
326 && (mViewFlags & FLAG_LAYOUT_REQUESTED) == 0) {
327 return;
328 }
329
330 mLastWidthSpec = widthSpec;
331 mLastHeightSpec = heightSpec;
332
333 mViewFlags &= ~FLAG_SET_MEASURED_SIZE;
334 onMeasure(widthSpec, heightSpec);
335 if ((mViewFlags & FLAG_SET_MEASURED_SIZE) == 0) {
336 throw new IllegalStateException(getClass().getName()
337 + " should call setMeasuredSize() in onMeasure()");
338 }
339 }
340
341 protected void onMeasure(int widthSpec, int heightSpec) {
342 }
343
344 protected void setMeasuredSize(int width, int height) {
345 mViewFlags |= FLAG_SET_MEASURED_SIZE;
346 mMeasuredWidth = width;
347 mMeasuredHeight = height;
348 }
349
350 public int getMeasuredWidth() {
351 return mMeasuredWidth;
352 }
353
354 public int getMeasuredHeight() {
355 return mMeasuredHeight;
356 }
357
358 protected void onLayout(
359 boolean changeSize, int left, int top, int right, int bottom) {
360 }
361
362 /**
363 * Gets the bounds of the given descendant that relative to this view.
364 */
365 public boolean getBoundsOf(GLView descendant, Rect out) {
366 int xoffset = 0;
367 int yoffset = 0;
368 GLView view = descendant;
369 while (view != this) {
370 if (view == null) return false;
371 Rect bounds = view.mBounds;
372 xoffset += bounds.left;
373 yoffset += bounds.top;
374 view = view.mParent;
375 }
376 out.set(xoffset, yoffset, xoffset + descendant.getWidth(),
377 yoffset + descendant.getHeight());
378 return true;
379 }
380
381 protected void onVisibilityChanged(int visibility) {
382 for (int i = 0, n = getComponentCount(); i < n; ++i) {
383 GLView child = getComponent(i);
384 if (child.getVisibility() == GLView.VISIBLE) {
385 child.onVisibilityChanged(visibility);
386 }
387 }
388 }
389
390 protected void onAttachToRoot(GLRoot root) {
391 mRoot = root;
392 for (int i = 0, n = getComponentCount(); i < n; ++i) {
393 getComponent(i).onAttachToRoot(root);
394 }
395 }
396
397 protected void onDetachFromRoot() {
398 for (int i = 0, n = getComponentCount(); i < n; ++i) {
399 getComponent(i).onDetachFromRoot();
400 }
401 mRoot = null;
402 }
403
404 public void lockRendering() {
405 if (mRoot != null) {
406 mRoot.lockRenderThread();
407 }
408 }
409
410 public void unlockRendering() {
411 if (mRoot != null) {
412 mRoot.unlockRenderThread();
413 }
414 }
415
416 // This is for debugging only.
417 // Dump the view hierarchy into log.
418 void dumpTree(String prefix) {
419 Log.d(TAG, prefix + getClass().getSimpleName());
420 for (int i = 0, n = getComponentCount(); i < n; ++i) {
421 getComponent(i).dumpTree(prefix + "....");
422 }
423 }
424}