一屬性變量分析
構造函數完成獲取attr屬性內容的讀取,讀取用戶配置的UI屬性,用於構造新的UI結構。
屬性內容爲,注意這裏的SlidingShow作者自己定義的,拷自源碼包:
<resources>
<declare-styleable name="SlidingShow">
<attr name="handle" format="reference" />
<attr name="content" format="reference" />
<attr name="orientation" format="integer" />
<attr name="bottomOffset" format="dimension" />
<attr name="topOffset" format="dimension" />
<attr name="allowSingleTap" format="boolean" />
<attr name="animateOnClick" format="boolean" />
</declare-styleable>
</resources>
android:allowSingleTap:指示是否可以通過handle打開或關閉
android:animateOnClick:指示是否當使用者按下手柄打開/關閉時是否該有一個動畫。
android:content:隱藏的內容
android:handle:handle(手柄)
二源碼情景分析
在分析源碼前,先要選擇一個分析順序,順序按照ViewGroup繪製週期來進展。
根據viewGroup的繪製順序開始分析源碼。
2.1 測量函數
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {(1)
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
final View handle = mHandle;
measureChild(handle, widthMeasureSpec, heightMeasureSpec);//(2)獲取child View的大小
//定義mContent大小
if (mVertical) {
int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;(3)
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);
}
(1) widthMeasureSpec和heightMeasureSpec爲寬度規格和高度規格,可以獲取對應的mode(AT_MOST盡大/ EXACTLY精確/ UNSPECIFIED不限制),可以獲取對應尺寸。
(2) 獲取viewGroup中子視圖的尺寸,這裏是獲取handle的View大小。
(3) 根據對應的偏移量和handle view的大小,設置Content大小。
2.2 視圖函數
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {(1)
if (mTracking) {
return;
}
final int width = r - l; //viewGroup的寬度
final int height = b - t; //viewGroup的高度
final View handle = mHandle;
int childWidth = handle.getMeasuredWidth(); //handle尺寸
int childHeight = handle.getMeasuredHeight();
int childLeft;
int childTop;
final View content = mContent;
if (mVertical) {
childLeft = (width - childWidth) / 2;
/**
* 設定hanlde位置,這裏handle與mBottomOffset和自己大小有關
* 設定content位置,這裏content與mTopOffset和handle大小有關
*/
childTop = mExpanded ? mTopOffset : height - childHeight + mBottomOffset;(2)
//設定content的位置
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位置
handle.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);(3)
mHandleHeight = handle.getHeight();
mHandleWidth = handle.getWidth();
}
(2) 問號表達式,根據條件展開或是收縮決定handle的Top位置。
(3) 確定handle位置,包含了展開和收縮的判定,這裏Content位置還不太對,還顯示在expanded(展開狀態)。
2.3 繪製圖像函數
@Override
protected void dispatchDraw(Canvas canvas) { //繪製兩個子view
final long drawingTime = getDrawingTime(); //獲取當前GPU繪製view時間,不是日期時間(1)
final View handle = mHandle;
final boolean isVertical = mVertical;
drawChild(canvas, handle, drawingTime);(1)
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);(2)
drawChild(canvas, mContent, drawingTime);
canvas.restore(); //更改save方法前所有的繪製修改
}
} else if (mExpanded) {
drawChild(canvas, mContent, drawingTime);(3)
}
}
(1)根據當前配置的canvas繪製handle
(2)更改相應的canvas配置,用戶繪製content
(3)根據當前配置的canvas繪製content
2.4 完成繪製函數
@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());//設置單擊監聽類(1)
mContent = findViewById(mContentId);
if (mContent == null) {
throw new IllegalArgumentException("The content attribute is must refer to an"
+ " existing child.");
}
mContent.setVisibility(View.GONE);
}
(1)設置監聽類,內部設置相應的點擊事件動畫。
2.4.1 監聽類的解析
private class DrawerToggler implements OnClickListener {
public void onClick(View v) {
if (mLocked) {(1)
return;
}
// mAllowSingleTap isn't relevant here; you're *always*
// allowed to open/close the drawer by clicking with the
// trackball.
//android:allowSingleTap:指示是否可以通過handle打開或關閉
//android:animateOnClick:指示是否當使用者按下手柄打開/關閉時是否該有一個動畫。
if (mAnimateOnClick) {(2)
animateToggle();(3)
} else {
toggle();(4)
}
}
}
(1)判斷是否將view鎖住,不允許點擊。(3)animateToggle()方法,單擊後的動畫效果
public void animateToggle() {
if (!mExpanded) {
animateOpen();
} else {
animateClose();
}
}
展開和收縮兩種動畫效果: public void animateOpen() {
prepareContent(); //準備content(-1)
final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
if (scrollListener != null) {
scrollListener.onScrollStarted();//調用onScrollStarted函數
}
animateOpen(mVertical ? mHandle.getTop() : mHandle.getLeft());//展開動畫(-2)
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);//設定當前的accessibilityEvent
if (scrollListener != null) {
scrollListener.onScrollEnded(); //調用onScrollEnded函數
}
}
(-1)準備content 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 = getBottom() - getTop() - childHeight - mTopOffset;
content.measure(MeasureSpec.makeMeasureSpec(getRight() - getLeft(), 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 = getRight() - getLeft() - childWidth - mTopOffset;
content.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getBottom() - getTop(), 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();
if (!content.isHardwareAccelerated()) content.buildDrawingCache();
content.setVisibility(View.GONE);
// mContent.setVisibility(View.VISIBLE);
}
重新繪製content,設計相應的measure() layout()等方法。 跟onLayout(boolean changed, int l, int t, int r, int b)方法中繪製content相同。 private void animateOpen(int position) {
prepareTracking(position);//準備路徑
performFling(position, -mMaximumAcceleration, true);//執行跳動
}
private void prepareTracking(int position) {
mTracking = true;//設置標誌位
mVelocityTracker = VelocityTracker.obtain();(--1)
boolean opening = !mExpanded;
if (opening) {
mAnimatedAcceleration = mMaximumAcceleration; //加速度設定
mAnimatedVelocity = mMaximumMajorVelocity; //最大速度設定
mAnimationPosition = mBottomOffset +
(mVertical ? getHeight() - mHandleHeight : getWidth() - mHandleWidth);
moveHandle((int) mAnimationPosition); //移動動畫(--2)
mAnimating = true;
mHandler.removeMessages(MSG_ANIMATE);(--3)
long now = SystemClock.uptimeMillis();
mAnimationLastTime = now; //記錄時間
mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
mAnimating = true;
} else {
if (mAnimating) {
mAnimating = false;
mHandler.removeMessages(MSG_ANIMATE);
}
moveHandle(position);(--4)
}
}
(--1) 速度追蹤器獲取 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 + getBottom() - getTop() -
mHandleHeight - handle.getTop());
invalidate();
} else {
final int top = handle.getTop();//中間狀態
int deltaY = position - top;
if (position < mTopOffset) {
deltaY = mTopOffset - top;
} else if (deltaY > mBottomOffset + getBottom() - getTop() - mHandleHeight - top) {
deltaY = mBottomOffset + getBottom() - getTop() - 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 {
......
}
}
(--3) 移除動畫消息(--4) 移動到相應位置
2.4.1總結animateClose();和animateOpen();基本上一樣這裏就不在描述了。toggle();更是簡單了很多,沒有對應的動畫,這裏也不再分析。
2.5 滑動事件處理
前面的介紹中,首先描繪如何繪製一個View,並給出了繪製順序;後來設計了相應的點擊事件處理,並提供了有動畫和無動畫兩種情況下的處理函數;那麼最後則是處理滑動或者多點觸控的事件。2. 如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之後return false,那麼後續的move, up等事件將繼續會先傳遞給該ViewGroup,之後才和down事件一樣傳遞給最終的目標view的onTouchEvent()處理。
3. 如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之後return true,那麼後續的move, up等事件將不再傳遞給onInterceptTouchEvent(),而是和down事件一樣傳遞給該ViewGroup的onTouchEvent()處理,注意,目標view將接收不到任何事件。
4. 如果最終需要處理事件的view的onTouchEvent()返回了false,那麼該事件將被傳遞至其上一層次的view的onTouchEvent()處理。
5. 如果最終需要處理事件的view 的onTouchEvent()返回了true,那麼後續事件將可以繼續傳遞給該view的onTouchEvent()處理。
@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;//返回true,Event交由onTouchEvent處理
}
@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);// sqrt(x2+ y2).
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 + getBottom() - getTop() -
mHandleHeight - mTapThreshold) :
(mExpanded && left < mTapThreshold + mTopOffset) ||
(!mExpanded && left > mBottomOffset + getRight() - getLeft() -
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);//執行鬆開手的後面運動(1)
}
} else {
performFling(vertical ? top : left, velocity, false);
}
} else {
performFling(vertical ? top : left, velocity, false);
}
}
break;
}
}
return mTracking || mAnimating || super.onTouchEvent(event);
}
(1)鬆開手後的處理函數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 doAnimation() {
if (mAnimating) {
incrementAnimation();
if (mAnimationPosition >= mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1) {
mAnimating = false;
closeDrawer();
} else if (mAnimationPosition < mTopOffset) {
mAnimating = false;
openDrawer();
} else {
moveHandle((int) mAnimationPosition);
mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE),
mCurrentAnimationTime);//循環消息
}
}
}
private void incrementAnimation() {
long now = SystemClock.uptimeMillis();
float t = (now - mAnimationLastTime) / 1000.0f; // ms -> s
final float position = mAnimationPosition;
final float v = mAnimatedVelocity; // px/s
final float a = mAnimatedAcceleration; // px/s/s
mAnimationPosition = position + (v * t) + (0.5f * a * t * t); // px
mAnimatedVelocity = v + (a * t); // px/s
mAnimationLastTime = now; // ms
}
private class SlidingHandler extends Handler {
public void handleMessage(Message m) {
switch (m.what) {
case MSG_ANIMATE:
doAnimation();
break;
}
}
}