首先看View#scrollTo(int x,int y)
,以x
为例。当传入的x>0
,View
内容发生了滚动,并且沿着x
轴的负方向滚动。why???
scrollTo滚动的是View的内容。如果想通过scrollTo使某个view发生滚动,应当调用其父View的scrollTo方法。为什么滚动的是view的内容,后面会说这个。
来看源码。
先看View#scrollTo(int x,int y)
源码。x
直接被赋值给mScrollX
。
#View.java
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
对于一个View,当调用其父view的scrollTo方法使其从A点滚动到B点,再滚动到C点,此时view的mScrollX是其总(A->C)的滚动距离,而不是阶段(A->B或者B->C)滚动距离
在看View# invalidate(int l, int t, int r, int b)
。这里的scrollX就是上一个方法传入的参数x
。l-scrollX
是绘制区域的左边,r-scrollX
是绘制区域的右边。x>0
绘制区域就会左移;x<0
绘制区域就会右移。
#View.java
public void invalidate(int l, int t, int r, int b) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
}
从上面可以看到View#scrollTo
调用会触发View#invalidate
,从而导致绘制区域发生了改变,也就是我们看到View
显示内容的区域发生了改变。
要使一个View
的位置发生改变,应当改变View
的mLeft
和mTop
两个属性值。很明显,View#scrollTo
并没有改变这两个属性的值。因此View#scrollTo
不会引起View
的位置滚动,而是滚动了View
的内容。
那如何自定义一个可滚动的View呢?
继承自ViewGroup。当然如有必要需要重写onInterceptTouchEvent,拦截MotionEvent.ACTION_MOVE事件,重写onTouchEvent,然后调用scrollTo或者scrollBy实现滚动。
private float lastDownX,lastDownY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
lastDownX = event.getX();
lastDownY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
if (满足一定条件){
//请求父View不要拦截
getParent().requestDisallowInterceptTouchEvent(true);
//返回true。将ACTION_MOVE事件交给自身处理。
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
lastDownX = event.getX();
lastDownY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float moveX = event.getX();
float moveY = event.getY();
float diffX = moveX - lastDownX;
float diffY = moveY - lastDownY;
/**
* 从左向右滑动,diffX<0;getScrollX()>0;
* scrollTo的参数x>0
* x = getScrollX() - diffX
*
* 从右向左滑动,diffX>0,getScrollX()<0;
* scrollTo的参数x<0
* x = getScrollX() - diffX
*
* 参数y同理
*/
scrollTo((int)(getScrollX()-diffX),(int)(getScrollY()-diffY));
//scrollBy(-(int)diffX,-(int)diffY);
return true;
case MotionEvent.ACTION_UP:
lastDownX = 0;
lastDownY = 0;
break;
}
return super.onTouchEvent(event);
}
滚动的时候还要考虑边界约束的问题,以QQ消息列表侧滑删除为例。
从左向右侧滑要对左边界约束,从右向左侧滑要对右边界约束。
在ACTION_MOVE事件中做边界约束。使用上面的代码。
//最大滑动距离,根据自己的业务情况来确定这个值。以QQ侧滑删除为例,这个值是删除按钮的宽度。
int maxScrollDistance;
case MotionEvent.ACTION_MOVE:
//某一时刻要滑动到的位置,这个位置到达边界时,要做约束。
float targetX = getScrollX() - (event.x - lastDownX);
//从左向右滑动,当滑动到左边界时,getScrollX() = 0;继续滑动,getScrollX()<0;对这个情况要做个约束,避免滑动越过左边界。
if (targetX<0) targetX = 0;
//从右向左滑动,当滑动到右边界时,getScrollX() = maxScrollDistance;继续滑动,getScrollX()>maxScrollDistance;对这个情况要做个约束,避免滑动越过右边界。
if (targetX>maxScrollDistance) targetX = maxScrollDistance;
scrollTo(targetX,0);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。