/// <summary>Draw one child of this View Group.</summary> /// <remarks> /// Draw one child of this View Group. This method is responsible for getting /// the canvas in the right state. This includes clipping, translating so /// that the child's scrolled origin is at 0, 0, and applying any animation /// transformations. /// </remarks> /// <param name="canvas">The canvas on which to draw the child</param> /// <param name="child">Who to draw</param> /// <param name="drawingTime">The time at which draw is occuring</param> /// <returns>True if an invalidate() was issued</returns> protected internal virtual bool drawChild(android.graphics.Canvas canvas, android.view.View child, long drawingTime) { bool more = false; int cl = child.mLeft; int ct = child.mTop; int cr = child.mRight; int cb = child.mBottom; bool childHasIdentityMatrix = child.hasIdentityMatrix(); int flags = mGroupFlags; if ((flags & FLAG_CLEAR_TRANSFORMATION) == FLAG_CLEAR_TRANSFORMATION) { mChildTransformation.clear(); mGroupFlags &= ~FLAG_CLEAR_TRANSFORMATION; } android.view.animation.Transformation transformToApply = null; android.view.animation.Transformation invalidationTransform; android.view.animation.Animation a = child.getAnimation(); bool concatMatrix = false; bool scalingRequired = false; bool caching; int layerType = mDrawLayers ? child.getLayerType() : LAYER_TYPE_NONE; bool hardwareAccelerated = canvas.isHardwareAccelerated(); if ((flags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE || (flags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE) { caching = true; if (mAttachInfo != null) { scalingRequired = mAttachInfo.mScalingRequired; } } else { caching = (layerType != LAYER_TYPE_NONE) || hardwareAccelerated; } if (a != null) { bool initialized = a.isInitialized(); if (!initialized) { a.initialize(cr - cl, cb - ct, getWidth(), getHeight()); a.initializeInvalidateRegion(0, 0, cr - cl, cb - ct); child.onAnimationStart(); } more = a.getTransformation(drawingTime, mChildTransformation, scalingRequired ? mAttachInfo .mApplicationScale : 1f); if (scalingRequired && mAttachInfo.mApplicationScale != 1f) { if (mInvalidationTransformation == null) { mInvalidationTransformation = new android.view.animation.Transformation(); } invalidationTransform = mInvalidationTransformation; a.getTransformation(drawingTime, invalidationTransform, 1f); } else { invalidationTransform = mChildTransformation; } transformToApply = mChildTransformation; concatMatrix = a.willChangeTransformationMatrix(); if (more) { if (!a.willChangeBounds()) { if ((flags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) == FLAG_OPTIMIZE_INVALIDATE) { mGroupFlags |= FLAG_INVALIDATE_REQUIRED; } else { if ((flags & FLAG_INVALIDATE_REQUIRED) == 0) { // The child need to draw an animation, potentially offscreen, so // make sure we do not cancel invalidate requests mPrivateFlags |= DRAW_ANIMATION; invalidate(cl, ct, cr, cb); } } } else { if (mInvalidateRegion == null) { mInvalidateRegion = new android.graphics.RectF(); } android.graphics.RectF region = mInvalidateRegion; a.getInvalidateRegion(0, 0, cr - cl, cb - ct, region, invalidationTransform); // The child need to draw an animation, potentially offscreen, so // make sure we do not cancel invalidate requests mPrivateFlags |= DRAW_ANIMATION; int left = cl + (int)region.left; int top = ct + (int)region.top; invalidate(left, top, left + (int)(region.width() + .5f), top + (int)(region.height () + .5f)); } } } else { if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) == FLAG_SUPPORT_STATIC_TRANSFORMATIONS) { bool hasTransform = getChildStaticTransformation(child, mChildTransformation); if (hasTransform) { int transformType = mChildTransformation.getTransformationType(); transformToApply = transformType != android.view.animation.Transformation.TYPE_IDENTITY ? mChildTransformation : null; concatMatrix = (transformType & android.view.animation.Transformation.TYPE_MATRIX ) != 0; } } } concatMatrix |= !childHasIdentityMatrix; // Sets the flag as early as possible to allow draw() implementations // to call invalidate() successfully when doing animations child.mPrivateFlags |= DRAWN; if (!concatMatrix && canvas.quickReject(cl, ct, cr, cb, android.graphics.Canvas.EdgeType .BW) && (child.mPrivateFlags & DRAW_ANIMATION) == 0) { return more; } float alpha = child.getAlpha(); // Bail out early if the view does not need to be drawn if (alpha <= android.view.ViewConfiguration.ALPHA_THRESHOLD && (child.mPrivateFlags & ALPHA_SET) == 0 && !(child is android.view.SurfaceView)) { return more; } if (hardwareAccelerated) { // Clear INVALIDATED flag to allow invalidation to occur during rendering, but // retain the flag's value temporarily in the mRecreateDisplayList flag child.mRecreateDisplayList = (child.mPrivateFlags & INVALIDATED) == INVALIDATED; child.mPrivateFlags &= ~INVALIDATED; } child.computeScroll(); int sx = child.mScrollX; int sy = child.mScrollY; android.view.DisplayList displayList = null; android.graphics.Bitmap cache = null; bool hasDisplayList = false; if (caching) { if (!hardwareAccelerated) { if (layerType != LAYER_TYPE_NONE) { layerType = LAYER_TYPE_SOFTWARE; child.buildDrawingCache(true); } cache = child.getDrawingCache(true); } else { switch (layerType) { case LAYER_TYPE_SOFTWARE: { child.buildDrawingCache(true); cache = child.getDrawingCache(true); break; } case LAYER_TYPE_NONE: { // Delay getting the display list until animation-driven alpha values are // set up and possibly passed on to the view hasDisplayList = child.canHaveDisplayList(); break; } } } } bool hasNoCache = cache == null || hasDisplayList; bool offsetForScroll = cache == null && !hasDisplayList && layerType != LAYER_TYPE_HARDWARE; int restoreTo = canvas.save(); if (offsetForScroll) { canvas.translate(cl - sx, ct - sy); } else { canvas.translate(cl, ct); if (scalingRequired) { // mAttachInfo cannot be null, otherwise scalingRequired == false float scale = 1.0f / mAttachInfo.mApplicationScale; canvas.scale(scale, scale); } } if (transformToApply != null || alpha < 1.0f || !child.hasIdentityMatrix()) { if (transformToApply != null || !childHasIdentityMatrix) { int transX = 0; int transY = 0; if (offsetForScroll) { transX = -sx; transY = -sy; } if (transformToApply != null) { if (concatMatrix) { // Undo the scroll translation, apply the transformation matrix, // then redo the scroll translate to get the correct result. canvas.translate(-transX, -transY); canvas.concat(transformToApply.getMatrix()); canvas.translate(transX, transY); mGroupFlags |= FLAG_CLEAR_TRANSFORMATION; } float transformAlpha = transformToApply.getAlpha(); if (transformAlpha < 1.0f) { alpha *= transformToApply.getAlpha(); mGroupFlags |= FLAG_CLEAR_TRANSFORMATION; } } if (!childHasIdentityMatrix) { canvas.translate(-transX, -transY); canvas.concat(child.getMatrix()); canvas.translate(transX, transY); } } if (alpha < 1.0f) { mGroupFlags |= FLAG_CLEAR_TRANSFORMATION; if (hasNoCache) { int multipliedAlpha = (int)(255 * alpha); if (!child.onSetAlpha(multipliedAlpha)) { int layerFlags = android.graphics.Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; if ((flags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN || layerType != LAYER_TYPE_NONE) { layerFlags |= android.graphics.Canvas.CLIP_TO_LAYER_SAVE_FLAG; } if (layerType == LAYER_TYPE_NONE) { int scrollX = hasDisplayList ? 0 : sx; int scrollY = hasDisplayList ? 0 : sy; canvas.saveLayerAlpha(scrollX, scrollY, scrollX + cr - cl, scrollY + cb - ct, multipliedAlpha , layerFlags); } } else { // Alpha is handled by the child directly, clobber the layer's alpha child.mPrivateFlags |= ALPHA_SET; } } } } else { if ((child.mPrivateFlags & ALPHA_SET) == ALPHA_SET) { child.onSetAlpha(255); child.mPrivateFlags &= ~ALPHA_SET; } } if ((flags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { if (offsetForScroll) { canvas.clipRect(sx, sy, sx + (cr - cl), sy + (cb - ct)); } else { if (!scalingRequired || cache == null) { canvas.clipRect(0, 0, cr - cl, cb - ct); } else { canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight()); } } } if (hasDisplayList) { displayList = child.getDisplayList(); if (!displayList.isValid()) { // Uncommon, but possible. If a view is removed from the hierarchy during the call // to getDisplayList(), the display list will be marked invalid and we should not // try to use it again. displayList = null; hasDisplayList = false; } } if (hasNoCache) { bool layerRendered = false; if (layerType == LAYER_TYPE_HARDWARE) { android.view.HardwareLayer layer = child.getHardwareLayer(); if (layer != null && layer.isValid()) { child.mLayerPaint.setAlpha((int)(alpha * 255)); ((android.view.HardwareCanvas)canvas).drawHardwareLayer(layer, 0, 0, child.mLayerPaint ); layerRendered = true; } else { int scrollX = hasDisplayList ? 0 : sx; int scrollY = hasDisplayList ? 0 : sy; canvas.saveLayer(scrollX, scrollY, scrollX + cr - cl, scrollY + cb - ct, child.mLayerPaint , android.graphics.Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | android.graphics.Canvas.CLIP_TO_LAYER_SAVE_FLAG ); } } if (!layerRendered) { if (!hasDisplayList) { // Fast path for layouts with no backgrounds if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { child.mPrivateFlags &= ~DIRTY_MASK; child.dispatchDraw(canvas); } else { child.draw(canvas); } } else { child.mPrivateFlags &= ~DIRTY_MASK; ((android.view.HardwareCanvas)canvas).drawDisplayList(displayList, cr - cl, cb - ct, null); } } } else { if (cache != null) { child.mPrivateFlags &= ~DIRTY_MASK; android.graphics.Paint cachePaint; if (layerType == LAYER_TYPE_NONE) { cachePaint = mCachePaint; if (alpha < 1.0f) { cachePaint.setAlpha((int)(alpha * 255)); mGroupFlags |= FLAG_ALPHA_LOWER_THAN_ONE; } else { if ((flags & FLAG_ALPHA_LOWER_THAN_ONE) == FLAG_ALPHA_LOWER_THAN_ONE) { cachePaint.setAlpha(255); mGroupFlags &= ~FLAG_ALPHA_LOWER_THAN_ONE; } } } else { cachePaint = child.mLayerPaint; cachePaint.setAlpha((int)(alpha * 255)); } canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint); } } canvas.restoreToCount(restoreTo); if (a != null && !more) { if (!hardwareAccelerated && !a.getFillAfter()) { child.onSetAlpha(255); } finishAnimatingView(child, a); } if (more && hardwareAccelerated) { // invalidation is the trigger to recreate display lists, so if we're using // display lists to render, force an invalidate to allow the animation to // continue drawing another frame invalidate(true); if (a.hasAlpha() && (child.mPrivateFlags & ALPHA_SET) == ALPHA_SET) { // alpha animations should cause the child to recreate its display list child.invalidate(true); } } child.mRecreateDisplayList = false; return more; }
/// <summary> /// Returns true if a child view contains the specified point when transformed /// into its coordinate space. /// </summary> /// <remarks> /// Returns true if a child view contains the specified point when transformed /// into its coordinate space. /// Child must not be null. /// </remarks> /// <hide></hide> protected internal virtual bool isTransformedTouchPointInView(float x, float y, android.view.View child, android.graphics.PointF outLocalPoint) { float localX = x + mScrollX - child.mLeft; float localY = y + mScrollY - child.mTop; if (!child.hasIdentityMatrix() && mAttachInfo != null) { float[] localXY = mAttachInfo.mTmpTransformLocation; localXY[0] = localX; localXY[1] = localY; child.getInverseMatrix().mapPoints(localXY); localX = localXY[0]; localY = localXY[1]; } bool isInView = child.pointInView(localX, localY); if (isInView && outLocalPoint != null) { outLocalPoint.set(localX, localY); } return isInView; }
/// <summary> /// Transforms a motion event into the coordinate space of a particular child view, /// filters out irrelevant pointer ids, and overrides its action if necessary. /// </summary> /// <remarks> /// Transforms a motion event into the coordinate space of a particular child view, /// filters out irrelevant pointer ids, and overrides its action if necessary. /// If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. /// </remarks> private bool dispatchTransformedTouchEvent(android.view.MotionEvent @event, bool cancel, android.view.View child, int desiredPointerIdBits) { bool handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. int oldAction = @event.getAction(); if (cancel || oldAction == android.view.MotionEvent.ACTION_CANCEL) { @event.setAction(android.view.MotionEvent.ACTION_CANCEL); if (child == null) { handled = base.dispatchTouchEvent(@event); } else { handled = child.dispatchTouchEvent(@event); } @event.setAction(oldAction); return handled; } // Calculate the number of pointers to deliver. int oldPointerIdBits = @event.getPointerIdBits(); int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // If for some reason we ended up in an inconsistent state where it looks like we // might produce a motion event with no pointers in it, then drop the event. if (newPointerIdBits == 0) { return false; } // If the number of pointers is the same and we don't need to perform any fancy // irreversible transformations, then we can reuse the motion event for this // dispatch as long as we are careful to revert any changes we make. // Otherwise we need to make a copy. android.view.MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { handled = base.dispatchTouchEvent(@event); } else { float offsetX = mScrollX - child.mLeft; float offsetY = mScrollY - child.mTop; @event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(@event); @event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = android.view.MotionEvent.obtain(@event); } else { transformedEvent = @event.split(newPointerIdBits); } // Perform any necessary transformations and dispatch. if (child == null) { handled = base.dispatchTouchEvent(transformedEvent); } else { float offsetX = mScrollX - child.mLeft; float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (!child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }
/// <summary> /// Dispatches a generic pointer event to a child, taking into account /// transformations that apply to the child. /// </summary> /// <remarks> /// Dispatches a generic pointer event to a child, taking into account /// transformations that apply to the child. /// </remarks> /// <param name="event">The event to send.</param> /// <param name="child">The view to send the event to.</param> /// <returns> /// /// <code>true</code> /// if the child handled the event. /// </returns> private bool dispatchTransformedGenericPointerEvent(android.view.MotionEvent @event , android.view.View child) { float offsetX = mScrollX - child.mLeft; float offsetY = mScrollY - child.mTop; bool handled; if (!child.hasIdentityMatrix()) { android.view.MotionEvent transformedEvent = android.view.MotionEvent.obtain(@event ); transformedEvent.offsetLocation(offsetX, offsetY); transformedEvent.transform(child.getInverseMatrix()); handled = child.dispatchGenericMotionEvent(transformedEvent); transformedEvent.recycle(); } else { @event.offsetLocation(offsetX, offsetY); handled = child.dispatchGenericMotionEvent(@event); @event.offsetLocation(-offsetX, -offsetY); } return handled; }