首页 > 代码库 > SlidingDrawer源码分析
SlidingDrawer源码分析
一属性变量分析
构造函数完成获取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锁住,不允许点击。
(2)判断是否使用单击动画,可以不使用,可以使用。
(3)animateToggle()方法,单击后的动画效果
(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()处理。
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; } } }
SlidingDrawer源码分析
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。