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