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 com.android.gallery3d.anim.CanvasAnimation; 20 import com.android.gallery3d.common.Utils; 21 22 import android.graphics.Rect; 23 import android.os.SystemClock; 24 import android.view.MotionEvent; 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 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, 0); 233 234 CanvasAnimation anim = component.mAnimation; 235 if (anim != null) { 236 canvas.save(anim.getCanvasSaveFlags()); 237 if (anim.calculate(canvas.currentAnimationTimeMillis())) { 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, 0); 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