弹性ScrollView


本文出处:

实现原理

    View中也有scrollBy和scrollTo这两个方法,但是ScrollView对scrollTo进行重写  
    由于:public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }
    View:public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                invalidate();
            }
        }
    }
    所以也就相当于scrollBy和scrollTo这两个方法都被重写了。重写的代码中加入校验,当你移动到最上面或者最下面的时候无法再向上移动或向下移动。这就导致了如果简单调用scrollTo无法实现继续移动。如果你还要继续移动的话mScrollY 就为0或者是你内部视图的测量高度-ScrollView的高度。scrollTo是移动到,scrollBy是移动了。如下图:
   
                所以内容向下移动,手指向上滑动,那这个deltaY 就为正,也就是mScrollY =mScrollY +deltaY ,mScrollY 变大直到移动到最下无法移动位置;反之mScrollY 变小直到为0移动到最上面为止。
                final float preY = y;
                float nowY = ev.getY();
                int deltaY = (int) (preY - nowY);
                // 滚动,deltaY 为移动的距离,如果要用scrollTo需要计算准确的位置,也就是先前的位置在加上现在移动了多少
                scrollBy(0, deltaY);
               基于这些就可以重写ScrollView的onTouchEvent并结合ScrollView的内部视图的layout()方法、TranslateAnimation()实现反弹效果。
ScrollView代码:



具体代码


public class ElasticScrollView extends ScrollView {
 private View inner;
 private float y;
 private Rect normal = new Rect();
 private boolean animationFinish = true;

 public ElasticScrollView(Context context) {
  super(context);
 }

 public ElasticScrollView(Context context, AttributeSet attrs) {
  super(context, attrs);
 }

 @Override
 protected void onFinishInflate() {
  if (getChildCount() > 0) {
   inner = getChildAt(0);
  }
 }
 
 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
  return super.onInterceptTouchEvent(ev);
 }

 @Override
 public boolean onTouchEvent(MotionEvent ev) {
  if (inner == null) {
   return super.onTouchEvent(ev);
  } else {
   commOnTouchEvent(ev);
  }
  return super.onTouchEvent(ev);
 }

 public void commOnTouchEvent(MotionEvent ev) {
  if (animationFinish) {
   int action = ev.getAction();
   switch (action) {
   case MotionEvent.ACTION_DOWN:
//    System.out.println("ACTION_DOWN");
    y = ev.getY();
    super.onTouchEvent(ev);
    break;
   case MotionEvent.ACTION_UP:
//    System.out.println("ACTION_UP");
    y = 0;
    if (isNeedAnimation()) {
     animation();
    }
    super.onTouchEvent(ev);
    break;
   case MotionEvent.ACTION_MOVE:
//    System.out.println("ACTION_MOVE");
    final float preY = y == 0 ? ev.getY() : y;
    float nowY = ev.getY();
    int deltaY = (int) (preY - nowY);
    // 滚动
//    scrollBy(0, deltaY);

    y = nowY;
    // 当滚动到最上或者最下时就不会再滚动,这时移动布局
    if (isNeedMove()) {
     if (normal.isEmpty()) {
      // 保存正常的布局位置
      normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom());
     }
     // 移动布局
     inner.layout(inner.getLeft(), inner.getTop() - deltaY / 2, inner.getRight(), inner.getBottom() - deltaY / 2);
    } else {
     super.onTouchEvent(ev);
    }
    break;
   default:
    break;
   }
  }
 }

 // 开启动画移动

 public void animation() {
  // 开启移动动画
  TranslateAnimation ta = new TranslateAnimation(0, 0, 0, normal.top - inner.getTop());
  ta.setDuration(200);
  ta.setAnimationListener(new AnimationListener() {
   @Override
   public void onAnimationStart(Animation animation) {
    animationFinish = false;

   }

   @Override
   public void onAnimationRepeat(Animation animation) {

   }

   @Override
   public void onAnimationEnd(Animation animation) {
    inner.clearAnimation();
    // 设置回到正常的布局位置
    inner.layout(normal.left, normal.top, normal.right, normal.bottom);
    normal.setEmpty();
    animationFinish = true;
   }
  });
  inner.startAnimation(ta);
 }

 // 是否需要开启动画
 public boolean isNeedAnimation() {
  return !normal.isEmpty();
 }

 // 是否需要移动布局
 public boolean isNeedMove() {
  int offset = inner.getMeasuredHeight() - getHeight();
  int scrollY = getScrollY();
  if (scrollY == 0 || scrollY == offset) {
   return true;
  }
  return false;
 }

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值