前言
一直对Scroller这个类不太熟悉,之前老是在网上找着看,但是过不了多长时间后就忘记了,今天来整理一下
先看一下Scroller里面的方法:
http://api.apkbus.com/reference/android/widget/Scroller.html
说明
为了理解方便,拿SlideView来做说明,关于SlideView的demo网上有很多,这里为了讲解主要贴出SlideView:
public class SlideView extends LinearLayout {
private Context mContext;
private LinearLayout mViewContent;
private LinearLayout mHolder;
private TextView tv_delete;
// 弹性滑动对象,实现View平滑滚动的一个帮助类
private Scroller mScroller;
// 滑动回调接口,用来向上层通知滑动事件
private OnSlideListener mOnSlideListener;
private int mHolderWidth = 100;
private int mLastX = 0;
private int mLastY = 0;
private static final int TAN = 2;
public SlideView(Context context) {
super(context);
initView();
}
public SlideView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView(){
mContext = getContext();
mScroller = new Scroller(mContext);
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
//将R.layout.slide_view 添加到this, View view=View.inflate(this,R.layout.*,null);是生成一个新的View
View.inflate(mContext, R.layout.slide_view, this);
mViewContent = (LinearLayout)findViewById(R.id.view_content);
mHolder = (LinearLayout)findViewById(R.id.holder);
tv_delete = (TextView)findViewById(R.id.delete);
}
public void setButtonText(CharSequence text){
tv_delete.setText(text);
}
public void setContentView(View view){
mViewContent.addView(view);
}
public void onRequireTouchEvent(MotionEvent event){
// 获取点击的坐标
int x = (int)event.getX();
int y = (int)event.getY();
//当前view的左上角相对于母视图的左上角的X轴偏移量。
int scrollX = getScrollX();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if(!mScroller.isFinished()){
mScroller.abortAnimation();
}
if(mOnSlideListener != null){
mOnSlideListener.onSlide(this, OnSlideListener.SLIDE_STATUS_START_SCROLL);
}
break;
case MotionEvent.ACTION_MOVE:
//增量
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if(Math.abs(deltaX) < Math.abs(deltaY)*TAN){
// 滑动不满足条件 不做横向滑动
break;
}
/**
* 1. 这个SlideView相对于上次偏移的距离减去手指这次move的相对于上次move的增量(相对偏移量?)
*
* 2. 举个例子:如果现在手指滑动,ACTION_DOWN的坐标为(100,0),现在开始MOVE滑动(ACTION_MOVE)第一个MOVE的坐标为(50,0)
*
* 即deltaX=50-100,第一个MOVE时scrollX = 0,newScrollX = scrollX - deltaX;newScrollX = 50
* this.scrollTo(newScrollX, 0);view是偏移50,
*
* 第二个MOVE的到达坐标为(10,0),而mLastX是第一个MOVE的x坐标(mLastX = 50)
* deltaX=10-50 ,deltaX = -40 ;scrollX = 50 newScrollX = scrollX - deltaX;newScrollX = 90
* this.scrollTo(newScrollX, 0);view是偏移90,
*
*/
int newScrollX = scrollX - deltaX;
if(deltaX != 0){
if(newScrollX < 0){
newScrollX = 0;
}else if(newScrollX > mHolderWidth){
newScrollX = mHolderWidth;
}
//会触发computeScroll()
this.scrollTo(newScrollX, 0);
}
break;
case MotionEvent.ACTION_UP:
int newScrollx = 0;
//如果SlideView的偏移量大于默认要滑动距离的3/4,newScrollx=mHolderWidth,否则 newScrollx = 0;
if(scrollX - mHolderWidth*0.75 > 0){
newScrollx = mHolderWidth;
}
this.smoothScrollTo(newScrollx, 0);
// 通知上层滑动事件
if(mOnSlideListener != null){
mOnSlideListener.onSlide(this, newScrollx == 0 ? OnSlideListener.SLIDE_STATUS_OFF
: OnSlideListener.SLIDE_STATUS_ON);
}
break;
default:
break;
}
mLastX = x;
mLastY = y;
}
/**
* 调用此方法滚动到目标位置
* @param fx 目标x坐标
* @param fy 目标Y坐标
*/
private void smoothScrollTo(int fx, int fy){
int scrollX = getScrollX();
int dx = fx - scrollX;
int scrollY = getScrollY();
int dy = fy - scrollY;
//手指up之后从现在的位置偏移到scrollX+dx位置,dx是增量,触发computeScroll
mScroller.startScroll(scrollX, scrollY, dx, dy, Math.abs((dx)*3));
invalidate();
}
/**
* 由mScroller记录/计算好View滚动的位置后,最后由View的computeScroll(),完成实际的滚动
*/
@Override
public void computeScroll() {
//先判断mScroller滚动是否完成
if(mScroller.computeScrollOffset()){
//这里调用View的scrollTo()完成实际的滚动
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//必须调用该方法,否则不一定能看到滚动效果
postInvalidate();
}
super.computeScroll();
}
/**
* 设置滑动回调
* @param onSlideListener
*/
public void setOnSlideListener(OnSlideListener onSlideListener){
this.mOnSlideListener = onSlideListener;
}
public interface OnSlideListener {
public static final int SLIDE_STATUS_OFF = 0;
public static final int SLIDE_STATUS_START_SCROLL = 1;
public static final int SLIDE_STATUS_ON = 2;
public void onSlide(View view, int status);
}
}
先说一下实现的具体思路,SlideView是一个横向LinearLayout,作为父布局,它里面有两个child(当然可以更多),第一个child设置宽度充满整个父布局,这样的第二个child就会被挤出屏幕外面,scrollTo()和scrollBy()方法移动的是View的content,即让View的内容移动,如果在ViewGroup中使用scrollTo()和scrollBy()方法,那么移动的将是所有的子View,如果在View中使用,那么移动的将是View的内容。例如,TextView,content就是它的文本,ImageView,content就是它的drawable对象。
具体讲解一下:
1.ACTION_DOWN:mScroller停止动画,这个没什么说的
2.ACTION_MOVE:举个例子:SlideView已经偏移了scrollX,scrollTo是相对于原点偏移,getScrollX却是向对于开始时的位置
3.ACTION_UP:在UP之前可以看到mScroller没有做任何关于滚动的的动作,up的时候调用了
private void smoothScrollTo(int fx, int fy){
int scrollX = getScrollX();
int dx = fx - scrollX;
int scrollY = getScrollY();
int dy = fy - scrollY;
//手指up之后从现在的位置偏移到scrollX+dx位置,dx是增量,触发computeScroll
mScroller.startScroll(scrollX, scrollY, dx, dy, Math.abs((dx)*3));
invalidate();
}
触发了computeScroll()
@Override
public void computeScroll() {
//先判断mScroller滚动是否完成
if(mScroller.computeScrollOffset()){
//这里调用View的scrollTo()完成实际的滚动
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//必须调用该方法,否则不一定能看到滚动效果
postInvalidate();
}
super.computeScroll();
}
computeScroll里面做了什么??先mScroller.computeScrollOffset()判断mScroller滚动是否完成,computeScrollOffset做了什么呢?
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
float x = timePassed * mDurationReciprocal;
if (mInterpolator == null)
x = viscousFluid(x);
else
x = mInterpolator.getInterpolation(x);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
}
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}
可以看出int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);mStartTime = AnimationUtils.currentAnimationTimeMillis();获取过了多长时间,
然后if (timePassed < mDuration) ,看一下有没有到达设置的时间,
然后一通计算mCurrX = mStartX + Math.round(x * mDeltaX);(计算是为了在mDuration时间内完成滚动dx)得到mCurrX
,然后回到computeScroll(), scrollTo(mScroller.getCurrX(), mScroller.getCurrY());在次偏移mCurrX ,
然后回调computeScroll,直到滚动结束
注意:scrollTo(),如果第一次你在x轴向左上偏移了10,下次再向左偏移10的话,相当于较之原点你已经移到了20,所以说getscrollx也是基于开始位置的偏移,现在getscrollx是20,scrollBy()是相对于你上次移动