SlidingDrawer源码分析
发表时间:2020-10-19
发布人:葵宇科技
浏览次数:18
一属性变量分析
构造函数完成获取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绘制周期来进展。
[img]http://img.blog.csdn.net/20141228174928045
根据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(); }
(1) 参数changed表示view有新的尺寸或地位;参数l表示相对于父view的Left地位;参数t表示相对于父view的Top地位;参数r表示相对于父view的Right地位;参数b表示相对于父view的Bottom地位。
(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锁住,不许可点击。
(2)断定是否应用单击动画,可以不应用,可以应用。
(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雷同。
(-2)动画展开aimateOpen(boolean)办法
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) 速度追踪器获取
(--2) 处理移动操作,moveHandle
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,并给出了绘制次序;后来设计了响应的点击事宜处理,并供给了有动画和无动画两种情况下的处理函数;那么最后则是处理滑动或者多点触控的事宜。这里会用到两个办法,都是viewGroup的办法,分别是onInterceptTouchEvent()和onTouchEvent()办法,履行次序遵守下面五点:
1. down事宜起首会传递到onInterceptTouchEvent()办法
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();//停止动画 }
计算接下来活动所须要的参量,发送handler履行后面的动画。
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; } } }