本文主要是实现类似于ScrollView的一个控件,代码相当简单:
自定义ViewGroup通常需要重写onMeasure()来对子View进行测量,重写onLayout方法来确定子View的位置。重写onTouchEvent()方法增加响应事件。
我们实现的ViewGroup可以实现ScrollView所具有的上下滑动功能,但是在滑动的过程中,增加一个粘性的效果,即当一个子View向上滑动大于一定距离后,松开手指,它将自动向上滑动。显示下一个子View.
1.首先,对ViewGroup中的所有子View进行遍历。代码如下
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取子View个数
int countView = getChildCount();
for (int i = 0; i < countView; i++) {
View childView = getChildAt(i);
//测量所有子View的大小
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
}
2.接下来,就是对子View进行位置的设定。代码如下:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
//设置ViewGroup的高度,高度为所有子View之和。
MarginLayoutParams params = (MarginLayoutParams) getLayoutParams();
params.height = mScreenHeigth * childCount;
setLayoutParams(params);
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
//设置子View的位置
child.layout(l, i * mScreenHeigth, r, (i + 1) * mScreenHeigth);
}
}
}
3.通过上面步骤,就可以将View放置到ViewGroup中,但此时的ViewGroup还不能响应任何事件。自然也不能滑动。因此需要重写onTouchEvent()方法。在ViewGroup中添加滑动事件,通常可以使用scrollBy(0,dy) 方法,让手指滑动的时候让ViewGroup中的所有子View也滑动dy.
@Override
public boolean onTouchEvent(MotionEvent event) {
int y = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = y;
//记录触摸起点
break;
case MotionEvent.ACTION_MOVE:
//判断是否在滑动中,如果是在滑动中,这个动画不执行
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
//获取到手势移动的距离
int dy = lastY - y;
//如果是第一个子View时,不能向下滑动
if (getScrollY() < 0) {
dy = 0;
}
//如果是第最后一个子View时,不能向上滑动
if (getScrollY() > getHeight() - mScreenHeigth) {
dy = 0;
}
//视图滚动
scrollBy(0, dy);
lastY = y;
break;
//这里需要处理这个点击事件,因此返回true
}
return true;
}
4.通过上面的代码已经实现滑动,但是我们在使用这个控件的时候,和ScrollView不同,所有的子View直接放到这个ViewGroup中。而且,使用子View使用match_parent属性。使用具体数值的时候会出现,下拉不松开上推,在下拉,松开后再无法进行滑动。这是因为getScrollY变为了负值。这时需要在getScrollY()+dy就可以了。
5.最后我们来实现粘性效果
代码如下:
case MotionEvent.ACTION_UP:
//记录触摸终点
mEnd = getScrollY();
int dScrollY = mEnd - mStart;
//最关键的一些判断
if (dScrollY > 0) {
if (dScrollY < mScreenHeigth / 3) {
mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
} else {
mScroller.startScroll(0, getScrollY(), 0, mScreenHeigth - dScrollY);
}
} else {
if (-dScrollY < mScreenHeigth / 3) {
mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
} else {
mScroller.startScroll(0, getScrollY(), 0, -mScreenHeigth - dScrollY);
}
}
break;
完整代码
@Override
public boolean onTouchEvent(MotionEvent event) {
int y = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = y;
//记录触摸起点
mStart = getScrollY();
break;
case MotionEvent.ACTION_MOVE:
//判断是否在滑动中,如果是在滑动中,这个动画不执行
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
//获取到手势移动的距离
int dy = lastY - y;
if (getScrollY() < 0) {
dy = 0;
}
if (getScrollY() > getHeight() - mScreenHeigth) {
dy = 0;
}
//视图滚动
scrollBy(0, dy);
lastY = y;
break;
case MotionEvent.ACTION_UP:
//记录触摸终点
mEnd = getScrollY();
int dScrollY = mEnd - mStart;
if (dScrollY > 0) {
if (dScrollY < mScreenHeigth / 3) {
mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
} else {
mScroller.startScroll(0, getScrollY(), 0, mScreenHeigth - dScrollY);
}
} else {
if (-dScrollY < mScreenHeigth / 3) {
mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
} else {
mScroller.startScroll(0, getScrollY(), 0, -mScreenHeigth - dScrollY);
}
}
break;
}
postInvalidate();
//这里需要处理这个点击事件,因此返回true
return true;
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
postInvalidate();
}
}
到这里就实现了我们想要的效果。