android中實作螢幕切換 1)activity切換 2 ) ViewGroup實作同一activity中不同view的切換。同時類似solidingDrawer在一個View中模拟出多個View之間得切換。主要代碼是在Panel.java中
Panel.java
package com.yht.imitateDrawer;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Matrix;
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.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.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
// pro1 ??? 改變抽屜把手的位置
// ??? 從按下然後拖動的的整個過程是怎麼樣的,怎麼形成動畫的。
/*展開
* 向handle的ACTION_DOWN按下,擷取滾動的因子,将content變為激活狀态。layout()會重新執行。
* 其它動作繼續,在第一個動作中擷取X,Y方向上的初始化滾動。
* 更新offsetLocaltion()
* 發生OnScroll()動作,更新mTranceX,然後調用更新UI
*/
public class Panel extends LinearLayout {
private static final String TAG = "Panel";
/**
* 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 int mPosition;
private int mDuration;
private boolean mLinearFlying;
private View mHandle;
private View mContent;
private Drawable mOpenedHandle;
private Drawable mClosedHandle;
private float mTrackX; // ???() X方向上的隐藏追蹤
private float mTrackY; // ???() Y方向上的隐藏跟蹤
private float mVelocity; // ???() Fling狀态每秒鐘劃的像素。
private Activity mActivity = null;
private OnPanelListener panelListener;
public static final int TOP = 0;
public static final int BOTTOM = 1;
public static final int LEFT = 2;
public static final int RIGHT = 3;
private enum State {
ABOUT_TO_ANIMATE, // 由READY進入這個狀态,但還沒有發生動畫,滾動,和滑動
ANIMATING, //正在動畫
READY, //就緒狀态什麼都不做
TRACKING, // 拖動
FLYING,
};
private boolean mIsContentHide = true; //
private State mState;
private Interpolator mInterpolator;
private GestureDetector mGestureDetector;//手勢監聽器
private PanelOnGestureListener mGestureListener; //手勢監聽器的回調
private int mContentHeight;
private int mContentWidth;
private int mOrientation; //這個方向是動畫出現的方向。可以修改mPositon來修改實驗一下
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 defaults to 750 ms
mPosition = a.getInteger(R.styleable.Panel_position, BOTTOM); // position defaults to BOTTOM
mLinearFlying = a.getBoolean(R.styleable.Panel_linearFlying, false); // linearFlying defaults to 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); //長按沒有傳回值是以去掉
}
/**
* 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;
}
/**
* Returns the opened status for Panel.
*
* @return True if Panel is opened, false otherwise.
*/
public boolean isOpen() {
return mContent.getVisibility() == VISIBLE;
}
@Override
/*
* 在這裡完成了一些handler與Content的初始化工作
*/
protected void onFinishInflate() {
// 調用超類方法
super.onFinishInflate();
// 擷取handle引用設定Handle的監聽器
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);
// 擷取Content的引用
mContent = findViewById(R.id.panelContent);
if (mContent == null) {
throw new RuntimeException("Your Panel must have a View whose id attribute is 'R.id.panelContent'");
}
ImageButton imgBtn = (ImageButton) findViewById(R.id.receive_box_pull_up_id);
imgBtn.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View arg0, MotionEvent ev) {
if(!isContentClosed()){
mHandle.dispatchTouchEvent(ev);
}
return true;
}
});
// reposition children
removeView(mHandle);
removeView(mContent);
if (mPosition == TOP || mPosition == LEFT) {
addView(mContent);
addView(mHandle);
} else {
addView(mHandle);
addView(mContent);
}
// 設定handle的背景
if (mClosedHandle != null) {
// mHandle.setBackgroundDrawable(mClosedHandle);
}
// 隐藏内容
mContent.setVisibility(GONE);
}
@Override
/*
* 主要是擷取了Content的寬高
*/
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) {
// String name = getResources().getResourceEntryName(getId());
// Log.d(TAG, name + " ispatchDraw " + mState);
// this is why 'mState' was added:
// avoid flicker before animation start
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); // ???() translate(float,float)的作用。将控件的起始坐标平移到(0,delta)的位置
} else {
canvas.translate(delta, 0); // ???() 這裡平移到了delta,0的位置,但是等控件完全展開後如果還是這個矩陣的話,那Content應該是看不到,現在能
//看到,那在哪裡轉化了呢。動畫完了就還原
}
}
if (mState == State.TRACKING || mState == State.FLYING) {
canvas.translate(mTrackX, mTrackY);
/*
* 激活狀态的Content和handler以mTranceX和mTranckY為參數不斷的移動就能形成動畫
*/
// ??? translate會影響onLayout嗎。
/*
* 不會,translate隻能使view看起來發生了變化,但layout(),不會被調用
* 也就是,外部的處罰事件還是會按照translate以前的标準觸發。
*/
// ??? translate和MotionEvent什麼關系
/*
* 沒什麼關系,translate不會改變架構中事件的判斷。
*/
}
super.dispatchDraw(canvas);
}
private float ensureRange(float v, int min, int max) {
v = Math.max(v, min);
v = Math.min(v, max);
return v;
}
OnTouchListener touchListener = new OnTouchListener() {
int initX; //
int initY;
boolean setInitialPosition; //判斷是否設定了初始滾動位置 它是類的成員函數
public boolean onTouch(View v, MotionEvent event) {
// Log.d(TAG, "state: " + mState + " x: " + event.getX() + " y: " + event.getY());
int action = event.getAction();
// if 使用者向下按
// Content的X方向和Y方向滾動都為0
// if 内容為隐藏狀态 根據方向和位置修改X方向或Y方向上的滾動
if (action == MotionEvent.ACTION_DOWN) {
initX = 0;
initY = 0;
if (mContent.getVisibility() == GONE) {
// 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;
}
}
// 設定初始化的記錄為true
setInitialPosition = true;
} else {
// if 使用者按下的不是向下
// if 已經設定了初始滾動位置
// 修改内容X,Y方向上的滾動。
// 設定控件滾動的初始化位置
// 設定初始化的記錄為false
// ???() 為什麼要轉換
/*
*ACTION_DOWN的操作都是根據handle的,Content激活後
*重新layout,handle的位置發生了改變,而以後所有的操作
*的坐标又都是根據現在的handler的位置的,這樣就存在了誤差
*所有需要offsetLocation把這個誤差修正過來。
*/
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
// 如果這裡去掉,開始拖動不響應,拖動了一段距離後才能拉出Content
event.offsetLocation(initX, initY); // ???() 在X,Y的方向上分别加上initX,initY。
// ???() 為什麼會對scrollX産生那麼大的影響呢
/*
* 不加這一句會使distanceX的值差異非常大,而distanceX會影響到mTranceX。
*/
// ???()為什麼巨大的負的mTranceX沒有把handler給變沒了。
/*
* mTranceX是有範圍的,如果最小值小于這個範圍就不會在發生該變了,所有
* 會出現延遲的現象。其實那是還沒有到達最小值。
*/
}
// if 手勢監聽器沒有處理
// if 使用者事件為擡起事件 啟動動畫線程
boolean result = mGestureDetector.onTouchEvent(event);
if (!result) {
if (action == MotionEvent.ACTION_UP) {
// tup up after scrolling
post(startAnimation); // ??? 添加一個
}
}
return false;
}
};
/**
* 處理背景的drawable
*/
private void postProcess() {
if (mIsShrinking && mClosedHandle != null) {
// mHandle.setBackgroundDrawable(mClosedHandle);
} else
if (!mIsShrinking && mOpenedHandle != null) {
// 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) {
// not used
}
// ???() onScroll做了什麼事
/*
* 該變mTranceX和mTranceY,更新UI
*/
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) {
// not used
}
public boolean onSingleTapUp(MotionEvent e) {
// simple tap: click
post(startAnimation);
return true;
}
}
private 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;
}
};
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; // ???() 動畫的起始、終點坐标。相對于器parent View
// if 控件狀态為Fling
// 判斷是否處于收縮狀态
if (mState == State.FLYING) {
mIsShrinking = (mPosition == TOP || mPosition == LEFT) ^ (mVelocity > 0);
}
int calculatedDuration;
// if 控件為縱向
// 擷取Y方向Delta ???
// if 控件是拖動
// ???
// 計算周期時間 ??? 為什麼分為Flying與非Flying兩種狀态
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 = (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;
}
// 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 計算周期為0
// ???() toXDelta跟fromXDelta相等
if (calculatedDuration == 0) {
mState = State.READY;
setContentClose(false);
if (mIsShrinking) {
mContent.setVisibility(GONE);
setContentClose(true);
}
postProcess();
return;
}
// ???() TranslateAnimation怎麼知道移動的是哪個view
/*
* TranslateAnimation的所有屬性都會作用在啟動了anmation的View上。
*/
if(mOrientation == VERTICAL){
if(fromYDelta > toYDelta){
setContentClose(true);
}else{
setContentClose(false);
}
}else{
if(fromXDelta > toXDelta){
setContentClose(true);
}else{
setContentClose(false);
}
}
animation = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);
animation.setDuration(calculatedDuration);
animation.setAnimationListener(animationListener);
// ??? setInterpolator()什麼意思
if (mState == State.FLYING && mLinearFlying) {
animation.setInterpolator(new LinearInterpolator());
} else
if (mInterpolator != null) {
animation.setInterpolator(mInterpolator);
}
startAnimation(animation);
}
};
/**
*
* @return wheather Content is hide;
*/
private boolean isContentClosed(){
return mIsContentHide;
}
private void setContentClose(boolean flag){
mIsContentHide = flag;
}
}
在一個View中模拟抽屜主要是運用使用 android.graphics.Canvas .translate(float dx, float dy)和View的visibility statu,其實在拖動的整個過程,布局是不會改變的,隻是看起來好像改變的。
代碼下載下傳位址:http://download.csdn.net/source/3345404