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?
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