1/* 2 * Copyright (C) 2016 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 android.support.transition; 18 19import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21import android.content.Context; 22import android.graphics.Canvas; 23import android.graphics.Rect; 24import android.graphics.drawable.Drawable; 25import android.support.annotation.NonNull; 26import android.support.annotation.RequiresApi; 27import android.support.annotation.RestrictTo; 28import android.support.v4.view.ViewCompat; 29import android.view.MotionEvent; 30import android.view.View; 31import android.view.ViewGroup; 32import android.view.ViewParent; 33 34import java.lang.reflect.InvocationTargetException; 35import java.lang.reflect.Method; 36import java.util.ArrayList; 37 38@RequiresApi(14) 39class ViewOverlayApi14 implements ViewOverlayImpl { 40 41 /** 42 * The actual container for the drawables (and views, if it's a ViewGroupOverlay). 43 * All of the management and rendering details for the overlay are handled in 44 * OverlayViewGroup. 45 */ 46 protected OverlayViewGroup mOverlayViewGroup; 47 48 ViewOverlayApi14(Context context, ViewGroup hostView, View requestingView) { 49 mOverlayViewGroup = new OverlayViewGroup(context, hostView, requestingView, this); 50 } 51 52 static ViewGroup getContentView(View view) { 53 View parent = view; 54 while (parent != null) { 55 if (parent.getId() == android.R.id.content && parent instanceof ViewGroup) { 56 return (ViewGroup) parent; 57 } 58 if (parent.getParent() instanceof ViewGroup) { 59 parent = (ViewGroup) parent.getParent(); 60 } 61 } 62 return null; 63 } 64 65 static ViewOverlayApi14 createFrom(View view) { 66 ViewGroup contentView = getContentView(view); 67 if (contentView != null) { 68 final int numChildren = contentView.getChildCount(); 69 for (int i = 0; i < numChildren; ++i) { 70 View child = contentView.getChildAt(i); 71 if (child instanceof OverlayViewGroup) { 72 return ((OverlayViewGroup) child).mViewOverlay; 73 } 74 } 75 return new ViewGroupOverlayApi14(contentView.getContext(), contentView, view); 76 } 77 return null; 78 } 79 80 /** 81 * Used internally by View and ViewGroup to handle drawing and invalidation 82 * of the overlay 83 */ 84 ViewGroup getOverlayView() { 85 return mOverlayViewGroup; 86 } 87 88 @Override 89 public void add(@NonNull Drawable drawable) { 90 mOverlayViewGroup.add(drawable); 91 } 92 93 @Override 94 public void clear() { 95 mOverlayViewGroup.clear(); 96 } 97 98 @Override 99 public void remove(@NonNull Drawable drawable) { 100 mOverlayViewGroup.remove(drawable); 101 } 102 103 boolean isEmpty() { 104 return mOverlayViewGroup.isEmpty(); 105 } 106 107 108 /** 109 * OverlayViewGroup is a container that View and ViewGroup use to host 110 * drawables and views added to their overlays ({@code ViewOverlay} and 111 * {@code ViewGroupOverlay}, respectively). Drawables are added to the overlay 112 * via the add/remove methods in ViewOverlay, Views are added/removed via 113 * ViewGroupOverlay. These drawable and view objects are 114 * drawn whenever the view itself is drawn; first the view draws its own 115 * content (and children, if it is a ViewGroup), then it draws its overlay 116 * (if it has one). 117 * 118 * <p>Besides managing and drawing the list of drawables, this class serves 119 * two purposes: 120 * (1) it noops layout calls because children are absolutely positioned and 121 * (2) it forwards all invalidation calls to its host view. The invalidation 122 * redirect is necessary because the overlay is not a child of the host view 123 * and invalidation cannot therefore follow the normal path up through the 124 * parent hierarchy.</p> 125 * 126 * @see View#getOverlay() 127 * @see ViewGroup#getOverlay() 128 */ 129 static class OverlayViewGroup extends ViewGroup { 130 131 static Method sInvalidateChildInParentFastMethod; 132 133 static { 134 try { 135 sInvalidateChildInParentFastMethod = ViewGroup.class.getDeclaredMethod( 136 "invalidateChildInParentFast", int.class, int.class, Rect.class); 137 } catch (NoSuchMethodException e) { 138 } 139 140 } 141 142 /** 143 * The View for which this is an overlay. Invalidations of the overlay are redirected to 144 * this host view. 145 */ 146 ViewGroup mHostView; 147 View mRequestingView; 148 /** 149 * The set of drawables to draw when the overlay is rendered. 150 */ 151 ArrayList<Drawable> mDrawables = null; 152 /** 153 * Reference to the hosting overlay object 154 */ 155 ViewOverlayApi14 mViewOverlay; 156 157 OverlayViewGroup(Context context, ViewGroup hostView, View requestingView, 158 ViewOverlayApi14 viewOverlay) { 159 super(context); 160 mHostView = hostView; 161 mRequestingView = requestingView; 162 setRight(hostView.getWidth()); 163 setBottom(hostView.getHeight()); 164 hostView.addView(this); 165 mViewOverlay = viewOverlay; 166 } 167 168 @Override 169 public boolean dispatchTouchEvent(MotionEvent ev) { 170 // Intercept and noop all touch events - overlays do not allow touch events 171 return false; 172 } 173 174 public void add(Drawable drawable) { 175 if (mDrawables == null) { 176 177 mDrawables = new ArrayList<>(); 178 } 179 if (!mDrawables.contains(drawable)) { 180 // Make each drawable unique in the overlay; can't add it more than once 181 mDrawables.add(drawable); 182 invalidate(drawable.getBounds()); 183 drawable.setCallback(this); 184 } 185 } 186 187 public void remove(Drawable drawable) { 188 if (mDrawables != null) { 189 mDrawables.remove(drawable); 190 invalidate(drawable.getBounds()); 191 drawable.setCallback(null); 192 } 193 } 194 195 @Override 196 protected boolean verifyDrawable(@NonNull Drawable who) { 197 return super.verifyDrawable(who) || (mDrawables != null && mDrawables.contains(who)); 198 } 199 200 public void add(View child) { 201 if (child.getParent() instanceof ViewGroup) { 202 ViewGroup parent = (ViewGroup) child.getParent(); 203 if (parent != mHostView && parent.getParent() != null 204 && ViewCompat.isAttachedToWindow(parent)) { 205 // Moving to different container; figure out how to position child such that 206 // it is in the same location on the screen 207 int[] parentLocation = new int[2]; 208 int[] hostViewLocation = new int[2]; 209 parent.getLocationOnScreen(parentLocation); 210 mHostView.getLocationOnScreen(hostViewLocation); 211 ViewCompat.offsetLeftAndRight(child, parentLocation[0] - hostViewLocation[0]); 212 ViewCompat.offsetTopAndBottom(child, parentLocation[1] - hostViewLocation[1]); 213 } 214 parent.removeView(child); 215// if (parent.getLayoutTransition() != null) { 216// // LayoutTransition will cause the child to delay removal - cancel it 217// parent.getLayoutTransition().cancel(LayoutTransition.DISAPPEARING); 218// } 219 // fail-safe if view is still attached for any reason 220 if (child.getParent() != null) { 221 parent.removeView(child); 222 } 223 } 224 super.addView(child, getChildCount() - 1); 225 } 226 227 public void remove(View view) { 228 super.removeView(view); 229 if (isEmpty()) { 230 mHostView.removeView(this); 231 } 232 } 233 234 public void clear() { 235 removeAllViews(); 236 if (mDrawables != null) { 237 mDrawables.clear(); 238 } 239 } 240 241 boolean isEmpty() { 242 return getChildCount() == 0 243 && (mDrawables == null || mDrawables.size() == 0); 244 } 245 246 @Override 247 public void invalidateDrawable(@NonNull Drawable drawable) { 248 invalidate(drawable.getBounds()); 249 } 250 251 @Override 252 protected void dispatchDraw(Canvas canvas) { 253 int[] contentViewLocation = new int[2]; 254 int[] hostViewLocation = new int[2]; 255 mHostView.getLocationOnScreen(contentViewLocation); 256 mRequestingView.getLocationOnScreen(hostViewLocation); 257 canvas.translate(hostViewLocation[0] - contentViewLocation[0], 258 hostViewLocation[1] - contentViewLocation[1]); 259 canvas.clipRect( 260 new Rect(0, 0, mRequestingView.getWidth(), mRequestingView.getHeight())); 261 super.dispatchDraw(canvas); 262 final int numDrawables = (mDrawables == null) ? 0 : mDrawables.size(); 263 for (int i = 0; i < numDrawables; ++i) { 264 mDrawables.get(i).draw(canvas); 265 } 266 } 267 268 @Override 269 protected void onLayout(boolean changed, int l, int t, int r, int b) { 270 // Noop: children are positioned absolutely 271 } 272 273 /* 274 The following invalidation overrides exist for the purpose of redirecting invalidation to 275 the host view. The overlay is not parented to the host view (since a View cannot be a 276 parent), so the invalidation cannot proceed through the normal parent hierarchy. 277 There is a built-in assumption that the overlay exactly covers the host view, therefore 278 the invalidation rectangles received do not need to be adjusted when forwarded to 279 the host view. 280 */ 281 282 private void getOffset(int[] offset) { 283 int[] contentViewLocation = new int[2]; 284 int[] hostViewLocation = new int[2]; 285 mHostView.getLocationOnScreen(contentViewLocation); 286 mRequestingView.getLocationOnScreen(hostViewLocation); 287 offset[0] = hostViewLocation[0] - contentViewLocation[0]; 288 offset[1] = hostViewLocation[1] - contentViewLocation[1]; 289 } 290 291 public void invalidateChildFast(View child, final Rect dirty) { 292 if (mHostView != null) { 293 // Note: This is not a "fast" invalidation. Would be nice to instead invalidate 294 // using DisplayList properties and a dirty rect instead of causing a real 295 // invalidation of the host view 296 int left = child.getLeft(); 297 int top = child.getTop(); 298 int[] offset = new int[2]; 299 getOffset(offset); 300 // TODO: implement transforms 301// if (!child.getMatrix().isIdentity()) { 302// child.transformRect(dirty); 303// } 304 dirty.offset(left + offset[0], top + offset[1]); 305 mHostView.invalidate(dirty); 306 } 307 } 308 309 /** 310 * @hide 311 */ 312 @RestrictTo(LIBRARY_GROUP) 313 protected ViewParent invalidateChildInParentFast(int left, int top, Rect dirty) { 314 if (mHostView instanceof ViewGroup && sInvalidateChildInParentFastMethod != null) { 315 try { 316 int[] offset = new int[2]; 317 getOffset(offset); 318 sInvalidateChildInParentFastMethod.invoke(mHostView, left, top, dirty); 319 } catch (IllegalAccessException e) { 320 e.printStackTrace(); 321 } catch (InvocationTargetException e) { 322 e.printStackTrace(); 323 } 324 } 325 return null; 326 } 327 328 @SuppressWarnings("deprecation") 329 @Override 330 public ViewParent invalidateChildInParent(int[] location, Rect dirty) { 331 if (mHostView != null) { 332 dirty.offset(location[0], location[1]); 333 if (mHostView instanceof ViewGroup) { 334 location[0] = 0; 335 location[1] = 0; 336 int[] offset = new int[2]; 337 getOffset(offset); 338 dirty.offset(offset[0], offset[1]); 339 return super.invalidateChildInParent(location, dirty); 340// return ((ViewGroup) mHostView).invalidateChildInParent(location, dirty); 341 } else { 342 invalidate(dirty); 343 } 344 } 345 return null; 346 } 347 348 static class TouchInterceptor extends View { 349 TouchInterceptor(Context context) { 350 super(context); 351 } 352 } 353 } 354 355 356} 357