Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6587045a authored by Adam Cohen's avatar Adam Cohen Committed by Android (Google) Code Review
Browse files

Merge "Cleanup and bug fixes in StackView -> pushed functionality from...

Merge "Cleanup and bug fixes in StackView -> pushed functionality from AdapterViewAnimator to StackView -> only modifying clipping flags as far up the view    hierarchy as needed -> still need framework level solution for drawing outside bounds"
parents a1644109 9b073948
Loading
Loading
Loading
Loading
+12 −2
Original line number Diff line number Diff line
@@ -970,7 +970,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            mMotionTarget = null;
        }

        return target.dispatchTouchEvent(ev);
        if (target.dispatchTouchEvent(ev)) {
            return true;
        } else {
            ev.setLocation(xf, yf);
        }
        return false;
    }

    /**
@@ -1006,7 +1011,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            // There are many subtle interactions in touch event dispatch; change at your own risk.
            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            ev.offsetLocation(localX - x, localY - y);
            return child.dispatchTouchEvent(ev);
            if (child.dispatchTouchEvent(ev)) {
                return true;
            } else {
                ev.offsetLocation(x - localX, y - localY);
                return false;
            }
        }
        return false;
    }
+6 −106
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
@@ -323,15 +324,13 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
        return null;
    }

    private LayoutParams createOrReuseLayoutParams(View v) {
    LayoutParams createOrReuseLayoutParams(View v) {
        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
        if (currentLp instanceof LayoutParams) {
        if (currentLp instanceof ViewGroup.LayoutParams) {
            LayoutParams lp = (LayoutParams) currentLp;
            lp.setHorizontalOffset(0);
            lp.setVerticalOffset(0);
            return lp;
        }
        return new LayoutParams(v);
        return new ViewGroup.LayoutParams(0, 0);
    }

    void showOnly(int childIndex, boolean animate, boolean onLayout) {
@@ -472,10 +471,9 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>

            int childRight = mPaddingLeft + child.getMeasuredWidth();
            int childBottom = mPaddingTop + child.getMeasuredHeight();
            LayoutParams lp = (LayoutParams) child.getLayoutParams();

            child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
                    childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
            child.layout(mPaddingLeft, mPaddingTop,
                    childRight, childBottom);
        }
        mDataChanged = false;
    }
@@ -738,102 +736,4 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
            setAdapter(mRemoteViewsAdapter);
        }
    }

    private final Rect dirtyRect = new Rect();
    @Override
    public void removeViewInLayout(View view) {
        // TODO: need to investigate this block a bit more
        // and perhaps fix some other invalidations issues.
        View parent = null;
        view.setVisibility(INVISIBLE);
        if (view.getLayoutParams() instanceof LayoutParams) {
            LayoutParams lp = (LayoutParams) view.getLayoutParams();
            parent = lp.getParentAndDirtyRegion(dirtyRect);
        }

        super.removeViewInLayout(view);

        if (parent != null)
            parent.invalidate(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom);
    }

    static class LayoutParams extends ViewGroup.LayoutParams {
        int horizontalOffset;
        int verticalOffset;
        View mView;

        LayoutParams(View view) {
            super(0, 0);
            horizontalOffset = 0;
            verticalOffset = 0;
            mView = view;
        }

        LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            horizontalOffset = 0;
            verticalOffset = 0;
        }

        private Rect parentRect = new Rect();
        void invalidateGlobalRegion(View v, Rect r) {
            View p = v;
            boolean firstPass = true;
            parentRect.set(0, 0, 0, 0);
            while (p.getParent() != null && p.getParent() instanceof View
                    && !parentRect.contains(r)) {
                if (!firstPass) r.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY());
                firstPass = false;
                p = (View) p.getParent();
                parentRect.set(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY(),
                        p.getRight() - p.getScrollX(), p.getBottom() - p.getScrollY());
            }
            p.invalidate(r.left, r.top, r.right, r.bottom);
        }

        public View getParentAndDirtyRegion(Rect globalRect) {
            globalRect.set(mView.getLeft(), mView.getTop(), mView.getRight(), mView.getBottom());
            View p = mView;
            boolean firstPass = true;
            parentRect.set(0, 0, 0, 0);
            while (p.getParent() != null && p.getParent() instanceof View
                    && !parentRect.contains(globalRect)) {
                if (!firstPass) {
                    globalRect.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY());
                }

                firstPass = false;
                p = (View) p.getParent();
                parentRect.set(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY(),
                        p.getRight() - p.getScrollX(), p.getBottom() - p.getScrollY());
            }
            return p;
        }

        private Rect invalidateRect = new Rect();
        // This is public so that PropertyAnimator can access it
        public void setVerticalOffset(int newVerticalOffset) {
            int offsetDelta = newVerticalOffset - verticalOffset;
            verticalOffset = newVerticalOffset;
            if (mView != null) {
                mView.requestLayout();
                int top = Math.min(mView.getTop() + offsetDelta, mView.getTop());
                int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom());
                invalidateRect.set(mView.getLeft(), top, mView.getRight(), bottom);
                invalidateGlobalRegion(mView, invalidateRect);
            }
        }

        public void setHorizontalOffset(int newHorizontalOffset) {
            int offsetDelta = newHorizontalOffset - horizontalOffset;
            horizontalOffset = newHorizontalOffset;
            if (mView != null) {
                mView.requestLayout();
                int left = Math.min(mView.getLeft() + offsetDelta, mView.getLeft());
                int right = Math.max(mView.getRight() + offsetDelta, mView.getRight());
                invalidateRect.set(left, mView.getTop(), right, mView.getBottom());
                invalidateGlobalRegion(mView, invalidateRect);
            }
        }
    }
}
+220 −77
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -66,8 +67,6 @@ public class StackView extends AdapterViewAnimator {
    private static final float SLIDE_UP_RATIO = 0.7f;

    private final WeakHashMap<View, Float> mRotations = new WeakHashMap<View, Float>();
    private final WeakHashMap<View, Integer>
            mChildrenToApplyTransformsTo = new WeakHashMap<View, Integer>();

    /**
     * Sentinel value for no current active pointer.
@@ -90,9 +89,12 @@ public class StackView extends AdapterViewAnimator {
    private int mMaximumVelocity;
    private VelocityTracker mVelocityTracker;

    private static HolographicHelper sHolographicHelper;
    private ImageView mHighlight;
    private StackSlider mStackSlider;
    private boolean mFirstLayoutHappened = false;
    private ViewGroup mAncestorContainingAllChildren = null;
    private int mAncestorHeight = 0;

    public StackView(Context context) {
        super(context);
@@ -117,9 +119,11 @@ public class StackView extends AdapterViewAnimator {
        addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight));
        mStackSlider = new StackSlider();

        if (!sPaintsInitialized) {
            initializePaints();
        if (sHolographicHelper == null) {
            sHolographicHelper = new HolographicHelper();
        }
        setClipChildren(false);
        setClipToPadding(false);
    }

    /**
@@ -205,6 +209,7 @@ public class StackView extends AdapterViewAnimator {
        if (!mRotations.containsKey(child)) {
            float rotation = (float) (Math.random()*26 - 13);
            mRotations.put(child, rotation);
            child.setRotation(rotation);
        }

        // Child has been removed
@@ -212,47 +217,36 @@ public class StackView extends AdapterViewAnimator {
            if (mRotations.containsKey(child)) {
                mRotations.remove(child);
            }
            if (mChildrenToApplyTransformsTo.containsKey(child)) {
                mChildrenToApplyTransformsTo.remove(child);
        }
    }

        // if this view is already in the layout, we need to
        // wait until layout has finished in order to set the
        // pivot point of the rotation (requiring getMeasuredWidth/Height())
        mChildrenToApplyTransformsTo.put(child, relativeIndex);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
    }

        if (!mChildrenToApplyTransformsTo.isEmpty()) {
            for (View child: mChildrenToApplyTransformsTo.keySet()) {
                if (mRotations.containsKey(child)) {
                    child.setPivotX(child.getMeasuredWidth()/2);
                    child.setPivotY(child.getMeasuredHeight()/2);
                    child.setRotation(mRotations.get(child));
    // TODO: right now, this code walks up the hierarchy as far as needed and disables clipping
    // so that the stack's children can draw outside of the stack's bounds. This is fine within
    // the context of widgets in the launcher, but is destructive in general, as the clipping
    // values are not being reset. For this to be a full framework level widget, we will need
    // framework level support for drawing outside of a parent's bounds.
    private void disableParentalClipping() {
        if (mAncestorContainingAllChildren != null) {
            Log.v(TAG, "Disabling parental clipping.");
            ViewGroup vg = this;
            while (vg.getParent() != null && vg.getParent() instanceof ViewGroup) {
                if (vg == mAncestorContainingAllChildren) break;
                vg = (ViewGroup) vg.getParent();
                vg.setClipChildren(false);
                vg.setClipToPadding(false);
            }
        }
            mChildrenToApplyTransformsTo.clear();
    }

    private void onLayout() {
        if (!mFirstLayoutHappened) {
            mViewHeight = Math.round(SLIDE_UP_RATIO*getMeasuredHeight());
            mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO*mViewHeight);

            // TODO: Right now this walks all the way up the view hierarchy and disables
            // ClipChildren and ClipToPadding. We're probably going  to want to reset
            // these flags as well.
            setClipChildren(false);
            setClipToPadding(false);
            ViewGroup view = this;
            while (view.getParent() != null && view.getParent() instanceof ViewGroup) {
                view = (ViewGroup) view.getParent();
                view.setClipChildren(false);
                view.setClipToPadding(false);
            }
            mFirstLayoutHappened = true;
        }
    }
@@ -261,7 +255,6 @@ public class StackView extends AdapterViewAnimator {
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch(action & MotionEvent.ACTION_MASK) {

            case MotionEvent.ACTION_DOWN: {
                if (mActivePointerId == INVALID_POINTER) {
                    mInitialX = ev.getX();
@@ -320,7 +313,7 @@ public class StackView extends AdapterViewAnimator {
            View v = getViewAtRelativeIndex(activeIndex);
            if (v == null) return;

            mHighlight.setImageBitmap(createOutline(v));
            mHighlight.setImageBitmap(sHolographicHelper.createOutline(v));
            mHighlight.bringToFront();
            v.bringToFront();
            mStackSlider.setView(v);
@@ -640,7 +633,6 @@ public class StackView extends AdapterViewAnimator {
        float getXProgress() {
            return mXProgress;
        }

    }

    @Override
@@ -649,20 +641,171 @@ public class StackView extends AdapterViewAnimator {
        setDisplayedChild(mWhichChild);
    }

    private static final Paint sHolographicPaint = new Paint();
    private static final Paint sErasePaint = new Paint();
    private static boolean sPaintsInitialized = false;
    private static final float STROKE_WIDTH = 3.0f;
    LayoutParams createOrReuseLayoutParams(View v) {
        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
        if (currentLp instanceof LayoutParams) {
            LayoutParams lp = (LayoutParams) currentLp;
            lp.setHorizontalOffset(0);
            lp.setVerticalOffset(0);
            return lp;
        }
        return new LayoutParams(v);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        boolean dataChanged = mDataChanged;
        if (dataChanged) {
            handleDataChanged();

            // if the data changes, mWhichChild might be out of the bounds of the adapter
            // in this case, we reset mWhichChild to the beginning
            if (mWhichChild >= mAdapter.getCount())
                mWhichChild = 0;

            showOnly(mWhichChild, true, true);
        }

    static void initializePaints() {
        sHolographicPaint.setColor(0xff6699ff);
        sHolographicPaint.setFilterBitmap(true);
        sErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
        sErasePaint.setFilterBitmap(true);
        sPaintsInitialized = true;
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);

            int childRight = mPaddingLeft + child.getMeasuredWidth();
            int childBottom = mPaddingTop + child.getMeasuredHeight();
            LayoutParams lp = (LayoutParams) child.getLayoutParams();

            child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
                    childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);

            //TODO: temp until fix in View
            child.setPivotX(child.getMeasuredWidth()/2);
            child.setPivotY(child.getMeasuredHeight()/2);
        }

    static Bitmap createOutline(View v) {
        mDataChanged = false;
        onLayout();
    }

    class LayoutParams extends ViewGroup.LayoutParams {
        int horizontalOffset;
        int verticalOffset;
        View mView;

        LayoutParams(View view) {
            super(0, 0);
            horizontalOffset = 0;
            verticalOffset = 0;
            mView = view;
        }

        LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            horizontalOffset = 0;
            verticalOffset = 0;
        }

        private Rect parentRect = new Rect();
        void invalidateGlobalRegion(View v, Rect r) {
            View p = v;
            if (!(v.getParent() != null && v.getParent() instanceof View)) return;

            View gp = (View) v.getParent();
            boolean firstPass = true;
            parentRect.set(0, 0, 0, 0);
            int depth = 0;
            while (gp.getParent() != null && gp.getParent() instanceof View
                    && !parentRect.contains(r)) {
                if (!firstPass) {
                    r.offset(p.getLeft() - gp.getScrollX(), p.getTop() - gp.getScrollY());
                    depth++;
                }
                firstPass = false;
                p = (View) p.getParent();
                gp = (View) p.getParent();
                parentRect.set(p.getLeft() - gp.getScrollX(), p.getTop() - gp.getScrollY(),
                        p.getRight() - gp.getScrollX(), p.getBottom() - gp.getScrollY());
            }

            if (depth > mAncestorHeight) {
                mAncestorContainingAllChildren = (ViewGroup) p;
                mAncestorHeight = depth;
                disableParentalClipping();
            }

            p.invalidate(r.left, r.top, r.right, r.bottom);
        }

        private Rect invalidateRect = new Rect();
        private RectF invalidateRectf = new RectF();
        // This is public so that PropertyAnimator can access it
        public void setVerticalOffset(int newVerticalOffset) {
            int offsetDelta = newVerticalOffset - verticalOffset;
            verticalOffset = newVerticalOffset;

            if (mView != null) {
                mView.requestLayout();
                int top = Math.min(mView.getTop() + offsetDelta, mView.getTop());
                int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom());

                invalidateRectf.set(mView.getLeft(),  top, mView.getRight(), bottom);

                float xoffset = -invalidateRectf.left;
                float yoffset = -invalidateRectf.top;
                invalidateRectf.offset(xoffset, yoffset);
                mView.getMatrix().mapRect(invalidateRectf);
                invalidateRectf.offset(-xoffset, -yoffset);
                invalidateRect.set((int) Math.floor(invalidateRectf.left),
                        (int) Math.floor(invalidateRectf.top),
                        (int) Math.ceil(invalidateRectf.right),
                        (int) Math.ceil(invalidateRectf.bottom));

                invalidateGlobalRegion(mView, invalidateRect);
            }
        }

        public void setHorizontalOffset(int newHorizontalOffset) {
            int offsetDelta = newHorizontalOffset - horizontalOffset;
            horizontalOffset = newHorizontalOffset;

            if (mView != null) {
                mView.requestLayout();
                int left = Math.min(mView.getLeft() + offsetDelta, mView.getLeft());
                int right = Math.max(mView.getRight() + offsetDelta, mView.getRight());
                invalidateRectf.set(left,  mView.getTop(), right, mView.getBottom());

                float xoffset = -invalidateRectf.left;
                float yoffset = -invalidateRectf.top;
                invalidateRectf.offset(xoffset, yoffset);
                mView.getMatrix().mapRect(invalidateRectf);
                invalidateRectf.offset(-xoffset, -yoffset);

                invalidateRect.set((int) Math.floor(invalidateRectf.left),
                        (int) Math.floor(invalidateRectf.top),
                        (int) Math.ceil(invalidateRectf.right),
                        (int) Math.ceil(invalidateRectf.bottom));

                invalidateGlobalRegion(mView, invalidateRect);
            }
        }
    }

    private static class HolographicHelper {
        private final Paint mHolographicPaint = new Paint();
        private final Paint mErasePaint = new Paint();
        private final float STROKE_WIDTH = 3.0f;

        HolographicHelper() {
            initializePaints();
        }

        void initializePaints() {
            mHolographicPaint.setColor(0xff6699ff);
            mHolographicPaint.setFilterBitmap(true);
            mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
            mErasePaint.setFilterBitmap(true);
        }

        Bitmap createOutline(View v) {
            if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) {
                return null;
            }
@@ -675,29 +818,29 @@ public class StackView extends AdapterViewAnimator {
            v.setRotationX(0);
            canvas.concat(v.getMatrix());
            v.draw(canvas);

            v.setRotationX(rotationX);

        Bitmap outlineBitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
                Bitmap.Config.ARGB_8888);
        Canvas outlineCanvas = new Canvas(outlineBitmap);
        drawOutline(outlineCanvas, bitmap);
        bitmap.recycle();
        return outlineBitmap;
            drawOutline(canvas, bitmap);
            return bitmap;
        }

    static void drawOutline(Canvas dest, Bitmap src) {
        dest.drawColor(0, PorterDuff.Mode.CLEAR);

        final Matrix id = new Matrix();
        final Matrix scaleMatrix = new Matrix();
        void drawOutline(Canvas dest, Bitmap src) {
            Bitmap mask = src.extractAlpha();
        Matrix id = new Matrix();

        Matrix m = new Matrix();
        float xScale = STROKE_WIDTH*2/(src.getWidth());
        float yScale = STROKE_WIDTH*2/(src.getHeight());
        m.preScale(1+xScale, 1+yScale, src.getWidth()/2, src.getHeight()/2);
        dest.drawBitmap(mask, m, sHolographicPaint);
            dest.drawColor(0, PorterDuff.Mode.CLEAR);

            float xScale = STROKE_WIDTH*2/(dest.getWidth());
            float yScale = STROKE_WIDTH*2/(dest.getHeight());

        dest.drawBitmap(src, id, sErasePaint);
            scaleMatrix.reset();
            scaleMatrix.preScale(1+xScale, 1+yScale, dest.getWidth()/2, dest.getHeight()/2);
            dest.setMatrix(id);
            dest.drawBitmap(mask, scaleMatrix, mHolographicPaint);
            dest.drawBitmap(mask, id, mErasePaint);
            mask.recycle();
        }
    }
}