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