Android View - Scroll

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();
        }
    }


}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

空白的泡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值