package android.widget;
import android.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
public class SlidingDrawer extends ViewGroup {
public static final int ORIENTATION_HORIZONTAL = 0;//水平方向
public static final int ORIENTATION_VERTICAL = 1;//垂直方向
private static final int TAP_THRESHOLD = 6;//敲击临界点
private static final float MAXIMUM_TAP_VELOCITY = 100.0f;//最大敲击速度
private static final float MAXIMUM_MINOR_VELOCITY = 150.0f;//最大次速度
private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f;//最大主速度
private static final float MAXIMUM_ACCELERATION = 2000.0f;//最大加速度
private static final int VELOCITY_UNITS = 1000;//最小速度(速度单元)
private static final int MSG_ANIMATE = 1000;//消息动画
private static final int ANIMATION_FRAME_DURATION = 1000 / 60;//动画框架持续时间
private static final int EXPANDED_FULL_OPEN = -10001;//展开
private static final int COLLAPSED_FULL_CLOSED = -10002;//缩回去
private final int mHandleId;//把手ID
private final int mContentId;//内容ID
//成员变量
private View mHandle;//把手
private View mContent;//内容
private final Rect mFrame = new Rect();//唯一框架矩形引用
private final Rect mInvalidate = new Rect();//唯一无效矩形引用
private boolean mTracking;//是否正在跟踪
private boolean mLocked;//是否已被锁上
private VelocityTracker mVelocityTracker;//速度追踪器
private boolean mVertical;//是否垂直
private boolean mExpanded;//是否已经被扩展
private int mBottomOffset;//底部偏移量
private int mTopOffset;//顶部偏移量
private int mHandleHeight;//把手高度
private int mHandleWidth;//把手宽度
//三个抽屉状态监听器
private OnDrawerOpenListener mOnDrawerOpenListener;//打开时
private OnDrawerCloseListener mOnDrawerCloseListener;//关闭时
private OnDrawerScrollListener mOnDrawerScrollListener;//滚动时
private final Handler mHandler = new SlidingHandler();//唯一的一个handler---mHandler
private float mAnimatedAcceleration;//动画加速度
private float mAnimatedVelocity;//动画速度
private float mAnimationPosition;//动画位置
private long mAnimationLastTime;//动画最后时间
private long mCurrentAnimationTime;//动画当前时间
private int mTouchDelta;//触摸次数
private boolean mAnimating;//动画是否正在进行
private boolean mAllowSingleTap;//是否允许单独一次敲击
private boolean mAnimateOnClick;//是否点击了动画-----------------------------------------------------------可能是这儿
private final int mTapThreshold;//敲击边界值
private final int mMaximumTapVelocity;//最大敲击速度
private final int mMaximumMinorVelocity;//最大次速度
private final int mMaximumMajorVelocity;//最大主速度
private final int mMaximumAcceleration;//最大加速度
private final int mVelocityUnits;//最小速度(速度单元)
public static interface OnDrawerOpenListener {
public void onDrawerOpened();
}
public static interface OnDrawerCloseListener {
public void onDrawerClosed();
}
public static interface OnDrawerScrollListener {
public void onScrollStarted();
public void onScrollEnded();
}
public SlidingDrawer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingDrawer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingDrawer, defStyle, 0);
int orientation = a.getInt(R.styleable.SlidingDrawer_orientation, ORIENTATION_VERTICAL);
mVertical = orientation == ORIENTATION_VERTICAL;
mBottomOffset = (int) a.getDimension(R.styleable.SlidingDrawer_bottomOffset, 0.0f);
mTopOffset = (int) a.getDimension(R.styleable.SlidingDrawer_topOffset, 0.0f);
mAllowSingleTap = a.getBoolean(R.styleable.SlidingDrawer_allowSingleTap, true);
mAnimateOnClick = a.getBoolean(R.styleable.SlidingDrawer_animateOnClick, true);
int handleId = a.getResourceId(R.styleable.SlidingDrawer_handle, 0);
if (handleId == 0) {
throw new IllegalArgumentException("The handle attribute is required and must refer "
+ "to a valid child.");
}
int contentId = a.getResourceId(R.styleable.SlidingDrawer_content, 0);
if (contentId == 0) {
throw new IllegalArgumentException("The content attribute is required and must refer "
+ "to a valid child.");
}
if (handleId == contentId) {
throw new IllegalArgumentException("The content and handle attributes must refer "
+ "to different children.");
}
mHandleId = handleId;
mContentId = contentId;
final float density = getResources().getDisplayMetrics().density;
mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f);
mMaximumTapVelocity = (int) (MAXIMUM_TAP_VELOCITY * density + 0.5f);
mMaximumMinorVelocity = (int) (MAXIMUM_MINOR_VELOCITY * density + 0.5f);
mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f);
mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f);
mVelocityUnits = (int) (VELOCITY_UNITS * density + 0.5f);
a.recycle();
setAlwaysDrawnWithCacheEnabled(false);
}
@Override
protected void onFinishInflate() {
mHandle = findViewById(mHandleId);
if (mHandle == null) {
throw new IllegalArgumentException("The handle attribute is must refer to an"
+ " existing child.");
}
mHandle.setOnClickListener(new DrawerToggler());//---------------------------------------------就是这里。点击事件
mContent = findViewById(mContentId);
if (mContent == null) {
throw new IllegalArgumentException("The content attribute is must refer to an"
+ " existing child.");
}
mContent.setVisibility(View.GONE);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions");
}
final View handle = mHandle;
measureChild(handle, widthMeasureSpec, heightMeasureSpec);
if (mVertical) {
int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;
mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
} else {
int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;
mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY));
}
setMeasuredDimension(widthSpecSize, heightSpecSize);
}
@Override
protected void dispatchDraw(Canvas canvas) {
final long drawingTime = getDrawingTime();
final View handle = mHandle;
final boolean isVertical = mVertical;
drawChild(canvas, handle, drawingTime);
if (mTracking || mAnimating) {
final Bitmap cache = mContent.getDrawingCache();
if (cache != null) {
if (isVertical) {
canvas.drawBitmap(cache, 0, handle.getBottom(), null);
} else {
canvas.drawBitmap(cache, handle.getRight(), 0, null);
}
} else {
canvas.save();
canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset,
isVertical ? handle.getTop() - mTopOffset : 0);
drawChild(canvas, mContent, drawingTime);
canvas.restore();
}
} else if (mExpanded) {
drawChild(canvas, mContent, drawingTime);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mTracking) {
return;
}
final int width = r - l;
final int height = b - t;
final View handle = mHandle;
int childWidth = handle.getMeasuredWidth();
int childHeight = handle.getMeasuredHeight();
int childLeft;
int childTop;
final View content = mContent;
if (mVertical) {
childLeft = (width - childWidth) / 2;
childTop = mExpanded ? mTopOffset : height - childHeight + mBottomOffset;
content.layout(0, mTopOffset + childHeight, content.getMeasuredWidth(),
mTopOffset + childHeight + content.getMeasuredHeight());
} else {
childLeft = mExpanded ? mTopOffset : width - childWidth + mBottomOffset;
childTop = (height - childHeight) / 2;
content.layout(mTopOffset + childWidth, 0,
mTopOffset + childWidth + content.getMeasuredWidth(),
content.getMeasuredHeight());
}
handle.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
mHandleHeight = handle.getHeight();
mHandleWidth = handle.getWidth();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mLocked) {
return false;
}
final int action = event.getAction();
float x = event.getX();
float y = event.getY();
final Rect frame = mFrame;
final View handle = mHandle;
handle.getHitRect(frame);
if (!mTracking && !frame.contains((int) x, (int) y)) {
return false;
}
if (action == MotionEvent.ACTION_DOWN) {
mTracking = true;
handle.setPressed(true);
// Must be called before prepareTracking()
prepareContent();
// Must be called after prepareContent()
if (mOnDrawerScrollListener != null) {
mOnDrawerScrollListener.onScrollStarted();
}
if (mVertical) {
final int top = mHandle.getTop();
mTouchDelta = (int) y - top;
prepareTracking(top);
} else {
final int left = mHandle.getLeft();
mTouchDelta = (int) x - left;
prepareTracking(left);
}
mVelocityTracker.addMovement(event);
}
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mLocked) {
return true;
}
if (mTracking) {
mVelocityTracker.addMovement(event);
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE:
moveHandle((int) (mVertical ? event.getY() : event.getX()) - mTouchDelta);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(mVelocityUnits);
float yVelocity = velocityTracker.getYVelocity();
float xVelocity = velocityTracker.getXVelocity();
boolean negative;
final boolean vertical = mVertical;
if (vertical) {
negative = yVelocity < 0;
if (xVelocity < 0) {
xVelocity = -xVelocity;
}
if (xVelocity > mMaximumMinorVelocity) {
xVelocity = mMaximumMinorVelocity;
}
} else {
negative = xVelocity < 0;
if (yVelocity < 0) {
yVelocity = -yVelocity;
}
if (yVelocity > mMaximumMinorVelocity) {
yVelocity = mMaximumMinorVelocity;
}
}
float velocity = (float) Math.hypot(xVelocity, yVelocity);
if (negative) {
velocity = -velocity;
}
final int top = mHandle.getTop();
final int left = mHandle.getLeft();
if (Math.abs(velocity) < mMaximumTapVelocity) {
if (vertical ? (mExpanded && top < mTapThreshold + mTopOffset) ||
(!mExpanded && top > mBottomOffset + mBottom - mTop -
mHandleHeight - mTapThreshold) :
(mExpanded && left < mTapThreshold + mTopOffset) ||
(!mExpanded && left > mBottomOffset + mRight - mLeft -
mHandleWidth - mTapThreshold)) {
if (mAllowSingleTap) {
playSoundEffect(SoundEffectConstants.CLICK);
if (mExpanded) {
animateClose(vertical ? top : left);
} else {
animateOpen(vertical ? top : left);
}
} else {
performFling(vertical ? top : left, velocity, false);
}
} else {
performFling(vertical ? top : left, velocity, false);
}
} else {
performFling(vertical ? top : left, velocity, false);
}
}
break;
}
}
return mTracking || mAnimating || super.onTouchEvent(event);
}
private void animateClose(int position) {
prepareTracking(position);
performFling(position, mMaximumAcceleration, true);
}
private void animateOpen(int position) {
prepareTracking(position);
performFling(position, -mMaximumAcceleration, true);
}
private void performFling(int position, float velocity, boolean always) {
mAnimationPosition = position;
mAnimatedVelocity = velocity;
if (mExpanded) {
if (always || (velocity > mMaximumMajorVelocity ||
(position > mTopOffset + (mVertical ? mHandleHeight : mHandleWidth) &&
velocity > -mMaximumMajorVelocity))) {
// We are expanded, but they didn't move sufficiently to cause
// us to retract. Animate back to the expanded position.
mAnimatedAcceleration = mMaximumAcceleration;
if (velocity < 0) {
mAnimatedVelocity = 0;
}
} else {
// We are expanded and are now going to animate away.
mAnimatedAcceleration = -mMaximumAcceleration;
if (velocity > 0) {
mAnimatedVelocity = 0;
}
}
} else {
if (!always && (velocity > mMaximumMajorVelocity ||
(position > (mVertical ? getHeight() : getWidth()) / 2 &&
velocity > -mMaximumMajorVelocity))) {
// We are collapsed, and they moved enough to allow us to expand.
mAnimatedAcceleration = mMaximumAcceleration;
if (velocity < 0) {
mAnimatedVelocity = 0;
}
} else {
// We are collapsed, but they didn't move sufficiently to cause
// us to retract. Animate back to the collapsed position.
mAnimatedAcceleration = -mMaximumAcceleration;
if (velocity > 0) {
mAnimatedVelocity = 0;
}
}
}
long now = SystemClock.uptimeMillis();
mAnimationLastTime = now;
mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
mAnimating = true;
mHandler.removeMessages(MSG_ANIMATE);
mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
stopTracking();
}
private void prepareTracking(int position) {
mTracking = true;
mVelocityTracker = VelocityTracker.obtain();
boolean opening = !mExpanded;
if (opening) {
mAnimatedAcceleration = mMaximumAcceleration;
mAnimatedVelocity = mMaximumMajorVelocity;
mAnimationPosition = mBottomOffset +
(mVertical ? getHeight() - mHandleHeight : getWidth() - mHandleWidth);
moveHandle((int) mAnimationPosition);
mAnimating = true;
mHandler.removeMessages(MSG_ANIMATE);
long now = SystemClock.uptimeMillis();
mAnimationLastTime = now;
mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
mAnimating = true;
} else {
if (mAnimating) {
mAnimating = false;
mHandler.removeMessages(MSG_ANIMATE);
}
moveHandle(position);
}
}
private void moveHandle(int position) {
final View handle = mHandle;
if (mVertical) {
if (position == EXPANDED_FULL_OPEN) {
handle.offsetTopAndBottom(mTopOffset - handle.getTop());
invalidate();
} else if (position == COLLAPSED_FULL_CLOSED) {
handle.offsetTopAndBottom(mBottomOffset + mBottom - mTop -
mHandleHeight - handle.getTop());
invalidate();
} else {
final int top = handle.getTop();
int deltaY = position - top;
if (position < mTopOffset) {
deltaY = mTopOffset - top;
} else if (deltaY > mBottomOffset + mBottom - mTop - mHandleHeight - top) {
deltaY = mBottomOffset + mBottom - mTop - mHandleHeight - top;
}
handle.offsetTopAndBottom(deltaY);
final Rect frame = mFrame;
final Rect region = mInvalidate;
handle.getHitRect(frame);
region.set(frame);
region.union(frame.left, frame.top - deltaY, frame.right, frame.bottom - deltaY);
region.union(0, frame.bottom - deltaY, getWidth(),
frame.bottom - deltaY + mContent.getHeight());
invalidate(region);
}
} else {
if (position == EXPANDED_FULL_OPEN) {
handle.offsetLeftAndRight(mTopOffset - handle.getLeft());
invalidate();
} else if (position == COLLAPSED_FULL_CLOSED) {
handle.offsetLeftAndRight(mBottomOffset + mRight - mLeft -
mHandleWidth - handle.getLeft());
invalidate();
} else {
final int left = handle.getLeft();
int deltaX = position - left;
if (position < mTopOffset) {
deltaX = mTopOffset - left;
} else if (deltaX > mBottomOffset + mRight - mLeft - mHandleWidth - left) {
deltaX = mBottomOffset + mRight - mLeft - mHandleWidth - left;
}
handle.offsetLeftAndRight(deltaX);
final Rect frame = mFrame;
final Rect region = mInvalidate;
handle.getHitRect(frame);
region.set(frame);
region.union(frame.left - deltaX, frame.top, frame.right - deltaX, frame.bottom);
region.union(frame.right - deltaX, 0,
frame.right - deltaX + mContent.getWidth(), getHeight());
invalidate(region);
}
}
}
private void prepareContent() {
if (mAnimating) {
return;
}
// Something changed in the content, we need to honor the layout request
// before creating the cached bitmap
final View content = mContent;
if (content.isLayoutRequested()) {
if (mVertical) {
final int childHeight = mHandleHeight;
int height = mBottom - mTop - childHeight - mTopOffset;
content.measure(MeasureSpec.makeMeasureSpec(mRight - mLeft, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
content.layout(0, mTopOffset + childHeight, content.getMeasuredWidth(),
mTopOffset + childHeight + content.getMeasuredHeight());
} else {
final int childWidth = mHandle.getWidth();
int width = mRight - mLeft - childWidth - mTopOffset;
content.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mBottom - mTop, MeasureSpec.EXACTLY));
content.layout(childWidth + mTopOffset, 0,
mTopOffset + childWidth + content.getMeasuredWidth(),
content.getMeasuredHeight());
}
}
// Try only once... we should really loop but it's not a big deal
// if the draw was cancelled, it will only be temporary anyway
content.getViewTreeObserver().dispatchOnPreDraw();
content.buildDrawingCache();
content.setVisibility(View.GONE);
}
抽屉类SlidingDrawer源码解析
最新推荐文章于 2024-04-22 19:33:31 发布