转载请注意:
http://blog.youkuaiyun.com/wjzj000/article/details/53874285
本菜开源的一个自己写的Demo,希望能给Androider们有所帮助,水平有限,见谅见谅…
https://github.com/zhiaixinyang/PersonalCollect (拆解GitHub上的优秀框架于一体,全部拆离不含任何额外的库导入)
https://github.com/zhiaixinyang/MyFirstApp(Retrofit+RxJava+MVP)
写在前面
在一切的开始之前,先看一个简单的效果:
实现这种效果有很多种方案,今天在这就主要借这个机会来记录一下scrollTo,scrollBy以及Scroller的用法。
让我们先了解一发:
关于scrollTo方法:
先看一下scrollTo()的源码解释:
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
//翻译过来:设置视图的滚动位置。 这将导致调用{@link #onScrollChanged(int,int,int,int)},并且视图将失效。
说实话不是很好理解。但是根据效果我们可以这么理解:scrollTo的效果是移向你传进的坐标。如果你再次调用这个方法,传值不变时!你会发现并没有任何的位置移动。(和scrollBy方法有区别)因为它已经到达了这个位置因此不会再移动,而scrollBy却不然。
此外此方法移动的是View内部的位置而不是View整体。如果View不是一个ViewGroup的话,例如TextView,那么移动的就是TextView的文字内容;如果是ViewGroup那么便是ViewGroup中的子元素。
关于scrollBy方法:
看一下scrollBy的源码:
//注释基本和scrollTo相同
/**
* Move the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
我们可以看出,scrollBy内部是调用的scrollTo,但是这里的传参导致了它们二者的不同。
scrollBy传入了mScrollX…这是个什么东西?我们在View中可以调用getScrollX()方法拿到这个值;其实这个值就是滑动的距离,对于X轴来说左滑动为正(增加),对于Y轴上滑动为正(增加)。
scrollBy的效果与scrollTo也截然相反。scrollBy就基于当前位置的移动,简单说它可以不断的移动。
只用文字去叙述有点单调,接下来上高清无码gif:
- 这里我分别对俩这进行了多次点击,但是 传的值是固定的
!!!!这里有个需要注意的地方!!!!
在传值的时候,我们需要注意正负号问题:简单来说往左滑动时x为正,否则为负;上滑动时y为正,反之为负。
因为这里和mScrollX和mScrollY这俩个变量有关。这两个方法最终都会直接或间接的引用到这俩个变量。而它们俩的正负是这么判断的:如果View的左边缘在View内容区域(这俩个方法的移动都只是移动自己的内容区域)左边缘的右边为正,反之为负;如果View的上边缘在View内容区域的上边缘的下边mScrollY为正反之为负。
为什么是这样:当我们把内容区域的某个边缘当做参考点来理解就是这种情况。如果View的内容区域的左边缘为(0,…)那么View的左边缘在它的右边,理所应当为(+…,…)。同理mScrollY也是如此。
第三个我们来看一下Scroller
它其实就是一个辅助类:
//简单重写了onTouchEvent
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
int x=-5;
//在0.2内使View的x轴从0移到x
//但是具体怎么移动需要我们自己去实现,解释在下面
scroller.startScroll(0,0,x,0,200);
invalidate();
break;
}
return true;
}
/**
* invalidate()最终会调用computeScroll()
* 而View中的computeScroll()是一个空实现
* 因此需要我们自己去重写这个方法,去实现对应的效果
*/
@Override
public void computeScroll() {
super.computeScroll();
//如果返回true,说明滑动还不到结束的时候,应当继续。
if (scroller.computeScrollOffset()){
//而促使它滑动的方式依旧是scrollBy或是scrollTo
//注意此处x的位置,我们是使用的scroller.getCurrX()的返回值,至于它的作用往下来
scrollBy(scroller.getCurrX(),0);
//请求重新绘制View
invalidate();
}
}
Scroller这个类本身不具备任何移动View的作用。它的startScroll方法,我们进源码就会发现仅仅是一些简单的赋值。真正起到移动效果的是invalidate()方法,通过这个方法使得View重绘,因此就会调用computeScroll(),所以我们重写computeScroll()。
可能有朋友在这里会由衷的赞叹一句:尼玛SB吗?饶了这么一大圈不还是scrollTo/scrollBy么!
不不不,它有一个最重要的作用。先让我们注意一下我们在传参的时候,传了一个时间参数(200)。
这个值在computeScrollOffset()中体现它的作用:
//根据代码中的变量名,我们也能猜出:这里通过时间的流逝来计算移动进行的比例
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
//如果时间流逝小于应该传入的值,那么就继续执行
if (timePassed < mDuration) {
switch (mMode) {
//如果是滚动状态
case SCROLL_MODE:
//通过类插值器的效果来计算x的变化量,使其随时间进行均匀变化。
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
//省略部分代码
}
这里用于计算移动的比例,因此Scroller最大的效果就是移动可以随时间的变化而变化,简单说可以做一些弹性的效果。让滑动不在单点生硬。
其实理解这些方法的使用,最开始的那个效果真的很简单,所以接下来就是简单贴一下代码:
相关代码
onTouchEvent相关:
@Override
public boolean onTouchEvent(MotionEvent event) {
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!scroller.isFinished())
scroller.abortAnimation();
//记录手指按下时的坐标
lastY = y;
downY=event.getY();
//消费点击事件
return true;
case MotionEvent.ACTION_MOVE:
float dy = y - lastY;
scrollBy(0, (int) -dy);
lastY = y;
break;
}
return super.onTouchEvent(event);
}
scrollTo相关:
//topViewHeight就是我们需要滑动的View的高度
@Override
public void scrollTo(int x, int y) {
if (y < 0) {
y = 0;
}
if (y > topViewHeight) {
y = topViewHeight;
}
if (y != getScrollY()) {
super.scrollTo(x, y);
}
}
onFinishInflate相关:
//此方法在布局加载完成后回调,因此在此获得View的引用
@Override
protected void onFinishInflate() {
super.onFinishInflate();
topView=getChildAt(0);
}
onSizeChanged相关:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//获取topView的高度
topViewHeight = topView.getMeasuredHeight();
//画三角形所需的路线
path=new Path();
path.moveTo(avatarLeft-25,topViewHeight);
path.lineTo(avatarLeft+25,topViewHeight);
path.lineTo(avatarLeft,topViewHeight-25);
path.close();
}
//画三角形
@Override
protected void dispatchDraw(Canvas canvas) {
canvas.drawPath(path,paint);
super.dispatchDraw(canvas);
}
如果对基本的自定义View中的Canvas的一些Api不了解的可以看一下我的另一篇博客:
http://blog.youkuaiyun.com/wjzj000/article/details/53589024
PS:相关源码基本都存放于我的这个开源项目之中:
https://github.com/zhiaixinyang/PersonalCollect
尾声
OK,到此关于scrollTo和scrollBy以及Scroller的用法就结束了,接下来就是关于NestedScrolling机制的分析,让我们下一次博客见。
最后希望各位看官可以star我的GitHub,三叩九拜,满地打滚求star:
https://github.com/zhiaixinyang/PersonalCollect
https://github.com/zhiaixinyang/MyFirstApp