新建仿ios可上提下拉ScrollView的Java类,直接在xml布局文件中包含所需控件即可
package com.zhansy.mytext;
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.ScrollView;
//仿ios可上提下拉的ScrollView
public class MyScrollowView extends ScrollView {
private static final String TAG = "ElasticScrollView";
//移动因子, 是一个百分比, 比如手指移动了100px, 那么View就只移动50px
//目的是达到一个延迟的效果
private static final float MOVE_FACTOR = 0.5f;
//松开手指后, 界面回到正常位置需要的动画时间
private static final int ANIM_TIME = 300;
//ScrollView的子View, 也是ScrollView的唯一一个子View
private View contentView;
//手指按下时的Y值, 用于在移动时计算移动距离
//如果按下时不能上拉和下拉, 会在手指移动时更新为当前手指的Y值
private float startY;
//用于记录正常的布局位置
private Rect originalRect = new Rect();
//手指按下时记录是否可以继续下拉
private boolean canPullDown = false;
//手指按下时记录是否可以继续上拉
private boolean canPullUp = false;
//在手指滑动的过程中记录是否移动了布局
private boolean isMoved = false;
//滚动监听
private OnScrollListener srollListenter;
/**
* 主要是用在用户手指离开MyScrollView,MyScrollView还在继续滑动,我们用来保存Y的距离,然后做比较
*/
private int lastScrollY;
public MyScrollowView(Context context) {
super(context);
}
public MyScrollowView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
if (getChildCount() > 0) {
contentView = getChildAt(0);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(contentView == null) return;
//ScrollView中的唯一子控件的位置信息, 这个位置信息在整个控件的生命周期中保持不变
originalRect.set(contentView.getLeft(), contentView.getTop(), contentView
.getRight(), contentView.getBottom());
}
//在触摸事件中, 处理上拉和下拉的逻辑
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (contentView == null) {
return super.dispatchTouchEvent(ev);
}
// int action = ev.getAction();
//
// switch (action) {
// case MotionEvent.ACTION_DOWN:
//
// //判断是否可以上拉和下拉
// canPullDown = isCanPullDown();
// canPullUp = isCanPullUp();
//
// //记录按下时的Y值
// startY = ev.getY();
// break;
//
// case MotionEvent.ACTION_UP:
//
// if(!isMoved) break; //如果没有移动布局, 则跳过执行
//
// // 开启动画
// TranslateAnimation anim = new TranslateAnimation(0, 0, contentView.getTop(),
// originalRect.top);
// anim.setDuration(ANIM_TIME);
//
// contentView.startAnimation(anim);
//
// // 设置回到正常的布局位置
// contentView.layout(originalRect.left, originalRect.top,
// originalRect.right, originalRect.bottom);
//
// //将标志位设回false
// canPullDown = false;
// canPullUp = false;
// isMoved = false;
//
// break;
// case MotionEvent.ACTION_MOVE:
//
// //在移动的过程中, 既没有滚动到可以上拉的程度, 也没有滚动到可以下拉的程度
// if(!canPullDown && !canPullUp) {
// startY = ev.getY();
// canPullDown = isCanPullDown();
// canPullUp = isCanPullUp();
//
// break;
// }
//
// //计算手指移动的距离
// float nowY = ev.getY();
// int deltaY = (int) (nowY - startY);
//
// //是否应该移动布局
// boolean shouldMove =
// (canPullDown && deltaY > 0) //可以下拉, 并且手指向下移动
// || (canPullUp && deltaY< 0) //可以上拉, 并且手指向上移动
// || (canPullUp && canPullDown); //既可以上拉也可以下拉(这种情况出现在ScrollView包裹的控件比ScrollView还小)
//
// if(shouldMove){
// //计算偏移量
// int offset = (int)(deltaY * MOVE_FACTOR);
//
// //随着手指的移动而移动布局
// contentView.layout(originalRect.left, originalRect.top + offset,
// originalRect.right, originalRect.bottom + offset);
//
// isMoved = true; //记录移动了布局
// }
//
// break;
// default:
// break;
// }
return super.dispatchTouchEvent(ev);
}
/**
* 用于用户手指离开MyScrollView的时候获取MyScrollView滚动的Y距离,然后回调给onScroll方法中
*/
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
int scrollY = MyScrollowView.this.getScrollY();
//此时的距离和记录下的距离不相等,在隔5毫秒给handler发送消息
if(lastScrollY != scrollY){
lastScrollY = scrollY;
handler.sendMessageDelayed(handler.obtainMessage(), 5);
}
if( srollListenter!= null){
srollListenter.onScroll(scrollY);
}
};
};
@Override
public boolean onTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
//
lastScrollY = this.getScrollY();
// if(srollListenter!= null){
// srollListenter.onScroll(lastScrollY = this.getScrollY());
// }
// switch(ev.getAction()){
// case MotionEvent.ACTION_UP:
// handler.sendMessageDelayed(handler.obtainMessage(), 5);
// break;
// }
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//判断是否可以上拉和下拉
canPullDown = isCanPullDown();
canPullUp = isCanPullUp();
//记录按下时的Y值
startY = ev.getY();
break;
case MotionEvent.ACTION_UP:
if(!isMoved) break; //如果没有移动布局, 则跳过执行
// 开启动画
TranslateAnimation anim = new TranslateAnimation(0, 0, contentView.getTop(),
originalRect.top);
anim.setDuration(ANIM_TIME);
contentView.startAnimation(anim);
// 设置回到正常的布局位置
contentView.layout(originalRect.left, originalRect.top,
originalRect.right, originalRect.bottom);
//将标志位设回false
canPullDown = false;
canPullUp = false;
isMoved = false;
break;
case MotionEvent.ACTION_MOVE:
//在移动的过程中, 既没有滚动到可以上拉的程度, 也没有滚动到可以下拉的程度
if(!canPullDown && !canPullUp) {
startY = ev.getY();
canPullDown = isCanPullDown();
canPullUp = isCanPullUp();
break;
}
canPullDown = isCanPullDown();
canPullUp = isCanPullUp();
//计算手指移动的距离
float nowY = ev.getY();
int deltaY = (int) (nowY - startY);
//是否应该移动布局
boolean shouldMove =
(canPullDown && deltaY > 0) //可以下拉, 并且手指向下移动
|| (canPullUp && deltaY< 0) //可以上拉, 并且手指向上移动
|| (canPullUp && canPullDown); //既可以上拉也可以下拉(这种情况出现在ScrollView包裹的控件比ScrollView还小)
if(shouldMove){
//计算偏移量
int offset = (int)(deltaY * MOVE_FACTOR);
//随着手指的移动而移动布局
contentView.layout(originalRect.left, originalRect.top + offset,
originalRect.right, originalRect.bottom + offset);
isMoved = true; //记录移动了布局
}
break;
default:
break;
}
return super.onTouchEvent(ev);
}
@Override
protected int computeVerticalScrollOffset() {
// TODO Auto-generated method stub
handler.sendMessageDelayed(handler.obtainMessage(), 5);
return super.computeVerticalScrollOffset();
}
//判断是否滚动到顶部
private boolean isCanPullDown() {
return getScrollY() == 0 ||
contentView.getHeight() < getHeight() + getScrollY();
}
//判断是否滚动到底部
private boolean isCanPullUp() {
return contentView.getHeight() <= getMeasuredHeight() + getScrollY();
}
public void setOnScrollListener(OnScrollListener onscrolllistener){
this.srollListenter = onscrolllistener;
}
/**
* 滚动的回调接口
* @author chc
*
*/
public interface OnScrollListener{
/**
* 回调方法, 返回MyScrollView滑动的Y方向距离
* @param scrollY
* 、
*/
public void onScroll(int scrollY);
}
}
xml文件:
<com.zhansy.mytext.MyScrollowView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/hello_world"
android:textSize="20sp" />
</RelativeLayout>
</com.zhansy.mytext.MyScrollowView>