/*
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.
The Original Code is collection of files collectively known as Open Camera.
The Initial Developer of the Original Code is Almalence Inc.
Portions created by Initial Developer are Copyright (C) 2013
by Almalence Inc. All Rights Reserved.
*/
package com.almalence.ui;
/* <!-- +++
import com.almalence.opencam_plus.MainScreen;
import com.almalence.opencam_plus.R;
+++ --> */
// <!-- -+-
import com.almalence.opencam.MainScreen;
import com.almalence.opencam.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.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.GridView;
import android.widget.LinearLayout;
/***
* Panel - implements sliding panel
***/
public class Panel extends LinearLayout
{
/**
* Callback invoked when the panel is opened/closed.
*/
public static interface OnPanelListener
{
/**
* Invoked when the panel becomes fully closed.
*/
public void onPanelClosed(Panel panel);
/**
* Invoked when the panel becomes fully opened.
*/
public void onPanelOpened(Panel panel);
}
private boolean mIsShrinking;
private final int mPosition;
private final int mDuration;
private final float downSpace;
private final boolean mLinearFlying;
private View mHandle;
private View mContent;
private final Drawable mOpenedHandle;
private boolean mOpened;
private boolean startScrolling = false;
private boolean firstTime = true;
private boolean toTheTop = false;
private boolean locationTop = false;
private int moving = 0;
private boolean handle = true;
private final Drawable mClosedHandle;
private float mTrackX;
private float mTrackY;
private float mVelocity;
private boolean outsideControl = false;
private OnPanelListener panelListener;
private static final int TOP = 0;
private static final int BOTTOM = 1;
private static final int LEFT = 2;
// private 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 Panel(Context context, AttributeSet attrs)
{
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Panel);
mDuration = a.getInteger(R.styleable.Panel_animationDuration, 750); // duration
mPosition = a.getInteger(R.styleable.Panel_position, BOTTOM); // position
downSpace = a.getDimension(R.styleable.Panel_downSpace, 60);
mLinearFlying = a.getBoolean(R.styleable.Panel_linearFlying, false); // linearFlying
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);
}
/**
* Sets the listener that receives a notification when the panel becomes
* open/close.
*
* @param onPanelListener
* The listener to be notified when the panel is opened/closed.
*/
public void setOnPanelListener(OnPanelListener onPanelListener)
{
panelListener = onPanelListener;
}
/**
* Gets Panel's mHandle
*
* @return Panel's mHandle
*/
public View getHandle()
{
return mHandle;
}
/**
* Gets Panel's mContent
*
* @return Panel's mContent
*/
public View getContent()
{
return mContent;
}
/**
* Sets the acceleration curve for panel's animation.
*
* @param i
* The interpolator which defines the acceleration curve
*/
public void setInterpolator(Interpolator i)
{
mInterpolator = i;
}
/**
* Set the opened state of Panel.
*
* @param open
* True if Panel is to be opened, false if Panel is to be closed.
* @param animate
* True if use animation, false otherwise.
*
*/
public void setOpen(boolean open, boolean animate)
{
if (isOpen() ^ open)
{
mIsShrinking = !open;
if (animate)
{
mState = State.ABOUT_TO_ANIMATE;
if (!mIsShrinking)
{
// this could make flicker so we test mState in
// dispatchDraw()
// to see if is equal to ABOUT_TO_ANIMATE
mContent.setVisibility(VISIBLE);
}
post(startAnimation);
} else
{
mContent.setVisibility(open ? VISIBLE : GONE);
postProcess();
}
}
if (!open)
{
startScrolling = false;
firstTime = true;
}
}
/**
* Returns the opened status for Panel.
*
* @return True if Panel is opened, false otherwise.
*/
public boolean isOpen()
{
return mContent.getVisibility() == VISIBLE;
}
private void SetOnTouchListener(ViewGroup vg, OnTouchListener lst)
{
for (int i = 0; i < vg.getChildCount(); i++)
{
View child = vg.getChildAt(i);
if (child instanceof GridView)
{
(child).setOnTouchListener(lst);
} else if (child instanceof ViewGroup)
{
SetOnTouchListener((ViewGroup) child, lst);
}
}
}
@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'");
mContent.setOnTouchListener(touchListener);
SetOnTouchListener((ViewGroup) mContent, touchListener);
// reposition children
removeView(mHandle);
removeView(mContent);
if (mPosition == TOP || mPosition == LEFT)
{
addView(mContent);
addView(mHandle);
} else
{
addView(mHandle);
addView(mContent);
}
if (mClosedHandle != null)
{
mHandle.setBackgroundDrawable(mClosedHandle);
mOpened = false;
}
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);
mContent.getBackground().setAlpha((int) (255 - 255 * Math.abs(mTrackY / mContentHeight)));
}
super.dispatchDraw(canvas);
}
private float ensureRange(float v, int min, int max)
{
v = Math.max(v, min);
v = Math.min(v, max);
return v;
}
public OnTouchListener touchListener = new OnTouchListener()
{
int initX;
int initY;
boolean setInitialPosition;
public boolean onTouch(View v, MotionEvent event)
{
// if controls
// locked - skip
// any events
if (MainScreen.getGUIManager().lockControls)
return false;
int action = event.getAction();
if (v == MainScreen.getPreviewSurfaceView()
|| v == ((View) MainScreen.getInstance()
.findViewById(R.id.mainLayout1))
|| v.getParent() == (View) MainScreen
.getInstance().findViewById(
R.id.paramsLayout))
{
if (!mOpened)
{
handle = false;
if (action == MotionEvent.ACTION_DOWN)
{
if (event.getRawY() > ((20 + (toTheTop ? 0
: 65)) * MainScreen
.getMainContext().getResources()
.getDisplayMetrics().density))
return false;
else
startScrolling = true;
}
if (action == MotionEvent.ACTION_MOVE)
{
if (!startScrolling)
return false;
if (event.getY() < ((toTheTop ? 0 : 65) * MainScreen
.getMainContext().getResources()
.getDisplayMetrics().density))
return false;
reorder(true, false);
}
if ((action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP)
&& !startScrolling)
return false;
outsideControl = false;
} else
{
if ((event.getY() > (mContentHeight + 30))
&& (action != MotionEvent.ACTION_DOWN)
&& (action != MotionEvent.ACTION_UP))
return false;
outsideControl = true;
}
} else
{
handle = true;
outsideControl = false;
}
if (action == MotionEvent.ACTION_DOWN)
{
initX = 0;
initY = 0;
if (mContent.getVisibility() == GONE)
{
mContent.getBackground().setAlpha(0);
// since
// we
// may
// not
// know
// content
// dimensions
// we
// use
// factors
// here
if (mOrientation == VERTICAL)
{
initY = mPosition == TOP ? -1 : 1;
} else
{
initX = mPosition == LEFT ? -1 : 1;
}
}
setInitialPosition = true;
} else
{
if (setInitialPosition)
{
// now
// we
// know
// content
// dimensions,
// so we
// multiply
// factors...
initX *= mContentWidth;
initY *= mContentHeight;
// ...
// and
// set
// initial
// panel's
// position
mGestureListener.setScroll(initX, initY);
setInitialPosition = false;
// for
// offsetLocation
// we
// have
// to
// invert
// values
initX = -initX;
initY = -initY;
}
// offset
// every
// ACTION_MOVE
// &
// ACTION_UP
// event
event.offsetLocation(initX, initY);
}
if (!mGestureDetector.onTouchEvent(event))
{
if (action == MotionEvent.ACTION_UP)
{
// tup
// up
// after
// scrolling
post(startAnimation);
}
}
return false;
}
};
Runnable startAnimation = new Runnable()
{
public void run()
{
// this is why
// we post this
// Runnable
// couple of
// lines above:
// now its save
// to use
// mContent.getHeight()
// &&
// mContent.getWidth()
TranslateAnimation animation;
int fromXDelta = 0, toXDelta = 0, fromYDelta = 0, toYDelta = 0;
if (mState == State.FLYING)
{
mIsShrinking = (mPosition == TOP || mPosition == LEFT)
^ (mVelocity > 0);
}
if (mContentHeight == 0 || mContentWidth == 0)
return;
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;
}
// for
// FLYING
// events we
// calculate
// animation
// duration
// based on
// flying
// velocity
// also for
// very high
// velocity
// make sure
// duration
// >= 20 ms
if (mState == State.FLYING && mLinearFlying)
{
calculatedDuration = mDuration
* Math.abs(toYDelta - fromYDelta)
/ mContentHeight;
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;
}
// for
// FLYING
// events we
// calculate
// animation
// duration
// based on
// flying
// velocity
// also for
// very high
// velocity
// make sure
// duration
// >= 20 ms
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)
{
moving = 0;
reorder(locationTop, false);
mHandle.setBackgroundDrawable(mClosedHandle);
mOpened = false;
startScrolling = false;
} else if (!mIsShrinking && mOpenedHandle != null)
{
mOpened = true;
startScrolling = false;
firstTime = true;
mContent.getBackground().setAlpha(255);
mHandle.setBackgroundDrawable(mOpenedHandle);
}
// invoke listener if any
if (panelListener != null)
{
if (mIsShrinking)
{
panelListener.onPanelClosed(Panel.this);
} else
{
panelListener.onPanelOpened(Panel.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)
{
// we are animating or just about to animate
return false;
}
mState = State.ABOUT_TO_ANIMATE;
mIsShrinking = mContent.getVisibility() == VISIBLE;
if (!mIsShrinking)
{
// this could make flicker so we test mState in dispatchDraw()
// to see if is equal to ABOUT_TO_ANIMATE
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)
{
if (startScrolling && firstTime)
{
distanceY += mContentHeight;
firstTime = false;
}
mState = State.TRACKING;
float tmpY = 0, tmpX = 0;
if (outsideControl && e2.getY() > (mContentHeight))
return true;
if (scrollY > -450)
reorder(true, false);
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;
moving++;
invalidate();
}
return true;
}
public void onShowPress(MotionEvent e)
{
}
public boolean onSingleTapUp(MotionEvent e)
{
// simple tap: click
post(startAnimation);
return true;
}
}
// corrects margin between content and handler
public void reorder(boolean toTop, boolean isFromGUI)
{
if (isFromGUI)
locationTop = toTop;
toTheTop = toTop;
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) this.findViewById(R.id.panelHandle)
.getLayoutParams();
float d = MainScreen.getMainContext().getResources().getDisplayMetrics().density;
if (toTheTop)
{
if (!isFromGUI && (moving != 0) && !locationTop && handle)
{
moving = moving + 5;
int margin = (int) (downSpace - moving);
if (margin < 0)
margin = 0;
lp.topMargin = margin;
} else
lp.topMargin = 0;
} else
{
lp.topMargin = (int) (downSpace);
}
mHandle.setLayoutParams(lp);
}
}