还是先说一些废话在前边,以前总是喜欢拿来主义,遇到问题先从网上找.现在到了该还的时候了,希望大家能够从中汲取有用内容,以后回报伟大的我党我国…
上一节我们学习了如何实现一个自定义控件的基本框架.
简单来说,就是定义本控件想要对外提供的功能接口.
这一节我们就来重点实现其中的一些关键内容:
如何实现让布局中的子控件向下或向上移动,同时松手后还能平滑的滚动回去.
预备知识:
1. View.scrollTo()
2. View.scrollBy()
3. View.onTouchEvent()
4. Scroller类
View类通过调用 scrollTo或scrollBy能够实现其中的子控件立即滚动一段距离,但是非常生硬.是跳跃性的,比如scrollBy(0,100). 则内容瞬间会移动100距离. 如何让我们的子控件平滑位移呢?这个就需要借助Scroller类.
关于Scroller的基本原理大家可以去百度,这里不作讲解,只需要记住,借助Scroller对象可以实现平滑的位移效果即可.
这一节主要实现两个目标:
1. SuperRefreshLayout中的子控件跟随手指的移动而移动(重写onTouchEvent).
2. 松开手指后子控件能够平滑返回到最初位置(用到Scroller).
第一个目标:
基本原理是:监听onTouchEvent事件,记录每次Move的距离,并调用scrollBy进行位移.不过这里更简便的方法是直接使用手势辅助类:GestureDectector
// 声明GestureDetector成员,并在onInit()中初始化.
private GestureDetector mGesture;
// 实现初始化内容.
private void onInit() {
mGesture = new GestureDetector(getContext(), mGestureListener);
}
@Override // 监听本控件的触摸事件,并交给手势类去识别.这可以省去很多变量的声明和控制.
public boolean onTouchEvent(MotionEvent event) {
return mGesture.onTouchEvent(event);
}
// 手势监听,只需要实现滚动监听以及松手监听.
private OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
scrollBy(0, (int) distanceY);//沿Y轴滚动相应距离.
return false;
}
//必须返回true.表示这个事件不再交给子控件处理了,自己要处理.否则onScroll是执行不到的.
public boolean onDown(MotionEvent e) {
return true;
};
};
现在测试一下,可以发现子控件已经能够根据手指上下移动了.
下面处理松开手指的动作.即ACTION_UP事件.我们要做的时想让子控件再平滑回到原位.
已知条件:控件位移距离.通过getScrollY()可以得到.
要注意的是,界面坐标系的问题,getScrollY()大于0表示向上移动了,<0表示向下移动了.
由于 GestureDetector并没有处理提供onUp回调,因此我们只能在onTouchEvent中增加ACTION_UP处理.
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction()== MotionEvent.ACTION_UP){
onActionUp();
return true;
}
return mGesture.onTouchEvent(event);
}
private void onActionUp(){
}
要实现平滑滚动回原位,类似于很多控件的反弹效果就要用到Scroller类了.
来看具体实现:
private Scroller mScroller = null;
// 实现初始化内容.
private void onInit() {
mGesture = new GestureDetector(getContext(), mGestureListener);
// mScroller =new Scroller(getContext());//要模拟出弹簧的那种加速递减的效果,需要下面这个构造.减速运动.
mScroller = new Scroller(getContext(), new DecelerateInterpolator(3));
}
// 控件滚动回到原来的位置.
private void scrollBack() {
//向反方向滚动getScrollY()的距离.因此是负getScrollY();
smoothScroll(getScrollX(), - getScrollY() );
}
// 平滑滚动多少距离.
private void smoothScroll(int dx, int dy) {
// 要求速度为500px/250ms.即2px/ms. 所以实际时间计算为dy/2;当然这个是算的匀速值,其实应该带上加速度..
mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, Math.abs(dy/2));
invalidate();// mScroller其实只是一个位置启记录仪,必须要引起界面刷新才能执行真正的滚动.
}
@Override // 调用scrollTo或者inValidate会引起系统调用 computeScroll();
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
// 在这里mScroller能够告诉你是否已经滚动到目标位置了,如果没有,就应该继续滚动.
scrollTo(0, mScroller.getCurrY());
}
}
private void onActionUp() {
// Log.i(TAG, getScrollY() + "");
scrollBack();
}
以上两步就实现了一个可以过度上拉,下拉并且能够弹回原位的自定义控件了.
那么接下来,我们需要研究最核心的一个问题,判定什么时候才能下拉,什么时候才能上拉.比如ListView当我们滚动到顶部的时候,再继续向下拖就应该触发下拉的动作,或者滚动到底部的时候,再继续向上拖就应该触发上拉的动作.
具体什么控件该如何判定,我们留待下一节讲解.