/* * This file is part of Domodroid. * * Domodroid is Copyright (C) 2011 Pierre LAINE, Maxime CHOFARDET * * Domodroid is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * Domodroid is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * Domodroid. If not, see <http://www.gnu.org/licenses/>. */ package activities; import org.domogik.domodroid13.R; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.GestureDetector.OnGestureListener; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.TranslateAnimation; import android.view.animation.Animation.AnimationListener; import android.widget.LinearLayout; public class Sliding_Drawer extends LinearLayout { @SuppressWarnings("unused") private static final String TAG = "Panel"; public interface OnPanelListener { void onPanelClosed(Sliding_Drawer panel); void onPanelOpened(Sliding_Drawer panel); } private boolean mIsShrinking; private final int mPosition; private final int mDuration; private final boolean mLinearFlying; private View mHandle; private View mContent; private Drawable mOpenedHandle; private Drawable mClosedHandle; private float mTrackX; private float mTrackY; private float mVelocity; private OnPanelListener panelListener; private static final int TOP = 0; private static final int BOTTOM = 1; private static final int LEFT = 2; public static final int RIGHT = 3; private enum State { ABOUT_TO_ANIMATE, ANIMATING, READY, TRACKING, FLYING, } private State mState; private Interpolator mInterpolator; private final GestureDetector mGestureDetector; private int mContentHeight; private int mContentWidth; private final int mOrientation; private final PanelOnGestureListener mGestureListener; public Sliding_Drawer(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Panel); mDuration = a.getInteger(R.styleable.Panel_animationDuration, 750); mPosition = a.getInteger(R.styleable.Panel_position, BOTTOM); mLinearFlying = a.getBoolean(R.styleable.Panel_linearFlying, false); mOpenedHandle = a.getDrawable(R.styleable.Panel_openedHandle); mClosedHandle = a.getDrawable(R.styleable.Panel_closedHandle); a.recycle(); mOrientation = (mPosition == TOP || mPosition == BOTTOM) ? VERTICAL : HORIZONTAL; setOrientation(mOrientation); mState = State.READY; mGestureListener = new PanelOnGestureListener(); mGestureDetector = new GestureDetector(mGestureListener); mGestureDetector.setIsLongpressEnabled(false); } public void setOnPanelListener(OnPanelListener onPanelListener) { panelListener = onPanelListener; } public View getHandle() { return mHandle; } public View getContent() { return mContent; } public void setInterpolator(Interpolator i) { mInterpolator = i; } public void setOpen(boolean open, boolean animate) { if (isOpen() ^ open) { mIsShrinking = !open; if (animate) { mState = State.ABOUT_TO_ANIMATE; if (!mIsShrinking) { mContent.setVisibility(VISIBLE); } post(startAnimation); } else { mContent.setVisibility(open ? VISIBLE : GONE); postProcess(); } } } public boolean isOpen() { return mContent.getVisibility() == VISIBLE; } @Override protected void onFinishInflate() { super.onFinishInflate(); mHandle = findViewById(R.id.panelHandle); if (mHandle == null) { throw new RuntimeException("Your Panel must have a View whose id attribute is 'R.id.panelHandle'"); } mHandle.setOnTouchListener(touchListener); mContent = findViewById(R.id.panelContent); if (mContent == null) { throw new RuntimeException("Your Panel must have a View whose id attribute is 'R.id.panelContent'"); } removeView(mHandle); removeView(mContent); if (mPosition == TOP || mPosition == LEFT) { addView(mContent); addView(mHandle); } else { addView(mHandle); addView(mContent); } if (mClosedHandle != null) { mHandle.setBackgroundDrawable(mClosedHandle); } mContent.setVisibility(GONE); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mContentWidth = mContent.getWidth(); mContentHeight = mContent.getHeight(); } @Override protected void dispatchDraw(Canvas canvas) { if (mState == State.ABOUT_TO_ANIMATE && !mIsShrinking) { int delta = mOrientation == VERTICAL ? mContentHeight : mContentWidth; if (mPosition == LEFT || mPosition == TOP) { delta = -delta; } if (mOrientation == VERTICAL) { canvas.translate(0, delta); } else { canvas.translate(delta, 0); } } if (mState == State.TRACKING || mState == State.FLYING) { canvas.translate(mTrackX, mTrackY); } super.dispatchDraw(canvas); } private float ensureRange(float v, int min, int max) { v = Math.max(v, min); v = Math.min(v, max); return v; } private final OnTouchListener touchListener = new OnTouchListener() { int initX; int initY; boolean setInitialPosition; public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { initX = 0; initY = 0; if (mContent.getVisibility() == GONE) { if (mOrientation == VERTICAL) { initY = mPosition == TOP ? -1 : 1; } else { initX = mPosition == LEFT ? -1 : 1; } } setInitialPosition = true; } else { if (setInitialPosition) { initX *= mContentWidth; initY *= mContentHeight; mGestureListener.setScroll(initX, initY); setInitialPosition = false; initX = -initX; initY = -initY; } event.offsetLocation(initX, initY); } if (!mGestureDetector.onTouchEvent(event)) { if (action == MotionEvent.ACTION_UP) { post(startAnimation); } } return false; } }; private final Runnable startAnimation = new Runnable() { public void run() { TranslateAnimation animation; int fromXDelta = 0, toXDelta = 0, fromYDelta = 0, toYDelta = 0; if (mState == State.FLYING) { mIsShrinking = (mPosition == TOP || mPosition == LEFT) ^ (mVelocity > 0); } int calculatedDuration; if (mOrientation == VERTICAL) { int height = mContentHeight; if (!mIsShrinking) { fromYDelta = mPosition == TOP ? -height : height; } else { toYDelta = mPosition == TOP ? -height : height; } if (mState == State.TRACKING) { if (Math.abs(mTrackY - fromYDelta) < Math.abs(mTrackY - toYDelta)) { mIsShrinking = !mIsShrinking; toYDelta = fromYDelta; } fromYDelta = (int) mTrackY; } else if (mState == State.FLYING) { fromYDelta = (int) mTrackY; } if (mState == State.FLYING && mLinearFlying) { calculatedDuration = (int) (1000 * Math.abs((toYDelta - fromYDelta) / mVelocity)); calculatedDuration = Math.max(calculatedDuration, 20); } else { calculatedDuration = mDuration * Math.abs(toYDelta - fromYDelta) / mContentHeight; } } else { int width = mContentWidth; if (!mIsShrinking) { fromXDelta = mPosition == LEFT ? -width : width; } else { toXDelta = mPosition == LEFT ? -width : width; } if (mState == State.TRACKING) { if (Math.abs(mTrackX - fromXDelta) < Math.abs(mTrackX - toXDelta)) { mIsShrinking = !mIsShrinking; toXDelta = fromXDelta; } fromXDelta = (int) mTrackX; } else if (mState == State.FLYING) { fromXDelta = (int) mTrackX; } if (mState == State.FLYING && mLinearFlying) { calculatedDuration = (int) (1000 * Math.abs((toXDelta - fromXDelta) / mVelocity)); calculatedDuration = Math.max(calculatedDuration, 20); } else { calculatedDuration = mDuration * Math.abs(toXDelta - fromXDelta) / mContentWidth; } } mTrackX = mTrackY = 0; if (calculatedDuration == 0) { mState = State.READY; if (mIsShrinking) { mContent.setVisibility(GONE); } postProcess(); return; } animation = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta); animation.setDuration(calculatedDuration); animation.setAnimationListener(animationListener); if (mState == State.FLYING && mLinearFlying) { animation.setInterpolator(new LinearInterpolator()); } else if (mInterpolator != null) { animation.setInterpolator(mInterpolator); } startAnimation(animation); } }; private final AnimationListener animationListener = new AnimationListener() { public void onAnimationEnd(Animation animation) { mState = State.READY; if (mIsShrinking) { mContent.setVisibility(GONE); } postProcess(); } public void onAnimationRepeat(Animation animation) { } public void onAnimationStart(Animation animation) { mState = State.ANIMATING; } }; private void postProcess() { if (mIsShrinking && mClosedHandle != null) { mHandle.setBackgroundDrawable(mClosedHandle); } else if (!mIsShrinking && mOpenedHandle != null) { mHandle.setBackgroundDrawable(mOpenedHandle); } if (panelListener != null) { if (mIsShrinking) { panelListener.onPanelClosed(Sliding_Drawer.this); } else { panelListener.onPanelOpened(Sliding_Drawer.this); } } } class PanelOnGestureListener implements OnGestureListener { float scrollY; float scrollX; public void setScroll(int initScrollX, int initScrollY) { scrollX = initScrollX; scrollY = initScrollY; } public boolean onDown(MotionEvent e) { scrollX = scrollY = 0; if (mState != State.READY) { return false; } mState = State.ABOUT_TO_ANIMATE; mIsShrinking = mContent.getVisibility() == VISIBLE; if (!mIsShrinking) { mContent.setVisibility(VISIBLE); } return true; } public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { mState = State.FLYING; mVelocity = mOrientation == VERTICAL ? velocityY : velocityX; post(startAnimation); return true; } public void onLongPress(MotionEvent e) { } public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { mState = State.TRACKING; float tmpY = 0, tmpX = 0; if (mOrientation == VERTICAL) { scrollY -= distanceY; if (mPosition == TOP) { tmpY = ensureRange(scrollY, -mContentHeight, 0); } else { tmpY = ensureRange(scrollY, 0, mContentHeight); } } else { scrollX -= distanceX; if (mPosition == LEFT) { tmpX = ensureRange(scrollX, -mContentWidth, 0); } else { tmpX = ensureRange(scrollX, 0, mContentWidth); } } if (tmpX != mTrackX || tmpY != mTrackY) { mTrackX = tmpX; mTrackY = tmpY; invalidate(); } return true; } public void onShowPress(MotionEvent e) { } public boolean onSingleTapUp(MotionEvent e) { post(startAnimation); return true; } } public Drawable getmOpenedHandle() { return mOpenedHandle; } public void setmOpenedHandle(Drawable mOpenedHandle) { this.mOpenedHandle = mOpenedHandle; } public Drawable getmClosedHandle() { return mClosedHandle; } public void setmClosedHandle(Drawable mClosedHandle) { this.mClosedHandle = mClosedHandle; } }