package com.justsystems.hpb.pad;
import android.content.Context;
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.View.OnTouchListener;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.justsystems.hpb.pad.util.Debug;
/**
* 上から開くSlidingDrawerのようなView。<br>
* 2.3以下ではViewのorientation属性に対応していないため、独自に実装した。
*
*/
public class SlidingView extends LinearLayout implements AbsSlidingView,
AnimationListener, OnTouchListener, OnGestureListener {
/** アニメーションの最大時間 */
private static final int MAX_ANIMATION_DURATION = 700;
/** 通常のアニメーションの時間 */
private static final int DEFAULT_ANIMATION_DURATION = 300;
/** アニメーションの最短時間 */
private static final int MIN_ANIMATION_DURATION = 200;
private OnStateChengeListener stateListener;
private ImageView handle;
private GestureDetector detector;
/** ハンドルの押下時アイコン */
private Drawable presed;
/** ハンドルの非押下時アイコン */
private Drawable notPresed;
private boolean isOpen;
public SlidingView(Context context, AttributeSet attrs) {
super(context, attrs);
this.presed = context.getResources().getDrawable(
R.drawable.ic_top_arrow_up_pressed);
this.notPresed = context.getResources().getDrawable(
R.drawable.ic_top_arrow_up_not_pressed);
this.detector = new GestureDetector(context, this);
setOnTouchListener(this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
this.handle = (ImageView) findViewById(R.id.handle);
this.handle.setOnTouchListener(this.touchListener);
}
public void open() {
offsetTopAndBottom(-getTop());
isOpen = true;
}
public void animateOpen() {
animateOpen(DEFAULT_ANIMATION_DURATION, true);
}
public void animateOpen(int duration) {
animateOpen(duration, false);
}
private void animateOpen(int duration, boolean calcOffset) {
if (isOpen) {
return;
}
Debug.logd("open duration" + duration + "calcOffset" + calcOffset);
final int top = getTop();
final int offset = top;
Debug.logd("offset" + offset);
offsetTopAndBottom(-top);
TranslateAnimation anim = new TranslateAnimation(0, 0, offset, 0);
anim.setAnimationListener(this);
duration = normalizeDuration(duration, Math.abs(offset), calcOffset);
Debug.logd("open duration" + duration);
anim.setDuration(duration);
startAnimation(anim);
}
public void close() {
offsetTopAndBottom(-getTop() - getHeight());
isOpen = false;
}
public void animateClose() {
animateClose(DEFAULT_ANIMATION_DURATION, true);
}
public void animateClose(int duration) {
animateClose(duration, false);
}
public void animateClose(int duration, boolean calcOffset) {
if (!isOpen) {
return;
}
Debug.logd("close duration" + duration + "calcOffset" + calcOffset);
final int offset = getTop();
Debug.logd("offset" + offset);
offsetTopAndBottom(-offset);
TranslateAnimation anim = new TranslateAnimation(0, 0, offset,
-getHeight());
anim.setAnimationListener(this);
duration = normalizeDuration(duration, Math.abs(getHeight() - offset),
calcOffset);
Debug.logd("close duration" + duration);
anim.setDuration(duration);
startAnimation(anim);
}
private int normalizeDuration(int duration, int distance, boolean calcOffset) {
if (calcOffset) {
return (int) (Math.max(MIN_ANIMATION_DURATION,
Math.min(MAX_ANIMATION_DURATION, duration))
* distance / (float) getHeight());
} else {
final float v = distance / (float) duration;
if (v < getHeight() / (float) MAX_ANIMATION_DURATION) {
return (int) (MAX_ANIMATION_DURATION * distance / (float) getHeight());
} else if (v > getHeight() / (float) MIN_ANIMATION_DURATION) {
return (int) (MIN_ANIMATION_DURATION * distance / (float) getHeight());
} else {
return Math.max(MIN_ANIMATION_DURATION,
Math.min(MAX_ANIMATION_DURATION, duration));
}
}
}
public boolean isOpened() {
return this.isOpen;
}
public void setOnStateChangeListener(OnStateChengeListener listener) {
this.stateListener = listener;
}
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
isOpen = !isOpen;
if (!isOpen) {
offsetTopAndBottom(-getTop() - getHeight());
if (this.stateListener != null) {
this.stateListener.onClose();
}
} else {
offsetTopAndBottom(-getTop());
if (this.stateListener != null) {
this.stateListener.onOpen();
}
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
animateClose();
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
final float difY = e2.getRawY() - e1.getRawY();
final float difX = e2.getX() - e1.getX();
if (difY < 0 && Math.abs(difY) > Math.abs(difX)) {
final float distY = Math.abs(difY);
final long time = e2.getEventTime() - e1.getEventTime();
final float restY = getHeight() - distY;
Debug.logd("restY" + restY + " time " + time + " distY" + distY);
final int animationDuration = (int) (time * restY / distY);
Debug.logd("animationDuration" + animationDuration);
animateClose(animationDuration);
return true;
}
return false;
}
private OnTouchListener touchListener = new OnTouchListener() {
private int downY;
@Override
public boolean onTouch(View v, MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
handle.setImageDrawable(presed);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
handle.setImageDrawable(notPresed);
break;
default:
break;
}
if (detector.onTouchEvent(event)) {
return true;
}
final int y = (int) event.getY();
final int offset = y - downY;
switch (action) {
case MotionEvent.ACTION_DOWN:
this.downY = y;
break;
case MotionEvent.ACTION_MOVE:
if (getBottom() + offset > getHeight()) {
offsetTopAndBottom(-getTop());
invalidate(0, -getTop(), getWidth(), getHeight() - getTop());
} else if (0 < getBottom() + offset) {
offsetTopAndBottom(offset);
invalidate(0, -getTop(), getWidth(), getHeight() - getTop());
}
break;
case MotionEvent.ACTION_UP:
if (getBottom() > getHeight() / 2) {
offsetTopAndBottom(-getTop());
invalidate();
} else {
animateClose();
}
this.downY = 0;
break;
default:
break;
}
return true;
}
};
}