这里写代码片#Android上仿IOS弹性ScrollView
滑动的原理:由于在ACTION_MOVE
事件中不断获取手指移动的微小的偏移量,这样就将一段距离划分成了N个非常小的偏移量。虽然在每个偏移量里面,通过scrollBy方法进行了瞬间移动,但在整体上却可以获得一个平滑移动效果。
阻尼效果的实现原理:比如你下拉50dp,innerView只移动25dp。从效果上看就好像有个力在阻挠你下拉。
难点:
1. 移动过程中的处理:
MotionEvent.ACTION_MOVE
中,在手指移动过程中,ev.getY()会不断获取当前手指的y坐标。
通过不断的计算前一次y的坐标和现在y坐标的偏移量detailY
,然后把detailY
在传到innerView.layout
中,从而实现了下拉的效果。
2.Rect normal = new Rect()
的作用?
用于记录innerView的原始位置。具体逻辑:在MotionEvent.ACTION_MOVE
中判断normal
为空,把innerView
的四个点存起来。然后在MotionEvent.ACTION_UP
中,即让innerView
弹回原位的动画结束后,为innerView
提供原始位置,并令normal
再次为空。
3.isNeedMove()
方法
作用:判断是否触发弹性移动效果
innerView.getMeasuredHeight()
:获取的是控件的总高度
getHeight()
:获取的是屏幕的高度
当控件的高度<屏幕的高度时候,getScrollY
始终为0.
当控件的高度>屏幕的高度时候,上下滑动innerView
时,getScrollY
才会有变化的值。
针对第一种情况。只要scrollY == 0
就会触发效果。
第二种情况。下拉的触发效果的条件scrollY == 0
,上拉触发效果的条件是scrollY == offset
private boolean isNeedMove() {
int offset = innerView.getMeasuredHeight() - getHeight();
int scrollY = getScrollY();
// 0是顶部,后面那个是底部
if (scrollY == 0 || scrollY == offset) {
return true;
}
return false;
}
代码:
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.ScrollView;
/**
* Created by Administrator on 2015/12/13.
*/
public class MyScrollview extends ScrollView {
//要操作的布局 孩子View
private View innerView;
// 点击时y坐标
private float y;
// 矩形(用于保存原innerView的四点坐标.)
private Rect normal = new Rect();
//用于记录动画是否结束
private boolean animationFinish = true;
public MyScrollview(Context context) {
super(context, null);
}
public MyScrollview(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyScrollview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/***
* 根据 XML 生成视图工作完成.该函数在生成视图的最后调用,在所有子视图添加完之后. 即使子类覆* 盖了 onFinishInflate 方法,也应该调用父类的方法,使该方法得以执行.
*/
@Override
protected void onFinishInflate() {
int childCount = getChildCount();
if (childCount > 0) {
innerView = getChildAt(0);
}
}
// 首先要保存父类ScrollView的onTouchEvent事件,在这基础上加入回弹的逻辑
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (innerView == null) {
return super.onTouchEvent(ev);
} else {
commonTouchEvent(ev);
}
return super.onTouchEvent(ev);
}
/**
* 自定义touch事件处理
*
* @param ev
*/
private void commonTouchEvent(MotionEvent ev) {
if (animationFinish) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
y = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float preY = y;
float nowY = ev.getY();
int detailY = (int) (preY - nowY);
y = nowY;
//操作view进行拖动detailY的一半
if (isNeedMove()) {
//布局改变位置之前,记录一下正常状态的位置
if (normal.isEmpty()) {
normal.set(innerView.getLeft(), innerView.getTop(), innerView.getRight(), innerView.getBottom());
}
innerView.layout(innerView.getLeft(), innerView.getTop() - detailY / 2, innerView.getRight(), innerView.getBottom() - detailY / 2);
}
break;
case MotionEvent.ACTION_UP:
y = 0;
//手指松开 布局回滚到原来的位置.
if (isNeedAnimation()) {
animation();
}
break;
}
}
}
private void animation() {
// 开启移动动画
TranslateAnimation ta = new TranslateAnimation(0, 0, 0, normal.top - innerView.getTop());
ta.setDuration(200);
ta.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
animationFinish = false;
}
@Override
public void onAnimationEnd(Animation animation) {
innerView.clearAnimation();
// 设置回到正常的布局位置
innerView.layout(normal.left, normal.top, normal.right, normal.bottom);
normal.setEmpty();
animationFinish = true;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
innerView.startAnimation(ta);
}
/**
* 判断是否需要回滚 是否需要开启动画
*
* @return
*/
private boolean isNeedAnimation() {
return !normal.isEmpty();
}
/**
* 判断是否需要移动 innerView.getMeasuredHeight():获取的是控件的总高度
* getHeight():获取的是屏幕的高度
* @return
*/
private boolean isNeedMove() {
int offset = innerView.getMeasuredHeight() - getHeight();
int scrollY = getScrollY();
// 0是顶部,后面那个是底部
if (scrollY == 0 || scrollY == offset) {
return true;
}
return false;
}
}