您的位置:首页 > 其它

仿写第三篇之仿写scrollview

2015-06-01 19:49 302 查看

前言

读完自定义viewgroup实践之仿写LinearLayout和自定义虚线两篇文章,相信你对自定义view和自定义viewgroup有一定的了解了。今天带大家实现垂直滚动的viewgroup。

对scrollview滚动原理的思考:首先scrollview有子view,所以其必是一个viewgroup。而其可上下滚动,这就说明肯定是在onTouchEvent方法中调了scrollTo或scrollBy方法。

好了,知道了其骨架。接下来看源码进一步分析:

1.看继承关系:

scrollview继承自framelayout,既然是viewgroup子孙,那么重要的也就是onmeasure、onlayou和LayoutParam了。看scrollview的addview()方法:

@Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child, params);
    }
它没写自己的LayoutParam,用的是viewgroup的LayoutParam。所以我写的也就没写LayoutParam。

2.onMeasure()

其首先调用父类的onmeasure方法。由于是framelayout,其measure子view时调用了measureChildWithMargins方法,而scrollview重写了该方法。看重写代码:

@Override
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }


其给子view的childHeightMeasureSpec 是以 MeasureSpec.UNSPECIFIED生成的。所以子view想要多高就可以多高。说个题外话,平常我们测量一个view的宽高时总是这样调用view.measure(0,0),而MeasureSpec.UNSPECIFIED的值就是0,也就是测量view有多大就多大,调用后就可以getMeasuredWidth和getMeasuredHeight获取宽高。

3.onlayout方法

由于其只有一个子view,也就是linearlayout,所以super.onLayout(changed, l, t, r, b),这里没啥可说的。

接下来是核心部分:

声明:mIsBeingDragged,true为开始拖动

onInterceptTouchEvent ------- true为截取事件,该方法返回的是mIsBeingDragged,

我们依据事件的整个过程来说明,首先触发ACTION_DOWN事件,如果还在滚动,则mIsBeingDragged为true。在ACTION_MOVE中,如果y方向的偏移大于了mTouchSlop(滚动之前可以滑动的距离),则mIsBeingDragged为true。

在滚动中了

if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
            return true;
        }
最后ACTION_UP,mIsBeingDragged为false。

onTouchEvent------ 具体的滚动逻辑在其中

mScroller也就是OverScroller,不熟悉的可百度下,它就是滚动辅助类。

ACTION_DOWN:在滚动中手指down,则停止滚动。保存mLastMotionY(最后触摸点的值)。

ACTION_MOVE:关键在overScrollBy()方法。这个方法中又调用了onOverScrolled()方法,而scrollview重写了该方法,直接看代码:

@Override
    protected void onOverScrolled(int scrollX, int scrollY,
            boolean clampedX, boolean clampedY) {
        // Treat animating scrolls differently; see #computeScroll() for why.
        if (!mScroller.isFinished()) {
            final int oldX = mScrollX;
            final int oldY = mScrollY;
            mScrollX = scrollX;
            mScrollY = scrollY;
            invalidateParentIfNeeded();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (clampedY) {
                mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
            }
        } else {
            super.scrollTo(scrollX, scrollY);
        }

        awakenScrollBars();
    }
也就是其调用了scrollTo方法。
ACTION_UP:计算velocityTracker,然后判断其是否可以fling。

flying --- 其中使用了scroller

public void fling(int velocityY) {
        if (getChildCount() > 0) {
            int height = getHeight() - mPaddingBottom - mPaddingTop;
            int bottom = getChildAt(0).getHeight();

            mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
                    Math.max(0, bottom - height), 0, height/2);

            if (mFlingStrictSpan == null) {
                mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
            }

            postInvalidateOnAnimation();
        }
    }
而view重绘时会调用computeScroll()方法。computeScroll一般与scroller一起使用,不熟悉的百度。

最后附上源码,也就是简单版的scrollview

源码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: