天天看點

自定義抽屜控件

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