samedi 9 mai 2015

How to make a View height bigger than screen in a Custom Layout class (for sliding up panel purpose)

I'm using this tutorial to implement a Sliding Up panel behaviour: FLAVIEN LAURENT. I'm not using any of the existing libraries 'cause I want two specific features:

  • The bottom from the view that I'll pull up is a MP Control Panel and the play button is a FAB. I need to make the top of the layout transparent, so only the FAB will appear;
  • When I pull up the Control Panel, I want to hide it behind the screen's top margin;

My problem is: as I slide it up beyound the screen top's border, the exact size of the panel is left transparent at the bottom of the View expanded. How can I make it goes until the end of the screen?

http://ift.tt/1cCDZlQ

Here is the problem! Transparent part that shows the bottom view after sliding up the panel

My Custom Layout class is like that:

package br.com.materialdesigntest.commons.util.layouts;

import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;

import br.com.materialdesigntest.R;
import br.com.materialdesigntest.commons.util.ImageFileUtil;

public class DraggableViewGroup extends RelativeLayout {
    private Integer CONTROL_PANEL_SIZE;

    private final ViewDragHelper mDragHelper;

    private View mView;
    private View mDraggableView;
    private View mExtendedPlayer;
    private View mPlayButton;
    private View mExtendedPlayerHolder;

    private float mInitialMotionX;
    private float mInitialMotionY;

    private int mDragRange;
    private int mTop;
    private float mDragOffset;
    private Context mContext;
    private Boolean started = Boolean.FALSE;

    // CONSTRUCTORS_________________________________________________________________________________
    public DraggableViewGroup(Context context) {
        this(context, null);
        mContext = context;
    }

    public DraggableViewGroup(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        mContext = context;
    }

    public DraggableViewGroup(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        CONTROL_PANEL_SIZE = (int) ImageFileUtil.convertDpToPixel(97, context);
        mDragHelper = ViewDragHelper.create(this, 1f, new DragHelperCallback());
        mContext = context;
    }
    // CALLBACKS____________________________________________________________________________________

    /**
     * Inflates the two views being animated
     */
    @Override
    protected void onFinishInflate() {
        mView = findViewById(R.id.viewHeader);
        mDraggableView = findViewById(R.id.current_cover_art);
        mExtendedPlayer = findViewById(R.id.ep_extended_player);
        mPlayButton = findViewById(R.id.options_fab_button);
    }

    /**
     * Called by a parent to request that a child update its values for mScrollX and mScrollY if necessary.
     */
    @Override
    public void computeScroll() {
        // Move the captured settling view by the appropriate amount for the current time.
        // If continueSettling returns true, the caller should call it again on the next frame to continue.
        // true if state callbacks should be deferred via posted message. Set this to true if you are
        // calling this method from computeScroll() or similar methods invoked as part of layout or drawing.
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    /**
     * If a vertical movement happened or if headerView was touched, intercepts the event, passing it
     * to onTouchEvent method
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = MotionEventCompat.getActionMasked(ev);

        // First user touch, ViewGroups doesn't care for that
        if ((action != MotionEvent.ACTION_DOWN)) {
            mDragHelper.cancel();
            return super.onInterceptTouchEvent(ev);
        }

        // User leaving the screen
        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            mDragHelper.cancel();
            return false;
        }

        final float x = ev.getX();
        final float y = ev.getY();

        boolean interceptTap = false;

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mInitialMotionX = x;
                mInitialMotionY = y;
                // Determine if the supplied view is under the given point in the parent view's coordinate system.
                interceptTap = mDragHelper.isViewUnder(mDraggableView, (int) x, (int) y)
                               || mDragHelper.isViewUnder(mExtendedPlayer, (int) x, (int) y);
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                final float adx = Math.abs(x - mInitialMotionX);
                final float ady = Math.abs(y - mInitialMotionY);

                // Threshold that determines if a gesture happened
                final int slop = mDragHelper.getTouchSlop();

                // A movement has happened in the X position: don't care for it
                if (ady > slop && adx > ady) {
                    mDragHelper.cancel();
                    return false;
                }
            }
        }

        Boolean answer = mDragHelper.shouldInterceptTouchEvent(ev) || interceptTap;

        if (!mDragHelper.isViewUnder(mPlayButton, (int) x, (int) y)) {
            answer = Boolean.FALSE;
        }

        // Returns the decision to intercept or not the event. As implemented, it should intercept only
        // id headerView was touched or a vertical movement has happened
        return answer;
    }

    /**
     * Records the initial points where the movement started. Checks if the movements triggers the
     * threashold. IF YES AND HEADERVIEW IS BEING HIT THEN CHECKS MOVEMENT IS DIFERENT OF TAPPING THEN
     * MAXIMIZES DESCVIEW, OTHERWISE MINIMIZES IT
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // Process a touch event received by the parent view. This method will dispatch callback
        // events as needed before returning. The parent view's onTouchEvent implementation should call this.
        mDragHelper.processTouchEvent(ev);

        final int action = ev.getAction();

        final float x = ev.getX();
        final float y = ev.getY();

        switch (action & MotionEventCompat.ACTION_MASK) {
            // Gets the point where the movement started from (user first touched the screen)
            case MotionEvent.ACTION_DOWN: {
                mInitialMotionX = x;
                mInitialMotionY = y;

                break;
            }

            // When user leaves the screens, checks if it was a tap or movement. If the first has happened
            // then maximizes the descView, otherwise minimize it
            case MotionEvent.ACTION_UP: {
                final float dx = x - mInitialMotionX;
                final float dy = y - mInitialMotionY;
                final int slop = mDragHelper.getTouchSlop();

                // uses points distance mathematics formula to determinar if the movement triggers the
                // threshold. IF NOT (it means it was just a tap), minimizes it, otherwise maximize it
                if (dx * dx + dy * dy < slop * slop) {
                    if (mDragOffset == 0) {
                        minimize();
                    } else {
                        maximize();
                    }
                }
                break;
            }
        }

        Boolean answer = isViewHit(mDraggableView, (int) x, (int) y)
                         || isViewHit(mExtendedPlayer, (int) x, (int) y);

        // Decides to continue receiving the events if headerView is under user finger and headerView or descView where hit by a gesture
        return answer;
    }

    /**
     * Measure the view and its content to determine the measured width and the measured height.
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
        int maxHeight = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
                resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mDragRange = getHeight() - CONTROL_PANEL_SIZE;

        if (!started) {
            started = Boolean.TRUE;
            mTop = getHeight() - CONTROL_PANEL_SIZE;
        }

        System.out.println("b == " + b);

        mView.layout(
                l,
                mTop,
                r,
                b);
    }


    // UTIL METHOODS________________________________________________________________________________
    public void maximize() {
        final int topBound = getPaddingTop() - CONTROL_PANEL_SIZE;
        int y = (int) (topBound);

        // Animate the given view to the given left and top
        if (mDragHelper.smoothSlideViewTo(mView, mView.getLeft(), y)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    public void minimize() {
        final int topBound = getPaddingTop();
        int y = (int) (topBound + 1 * mDragRange);

        // Animate the given view to the given left and top
        if (mDragHelper.smoothSlideViewTo(mView, mView.getLeft(), y)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    /**
     * Checks if the given view was subject of a tapping gesture
     *
     * @param view
     * @param x
     * @param y
     * @return
     */
    private boolean isViewHit(View view, int x, int y) {
        int[] viewLocation = new int[2];
        view.getLocationOnScreen(viewLocation);

        int[] parentLocation = new int[2];
        this.getLocationOnScreen(parentLocation);

        int screenX = parentLocation[0] + x;
        int screenY = parentLocation[1] + y;

        return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&
                screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();
    }

    // NESTED CLASSES/INTERFACES____________________________________________________________________
    private class DragHelperCallback extends ViewDragHelper.Callback {

        /**
         * Sets which view will be draggable
         *
         * @param child
         * @param pointerId
         * @return
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child == mView;
        }

        /**
         * Listener that watches for headerView positions changes, dimmes the descView based on
         * headerView top position
         *
         * @param changedView
         * @param left
         * @param top
         * @param dx
         * @param dy
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            mTop = top;
            mDragOffset = (float) top / mDragRange;
            requestLayout();
        }

        /**
         * Returns the range that a child view can move (up or down)
         * In this class, it will return the Y range possible to move inside the whole layout
         * that wrappers the two present views
         *
         * @param child
         * @return
         */
        @Override
        public int getViewVerticalDragRange(View child) {
            return mDragRange;
        }

        /**
         * When user releases the finger from headerView. If he left the screen below half of it
         * this method tells the app to minimize descView, otherwise maximize it
         *
         * @param releasedChild
         * @param xvel
         * @param yvel
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            if (yvel < 0) {
                maximize();
            } else {
                minimize();
            }
        }

        /**
         * Determines the vertical bounds to which the views will be allowed to move
         *
         * @param child
         * @param top
         * @param dy
         * @return
         */
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            final int topBound = getPaddingTop() - CONTROL_PANEL_SIZE;
            final int bottomBound = getHeight() - mView.getPaddingBottom();
            final int newTop = Math.min(Math.max(top, topBound), bottomBound);

            return newTop;
        }

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            final int leftBound = getPaddingLeft();
            final int rightBound = getWidth() - mView.getWidth();
            final int newLeft = Math.min(Math.max(left, leftBound), rightBound);

            return newLeft;
        }
    }
}

Aucun commentaire:

Enregistrer un commentaire