1.Scroller类是为了实现 View 平滑滚动的一个Helper类
(自定义view种经常用到)
既然是helper类:
所以 只是记录了位置,设置,配置,需要用到时取出来就好了
所以Scroller 不要想太多,其实没什么的,就当成一个工具类就好(提供的辅助滑动的工具类),主要是他要 怎么样才能结合view 使用
需要注意:滚动的是 View 中的 子view,并不是view本身
mScroller.getCurrX() //获取mScroller当前水平滚动的位置
mScroller.getCurrY() //获取mScroller当前竖直滚动的位置
mScroller.getFinalX() //获取mScroller最终停止的水平位置
mScroller.getFinalY() //获取mScroller最终停止的竖直位置
mScroller.setFinalX(int newX) //设置mScroller最终停留的水平位置,没有动画效果,直接跳到目标位置
mScroller.setFinalY(int newY) //设置mScroller最终停留的竖直位置,没有动画效果,直接跳到目标位置
//滚动,startX, startY为开始滚动的位置,dx,dy为滚动的偏移量, duration为完成滚动的时间
mScroller.startScroll(int startX, int startY, int dx, int dy) //使用默认完成时间250ms
mScroller.startScroll(int startX, int startY, int dx, int dy, int duration)
mScroller.extendDuration (int extend)//延长滚动多长时间
mScroller.abortAnimation() //停止,并立即运动到finalx,y
// 当想要知道新的位置时,调用此函数。如果返回true,表示动画还没有结束。位置改变以提供一个新的位置。
mScroller.computeScrollOffset() //返回值为boolean,true说明动画尚未完成,false说明动画已经完成。这是一个很重要的方法,通常放在View.computeScroll()中,用来判断是否滚动是否结束
//返回滚动事件的持续时间,以毫秒计算。
public final int getDuration ()
/** startX 滚动起始点X坐标
startY 滚动起始点Y坐标
velocityX 当滑动屏幕时X方向初速度,以每秒像素数计算
velocityY 当滑动屏幕时Y方向初速度,以每秒像素数计算
minX X方向的最小值,scroller不会滚过此点。
maxX X方向的最大值,scroller不会滚过此点。
minY Y方向的最小值,scroller不会滚过此点。
maxY Y方向的最大值,scroller不会滚过此点。
*/
public void fling (int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)
//返回滚动结束位置。仅针对“fling”手势有效
public final int getFinalX ()
//返回滚动结束位置。仅针对“fling”操作有效
public final int getFinalY ()
// 返回scroller是否已完成滚动,停止滚动返回true,否则返回false
public final boolean isFinished ()
//返回自滚动开始经过的时间(毫秒)
public int timePassed ()
2. View , ViewGroup 的结合使用
当自定义view 继承 View 或 ViewGroup 时罗列一下,
自定义如果需要移动等效果,常用到的几个方法,这些都是view 提供的,当继承后,可以利用达到自己想要的移动,滚动等效果
//获得当前的偏移量 x
public final int getScrollX() {
return mScrollX;
}
//获得当前的偏移量 y
public final int getScrollY() {
return mScrollY;
}
//视图内容相当于视图起始坐标的偏移量 ,X轴 方向
protected int mScrollX;
//该视图内容相当于视图起始坐标的偏移量 ,Y轴方向
protected int mScrollY;
// 移动到哪里,x,y 需要给出,也就会相应的调用onScrollChanged方法,并且view 会刷新(以前的视图无效)
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
//回调onScrollChanged方法
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
// 在本来的偏移量上再移动多少
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
// computeScroll 是个空方法
// 基本的解释就是 父View 需要子View 去更新 mScrollX,mScrollY 的时候会去调用,如果孩子用着 scroll,调用了invalidate()方法后,也就会相应的调用到这个方法,这2个方法的配合很重要。
/**
* Called by a parent to request that a child update its values for mScrollX
* and mScrollY if necessary. This will typically be done if the child is
* animating a scroll using a {@link android.widget.Scroller Scroller}
* object.
*/
public void computeScroll() {
}
// 让界面刷新时,需要用到的 invalidate
/**
* This is where the invalidate() work actually happens. A full invalidate()
* causes the drawing cache to be invalidated, but this function can be
* called with invalidateCache set to false to skip that invalidation step
* for cases that do not need it (for example, a component that remains at
* the same dimensions with the same content).
*
* invalidateCache Whether the drawing cache for this view should be
* invalidated as well. This is usually true for a full
* invalidate, but may be set to false if the View’s contents or
* dimensions have not changed.
*/
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(invalidateCache);
return;
}
if (skipInvalidate()) {
return;
}
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~PFLAG_DRAWN;
}
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
// Damage the entire projection receiver, if necessary.
if (mBackground != null && mBackground.isProjected()) {
final View receiver = getProjectionReceiver();
if (receiver != null) {
receiver.damageInParent();
}
}
// Damage the entire IsolatedZVolume receiving this view's shadow.
if (isHardwareAccelerated() && getZ() != 0) {
damageShadowReceiver();
}
}
}
3. 配合的实现方式
需要先设想滑动的效果,需要移动到哪里等,然后塞入工具类 scroll种,
真正调用view 的方法实现时,再取出调用
例:
1.//需要创建工具类
Scroller mScroller = new Scroller(mContext);
2.
private void myScrollTo(int destX, int destY) {
//先获取现在的view的偏移量 x
int scrollX = getScrollX();
//计算出在现在的偏移量 x 的基础上, 我还要继续便宜多少
int delta = destX - scrollX;
//这里看着是 startScroll 其实并没有真正的滑动,只是工具类Scroller记录了位置,执行时间等参数
mScroller.startScroll(scrollX, 0, delta, 0, Math.abs(delta) * 3);
// mScroller.startScroll(scrollX, 0, delta, 0, 5000);
//当调用了 invalidate 会立即调用computeScroll这个空方法,所以我们要在这个空方法实现我们的真正的效果
invalidate();
}
3. 会一直调用这个方法
//父view 要求他内部的子view的mScrollX和mScrollY发生变化时,通常情况下子view使用Scroller对象进行动画滚动.
@Override
public void computeScroll() {
//true 说明滚动未完成 false 说明滚动完成
boolean a = mScroller.computeScrollOffset();
System.out.println("==========a======"+ a);
if (a) {
//调用 View的 scrollTo方法开始滚动或滑动效果,参数则是从 Scroll这个工具类种取出来的
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
4. 总结:整个流程
View - (预设工具类) scroll.startScroll() - (刷新)invalidate() - (view重新绘制调用 ,一直重复调用哦)computeScroll - (判断是否执行完,是上面重复调用的关键)mScroller.computeScrollOffset()
网上的一个代码提供学习
public class SlideView extends LinearLayout {
private static final String TAG = "SlideView";
private Context mContext;
private LinearLayout mViewContent;
private RelativeLayout mHolder;
private Scroller mScroller;
private OnSlideListener mOnSlideListener;
private int mHolderWidth = 120;
private int mLastX = 0;
private int mLastY = 0;
private static final int TAN = 2;
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);
}
public SlideView(Context context) {
super(context);
initView();
}
public SlideView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView() {
mContext = getContext();
//创建滑动的 scroller
mScroller = new Scroller(mContext);
setOrientation(LinearLayout.HORIZONTAL);
//加载自定义view的界面布局
View.inflate(mContext, R.layout.rygslide_view_merge, this);
//总的linearlayout
mViewContent = (LinearLayout) findViewById(R.id.view_content);
//这个方法是转变为标准尺寸的一个函数,例如
// int size = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, context.getResources().getDisplayMetrics());
// 这里COMPLEX_UNIT_SP是单位,20是数值,也就是20sp。
mHolderWidth = Math.round(TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, mHolderWidth, getResources()
.getDisplayMetrics()));
}
//设置移动后显示的字体
public void setButtonText(CharSequence text) {
((TextView)findViewById(R.id.delete)).setText(text);
}
//添加自己本身 ListView 要显示的 xml界面
public void setContentView(View view) {
mViewContent.addView(view);
}
public View getContentView(){
return mViewContent;
}
public void setOnSlideListener(OnSlideListener onSlideListener) {
mOnSlideListener = onSlideListener;
}
public void shrink() {
if (getScrollX() != 0) {
this.smoothScrollTo(0, 0);
}
}
// 从listview端接收到 event事件,用来执行滑动等动画
public void onRequireTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int clickx = 0;
int clicky = 0;
int scrollX = getScrollX();
Log.d(TAG, "x=" + x + " y=" + y);
switch (event.getAction()) {
//当
case MotionEvent.ACTION_DOWN: {
clickx = (int) event.getX();
clicky = (int) event.getY();
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;
}
int newScrollX = scrollX - deltaX;
if (deltaX != 0) {
if (newScrollX < 0) {
newScrollX = 0;
} else if (newScrollX > mHolderWidth) {
newScrollX = mHolderWidth;
}
this.scrollTo(newScrollX, 0);
}
break;
}
case MotionEvent.ACTION_UP: {
int newScrollX = 0;
if((int)event.getX() == clickx && (int)event.getY() == clicky )
System.out.println("====================onclick================");
//手指滑动多少会判定, 这样进行撤回
if (scrollX - mHolderWidth * 0.75 > 0) {
newScrollX = mHolderWidth;
}
System.out.println("====ACTION_UP==="+ newScrollX );
//手指抬起,滑动到指定
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;
}
// 调用这里进行的刷新哦哦哦
// 1.滑动到界面外面,显示 删除
// 2.滑动回原来的位置
private void smoothScrollTo(int destX, int destY) {
int scrollX = getScrollX();
int delta = destX - scrollX;
mScroller.startScroll(scrollX, 0, delta, 0, Math.abs(delta) * 3);
// mScroller.startScroll(scrollX, 0, delta, 0, 5000);
invalidate();
}
//父view 要求他内部的子view的mScrollX和mScrollY发生变化时,通常情况下子view使用Scroller对象进行动画滚动.
@Override
public void computeScroll() {
//true 说明滚动未完成 false 说明滚动完成
boolean a = mScroller.computeScrollOffset();
System.out.println("==========a======"+ a);
if (a) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
}