仿写第三篇之仿写scrollview

来源:互联网 发布:nginx 日志配置 编辑:程序博客网 时间:2024/04/29 08:15

前言

读完自定义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

源码



0 0
原创粉丝点击