一属性变量分析

构造函数完成获取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绘制周期来进展。

SlidingDrawer源码分析_第1张图片

根据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;            }        }    }


更多相关文章

  1. Android 触控事件解析(Mastering the Android Touch System)
  2. Android 设置软键盘搜索键以及监听搜索键点击事件
  3. Android 开机logo动画
  4. Android基础03-事件处理
  5. Android中Touch事件的传递机制
  6. Android中属性动画和补间动画的区别
  7. Android 开机图片/文字/动画的修改
  8. Android opengl es创建动画详解

随机推荐

  1. Android实现震动功能
  2. Android开发第十周,学习分享
  3. Android 获取本地所有音频文件
  4. Android前端开发15:显示网络图片
  5. Color android
  6. Android 权限清单
  7. Building Your First App
  8. android NDK编译.a静态库
  9. AS 3.0 INSTALL_FAILED_TEST_ONLY真正原
  10. Android下拉刷新上拉加载更多ListView控