Android Scroller完全解析

在Android中,任何一个控件都是可以滚动的,因为在View类当中有scrollTo()和scrollBy()这两个方法,如下图所示:

这两个方法的主要作用是将View/ViewGroup移至指定的坐标中,并且将偏移量保存起来。另外:

mScrollX 代表X轴方向的偏移坐标
mScrollY 代表Y轴方向的偏移坐标

这两个方法都是用于对View进行滚动的,那么它们之间有什么区别呢?简单点讲,scrollBy()方法是让View相对于当前的位置滚动某段距离,而scrollTo()方法则是让View相对于初始的位置滚动某段距离。

关于偏移量的设置我们可以参看下源码:

public class View {
  ....
  protected int mScrollX; //该视图内容相当于视图起始坐标的偏移量,X轴方向
  protected int mScrollY; //该视图内容相当于视图起始坐标的偏移量,Y轴方向
  //返回值
  public final int getScrollX() {
    return mScrollX;
  }
  public final int getScrollY() {
    return mScrollY;
  }
  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()) {
        invalidate(); //一般都引起重绘
      }
    }
  }
  // 看出区别了吧 。 mScrollX 与 mScrollY 代表我们当前偏移的位置 , 在当前位置继续偏移(x ,y)个单位
  public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
  }
  //...
}

于是,在任何时刻我们都可以获取该View/ViewGroup的偏移位置了,即调用getScrollX()方法和getScrollY()方法。

下面我们写个例子看下它们的区别吧:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical">

  <Button
    android:id="@+id/scroll_to_btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="scrollTo"/>

  <Button
    android:id="@+id/scroll_by_btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="10dp"
    android:text="scrollBy"/>
</LinearLayout>

外层使用了一个LinearLayout,在里面包含了两个按钮,一个用于触发scrollTo逻辑,一个用于触发scrollBy逻辑。

public class MainActivity extends AppCompatActivity {

  private LinearLayout layout;
  private Button scrollToBtn;
  private Button scrollByBtn;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    layout = (LinearLayout) findViewById(R.id.layout);
    scrollToBtn = (Button) findViewById(R.id.scroll_to_btn);
    scrollByBtn = (Button) findViewById(R.id.scroll_by_btn);
    scrollToBtn.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        layout.scrollTo(getResources().getDimensionPixelOffset(R.dimen.horizontal_scroll),
            getResources().getDimensionPixelOffset(R.dimen.horizontal_scroll));
      }
    });
    scrollByBtn.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        layout.scrollBy(getResources().getDimensionPixelOffset(R.dimen.horizontal_scroll),
            getResources().getDimensionPixelOffset(R.dimen.horizontal_scroll));
      }
    });
  }
}

<resources>
  <dimen name="horizontal_scroll">-20dp</dimen>
  <dimen name="vertical_scroll">-30dp</dimen>
</resources>

当点击了scrollTo按钮时,我们调用了LinearLayout的scrollTo()方法,当点击了scrollBy按钮时,调用了LinearLayout的scrollBy()方法。那有的朋友可能会问了,为什么都是调用的LinearLayout中的scroll方法?这里一定要注意,不管是scrollTo()还是scrollBy()方法,滚动的都是该View内部的内容,而LinearLayout中的内容就是我们的两个Button,如果你直接调用button的scroll方法的话,那结果一定不是你想看到的。

另外还有一点需要注意,就是两个scroll方法中传入的参数,第一个参数x表示相对于当前位置横向移动的距离,正值向左移动,负值向右移动。第二个参数y表示相对于当前位置纵向移动的距离,正值向上移动,负值向下移动。
运行一下程序:

当我们点击scrollTo按钮时,两个按钮会一起向右下方滚动,之后再点击scrollTo按钮就没有任何作用了,界面不会再继续滚动,只有点击scrollBy按钮界面才会继续滚动,并且不停点击scrollBy按钮界面会一起滚动下去。

Scroller类

从上面例子运行结果可以看出,利用scrollTo()/scrollBy()方法把一个View偏移至指定坐标(x,y)处,整个过程是直接跳跃的,没有对这个偏移过程有任何控制,对用户而言不太友好。于是,基于这种偏移控制,Scroller类被设计出来了,该类的主要作用是为偏移过程制定一定的控制流程,从而使偏移更流畅,更完美。
我们分析下源码里去看看Scroller类的相关方法,其源代码(部分)如下: 路径位于 \frameworks\base\core\Java\android\widget\Scroller.java

public class Scroller { 

  private int mStartX;  //起始坐标点 , X轴方向
  private int mStartY;  //起始坐标点 , Y轴方向
  private int mCurrX;   //当前坐标点 X轴, 即调用startScroll函数后,经过一定时间所达到的值
  private int mCurrY;   //当前坐标点 Y轴, 即调用startScroll函数后,经过一定时间所达到的值 

  private float mDeltaX; //应该继续滑动的距离, X轴方向
  private float mDeltaY; //应该继续滑动的距离, Y轴方向
  private boolean mFinished; //是否已经完成本次滑动操作, 如果完成则为 true 

  //构造函数
  public Scroller(Context context) {
    this(context, null);
  }
  public final boolean isFinished() {
    return mFinished;
  }
  //强制结束本次滑屏操作
  public final void forceFinished(boolean finished) {
    mFinished = finished;
  }
  public final int getCurrX() {
    return mCurrX;
  }
   /* Call this when you want to know the new location. If it returns true,
   * the animation is not yet finished. loc will be altered to provide the
   * new location. */
  //根据当前已经消逝的时间计算当前的坐标点,保存在mCurrX和mCurrY值中
  public boolean computeScrollOffset() {
    if (mFinished) { //已经完成了本次动画控制,直接返回为false
      return false;
    }
    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    if (timePassed < mDuration) {
      switch (mMode) {
      case SCROLL_MODE:
        float x = (float)timePassed * mDurationReciprocal;
        ...
        mCurrX = mStartX + Math.round(x * mDeltaX);
        mCurrY = mStartY + Math.round(x * mDeltaY);
        break;
      ...
    }
    else {
      mCurrX = mFinalX;
      mCurrY = mFinalY;
      mFinished = true;
    }
    return true;
  }
  //开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达坐标为(startX+dx , startY+dy)出
  public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    mFinished = false;
    mDuration = duration;
    mStartTime = AnimationUtils.currentAnimationTimeMillis();
    mStartX = startX;    mStartY = startY;
    mFinalX = startX + dx; mFinalY = startY + dy;
    mDeltaX = dx;      mDeltaY = dy;
    ...
  }
}

其中比较重要的两个方法为:

public boolean computeScrollOffset()
函数功能说明:根据当前已经消逝的时间计算当前的坐标点,保存在mCurrX和mCurrY值中。

public void startScroll(int startX, int startY, int dx, int dy, int duration)
函数功能说明:开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,到达坐标为(startX+dx , startY+dy)处。

computeScroll()方法介绍:
为了易于控制滑屏控制,Android框架提供了 computeScroll()方法去控制这个流程。在绘制View时,会在draw()过程调用该方法。因此, 再配合使用Scroller实例,我们就可以获得当前应该的偏移坐标,手动使View/ViewGroup偏移至该处。
computeScroll()方法原型如下,该方法位于ViewGroup.java类中

/**
   * 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.
   * 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制 */
  public void computeScroll() { //空方法 ,自定义ViewGroup必须实现方法体
  }

为了实现偏移控制,一般自定义View/ViewGroup都需要重载该方法 。其调用过程位于View绘制流程draw()过程中,如下:

@Override
protected void dispatchDraw(Canvas canvas){
  ... 

  for (int i = 0; i < count; i++) {
    final View child = children[getChildDrawingOrder(count, i)];
    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
      more |= drawChild(canvas, child, drawingTime);
    }
  }
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
  ...
  child.computeScroll();
  ...
}

实例演示

ViewPager相信每个人都再熟悉不过了,因此它实在是太常用了,我们可以借助ViewPager来轻松完成页面之间的滑动切换效果,但是如果问到它是如何实现的话,我感觉大部分人还是比较陌生的。其实说到ViewPager最基本的实现原理主要就是两部分内容,一个是事件分发,一个是Scroller。对于事件分发,不了解的同学可以参考我这篇博客Android事件的分发、拦截和执行。
接下来我将结合事件分发和Scroller来实现一个简易版的ViewPager。首先自定义一个ViewGroup,不了解的可以参考Android自定义ViewGroup(一)之CustomGridLayout这篇文章。平滑偏移的主要做法如下:

第一、调用Scroller实例去产生一个偏移控制(对应于startScroll()方法)
第二、手动调用invalid()方法去重新绘制,剩下的就是在computeScroll()里根据当前已经逝去的时间,获取当前应该偏移的坐标(由Scroller实例对应的computeScrollOffset()计算而得)
第三、当前应该偏移的坐标,调用scrollBy()方法去缓慢移动至该坐标处。

新建一个ScrollerLayout并让它继承自ViewGroup来作为我们的简易ViewPager布局,代码如下所示:

public class ScrollerLayout extends ViewGroup {

  private Scroller mScroller; //用于完成滚动操作的实例
  private VelocityTracker mVelocityTracker = null ; //处理触摸的速率
  public static int SNAP_VELOCITY = 600 ; //最小的滑动速率
  private int mTouchSlop = 0 ;      //最小滑动距离,超过了,才认为开始滑动
  private float mLastionMotionX = 0 ;  //上次触发ACTION_MOVE事件时的屏幕坐标
  private int curScreen = 0 ; //当前屏幕
  private int leftBorder;  //界面可滚动的左边界
  private int rightBorder; //界面可滚动的右边界

  //两种状态: 是否处于滑屏状态
  private static final int TOUCH_STATE_REST = 0; //什么都没做的状态
  private static final int TOUCH_STATE_SCROLLING = 1; //开始滑屏的状态
  private int mTouchState = TOUCH_STATE_REST; //默认是什么都没做的状态

  public ScrollerLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    // 创建Scroller的实例
    mScroller = new Scroller(context);
    //初始化一个最小滑动距离
    mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
      View childView = getChildAt(i);
      // 为ScrollerLayout中的每一个子控件测量大小
      measureChild(childView, widthMeasureSpec, heightMeasureSpec);
    }
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (changed) {
      int childCount = getChildCount();
      for (int i = 0; i < childCount; i++) {
        View childView = getChildAt(i);
        // 为ScrollerLayout中的每一个子控件在水平方向上进行布局
        childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
      }
    }
    // 初始化左右边界值
    leftBorder = getChildAt(0).getLeft();
    rightBorder = getChildAt(getChildCount() - 1).getRight();
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    //表示已经开始滑动了,不需要走该Action_MOVE方法了(第一次时可能调用)。
    //该方法主要用于用户快速松开手指,又快速按下的行为。此时认为是处于滑屏状态的。
    if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
      return true;
    }
    final float x = ev.getX();
    switch (action) {
      case MotionEvent.ACTION_MOVE:
        final int xDiff = (int) Math.abs(mLastionMotionX - x);
        //超过了最小滑动距离,就可以认为开始滑动了
        if (xDiff > mTouchSlop) {
          mTouchState = TOUCH_STATE_SCROLLING;
        }
        break;
      case MotionEvent.ACTION_DOWN:
        mLastionMotionX = x;
        mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
        break;
      case MotionEvent.ACTION_CANCEL:
      case MotionEvent.ACTION_UP:
        mTouchState = TOUCH_STATE_REST;
        break;
    }
    return mTouchState != TOUCH_STATE_REST;
  }

  public boolean onTouchEvent(MotionEvent event){
    super.onTouchEvent(event);
    //获得VelocityTracker对象,并且添加滑动对象
    if (mVelocityTracker == null) {
      mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(event);
    //触摸点
    float x = event.getX();
    switch(event.getAction()){
      case MotionEvent.ACTION_DOWN:
        //如果屏幕的动画还没结束,你就按下了,我们就结束上一次动画,即开始这次新ACTION_DOWN的动画
        if(mScroller != null){
          if(!mScroller.isFinished()){
            mScroller.abortAnimation();
          }
        }
        mLastionMotionX = x ; //记住开始落下的屏幕点
        break ;
      case MotionEvent.ACTION_MOVE:
        int detaX = (int)(mLastionMotionX - x ); //每次滑动屏幕,屏幕应该移动的距离
        if (getScrollX() + detaX < leftBorder) {  //防止用户拖出边界这里还专门做了边界保护,当拖出边界时就调用scrollTo()方法来回到边界位置
          scrollTo(leftBorder, 0);
          return true;
        } else if (getScrollX() + getWidth() + detaX > rightBorder) {
          scrollTo(rightBorder - getWidth(), 0);
          return true;
        }
        scrollBy(detaX, 0);//开始缓慢滑屏咯。 detaX > 0 向右滑动 , detaX < 0 向左滑动
        mLastionMotionX = x ;
        break ;
      case MotionEvent.ACTION_UP:
        final VelocityTracker velocityTracker = mVelocityTracker ;
        velocityTracker.computeCurrentVelocity(1000);
        //计算速率
        int velocityX = (int) velocityTracker.getXVelocity() ;
        //滑动速率达到了一个标准(快速向右滑屏,返回上一个屏幕) 马上进行切屏处理
        if (velocityX > SNAP_VELOCITY && curScreen > 0) {
          // Fling enough to move left
          snapToScreen(curScreen - 1);
        }
        //快速向左滑屏,返回下一个屏幕
        else if(velocityX < -SNAP_VELOCITY && curScreen < (getChildCount()-1)){
          snapToScreen(curScreen + 1);
        }
        //以上为快速移动的 ,强制切换屏幕
        else{
          //我们是缓慢移动的,因此先判断是保留在本屏幕还是到下一屏幕
          snapToDestination();
        }
        //回收VelocityTracker对象
        if (mVelocityTracker != null) {
          mVelocityTracker.recycle();
          mVelocityTracker = null;
        }
        //修正mTouchState值
        mTouchState = TOUCH_STATE_REST ;
        break;
      case MotionEvent.ACTION_CANCEL:
        mTouchState = TOUCH_STATE_REST ;
        break;
    }
    return true ;
  }

  //我们是缓慢移动的,因此需要根据偏移值判断目标屏是哪个
  private void snapToDestination(){
    //判断是否超过下一屏的中间位置,如果达到就抵达下一屏,否则保持在原屏幕
    //公式意思是:假设当前滑屏偏移值即 scrollCurX 加上每个屏幕一半的宽度,除以每个屏幕的宽度就是我们目标屏所在位置了。
    int destScreen = (getScrollX() + getWidth() / 2 ) / getWidth() ;
    snapToScreen(destScreen);
  }

  //真正的实现跳转屏幕的方法
  private void snapToScreen(int whichScreen){
    //简单的移到目标屏幕,可能是当前屏或者下一屏幕,直接跳转过去,不太友好,为了友好性,我们在增加一个动画效果
    curScreen = whichScreen ;
    //防止屏幕越界,即超过屏幕数
    if(curScreen > getChildCount() - 1)
      curScreen = getChildCount() - 1 ;
    //为了达到下一屏幕或者当前屏幕,我们需要继续滑动的距离.根据dx值,可能向左滑动,也可能向右滑动
    int dx = curScreen * getWidth() - getScrollX() ;
    mScroller.startScroll(getScrollX(), 0, dx, 0, Math.abs(dx) * 2);
    //由于触摸事件不会重新绘制View,所以此时需要手动刷新View 否则没效果
    invalidate();
  }

  @Override
  public void computeScroll() {
    //重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
    if (mScroller.computeScrollOffset()) {
      scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
      invalidate();
    }
  }
}

代码比较长,但思路比较清晰。
(1)首先在ScrollerLayout的构造函数里面我们创建Scroller的实例,由于Scroller的实例只需创建一次,因此我们把它放到构造函数里面执行。另外在构建函数中我们还初始化的TouchSlop的值,这个值在后面将用于判断当前用户的操作是否是拖动。
(2)接着重写onMeasure()方法和onLayout()方法,在onMeasure()方法中测量ScrollerLayout里的每一个子控件的大小,在onLayout()方法中为ScrollerLayout里的每一个子控件在水平方向上进行布局,布局类似于方向为horizontal的LinearLayout。
(3) 接着重写onInterceptTouchEvent()方法, 在这个方法中我们记录了用户手指按下时的X坐标位置,以及用户手指在屏幕上拖动时的X坐标位置,当两者之间的距离大于TouchSlop值时,就认为用户正在拖动布局,置状态为TOUCH_STATE_SCROLLING,当用户手指抬起,重置状态为TOUCH_STATE_REST。这里当状态值为TOUCH_STATE_SCROLLING时返回true,将事件在这里拦截掉,阻止事件传递到子控件当中。
(4)那么当我们把事件拦截掉之后,就会将事件交给ScrollerLayout的onTouchEvent()方法来处理。
如果当前事件是ACTION_MOVE,说明用户正在拖动布局,那么我们就应该对布局内容进行滚动从而影响拖动事件,实现的方式就是使用我们刚刚所学的scrollBy()方法,用户拖动了多少这里就scrollBy多少。另外为了防止用户拖出边界这里还专门做了边界保护,当拖出边界时就调用scrollTo()方法来回到边界位置。
如果当前事件是ACTION_UP时,说明用户手指抬起来了,但是目前很有可能用户只是将布局拖动到了中间,我们不可能让布局就这么停留在中间的位置,因此接下来就需要借助Scroller来完成后续的滚动操作。首先计算滚动速率,判断当前动作是scroll还是fling。如果是fling,再根据fling的方向跳转到上一页或者下一页,调用函数snapToScreen。如果是scroll,就调用函数snapToDestination,函数中首先根据当前的滚动位置来计算布局应该继续滚动到哪一页,滚动到哪一页同样调用snapToScreen。再来看看snapToScreen写法吧,其实是调用startScroll()方法来滚动数据,紧接着调用invalidate()方法来刷新界面。
(5)重写computeScroll()方法,并在其内部完成平滑滚动的逻辑 。在整个后续的平滑滚动过程中,computeScroll()方法是会一直被调用的,因此我们需要不断调用Scroller的computeScrollOffset()方法来进行判断滚动操作是否已经完成了,如果还没完成的话,那就继续调用scrollTo()方法,并把Scroller的curX和curY坐标传入,然后刷新界面从而完成平滑滚动的操作。

现在ScrollerLayout已经准备好了,接下来我们修改activity_main.xml布局中的内容,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<com.hx.scroller.ScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <ImageView
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:background="@drawable/crazy_1" />

  <ImageView
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:background="@drawable/crazy_2" />

  <ImageView
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:background="@drawable/crazy_3" />

</com.hx.scroller.ScrollerLayout>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android程序开发之UIScrollerView里有两个tableView

    一,效果图. 二,工程图. 三,代码. RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController : UIViewController <UIScrollViewDelegate,UITableViewDelegate,UITableViewDataSource> { UIScrollView *_scrolView; UITableView *_tableView; UITableView

  • android使用 ScrollerView 实现 可上下滚动的分类栏实例

    如果不考虑更深层的性能问题,我个人认为ScrollerView还是很好用的.而且单用ScrollerView就可以实现分类型的RecyclerView或ListView所能实现的效果. 下面我单单从效果展示方面考虑,使用ScrollerView实现如下图所示的可滚动的多条目分类,只是为了跟大家一起分享一下新思路.(平时:若从复用性等方面考虑,这显然是存在瑕疵的~) 特点描述: 1.可上下滚动 2.有类似于网格布局的样式 3.子条目具有点击事件 刚看到这个效果时,首先想到的是使用分类型的Recyc

  • Android Scroller及下拉刷新组件原理解析

    Android事件拦截机制 Android中事件的传递和拦截和View树结构是相关联的,在View树中,分为叶子节点和普通节点,普通节点有子节点只能是ViewGroup,叶子节点可以是View或者ViewGroup.Android和事件分发拦截相关的方法有 dispatchTouchEvent(MotionEvent ev) 事件分发相关的方法,沿着View树将一个用户的触摸事件向下分发. onInterceptTouchEvent(MotionEvent ev) 在dispatchTouchE

  • 详解Android应用开发中Scroller类的屏幕滑动功能运用

    今天给大家介绍下Android中滑屏功能的一个基本实现过程以及原理初探,最后给大家重点讲解View视图中scrollTo 与scrollBy这两个函数的区别 .   首先 ,我们必须明白在Android View视图是没有边界的,Canvas是没有边界的,只不过我们通过绘制特定的View时对Canvas对象进行了一定的操作,例如 : translate(平移).clipRect(剪切)等,以便达到我们的对该Canvas对象绘制的要求 ,我们可以将这种无边界的视图称为"视图坐标"----

  • Android Scroller大揭秘

    在学习使用Scroller之前,需要明白scrollTo().scrollBy()方法. 一.View的scrollTo().scrollBy() scrollTo.scrollBy方法是View中的,因此任何的View都可以通过这两种方法进行移动.首先要明白的是,scrollTo.scrollBy滑动的是View中的内容(而且还是整体滑动),而不是View本身.我们的滑动控件如SrollView可以限定宽.高大小,以及在布局中的位置,但是滑动控件中的内容(或者里面的childView)可以是无

  • 深入理解Android中Scroller的滚动原理

    View的平滑滚动效果 什么是实现View的平滑滚动效果呢,举个简单的例子,一个View从在我们指定的时间内从一个位置滚动到另外一个位置,我们利用Scroller类可以实现匀速滚动,可以先加速后减速,可以先减速后加速等等效果,而不是瞬间的移动的效果,所以Scroller可以帮我们实现很多滑动的效果. 首先我们先来看一下Scroller的用法,基本可概括为"三部曲": 1.创建一个Scroller对象,一般在View的构造器中创建: public ScrollViewGroup(Cont

  • 详解Android Scroller与computeScroll的调用机制关系

    Android ViewGroup中的Scroller与computeScroll的有什么关系? 答:没有直接的关系 知道了答案,是不是意味着下文就没必要看了,如果说对ViewGroup自定义控件不感兴趣,可以不用看了. 1.Scroller到底是什么? 答:Scroller只是个计算器,提供插值计算,让滚动过程具有动画属性,但它并不是UI,也不是滑动辅助UI运动,反而是单纯地为滑动提供计算. 无论从构造方法还是其他方法,以及Scroller的属性可知,其并不会持有View,辅助ViewGrou

  • Android Scroller完全解析

    在Android中,任何一个控件都是可以滚动的,因为在View类当中有scrollTo()和scrollBy()这两个方法,如下图所示: 这两个方法的主要作用是将View/ViewGroup移至指定的坐标中,并且将偏移量保存起来.另外: mScrollX 代表X轴方向的偏移坐标 mScrollY 代表Y轴方向的偏移坐标 这两个方法都是用于对View进行滚动的,那么它们之间有什么区别呢?简单点讲,scrollBy()方法是让View相对于当前的位置滚动某段距离,而scrollTo()方法则是让Vi

  • Android创建与解析XML(三)——详解Sax方式

    1. Sax概述 SAX是一种占用内存少且解析速度快的解析器,它采用的是事件启动,不需要解析完整个文档,而是按照内容顺序看文档某个部分是否符合xml语法,如果符合就触发相应的事件,所谓的事件就是些回调方法(callback),这些方法 定义在ContentHandler中,下面是其主要方法: startDocument():当遇到文档的时候就触发这个事件 调用这个方法 可以在其中做些预处理工作,如:申请对象资源 endDocument():当结束文档的时候就触发这个事件 调用这个方法 可以在其中

  • Android XmlPullParser 方式解析 Xml 文档

    Android XmlPullParser 方式解析 Xml 文档 xml 文件格式 <?xml version="1.0" encoding="UTF-8"?> <persons> <person id="1"> <name>张三</name> <age>22</age> </person> <person id="2"&g

  • Android ActionBar完全解析使用官方推荐的最佳导航栏(下)

    本篇文章主要内容来自于Android Doc,我翻译之后又做了些加工,英文好的朋友也可以直接去读原文. http://developer.android.com/guide/topics/ui/actionbar.html 限于篇幅的原因,在上篇文章中我们只学习了ActionBar基础部分的知识,那么本篇文章我们将接着上一章的内容继续学习,探究一下ActionBar更加高级的知识.如果你还没有看过前面一篇文章的话,建议先去阅读Android ActionBar完全解析,使用官方推荐的最佳导航栏(

  • Android 创建与解析XML(四)——详解Pull方式

     1.Pull概述 Android系统中和创建XML相关的包为org.xmlpull.v1,在这个包中不仅提供了用于创建XML的 XmlSerializer,还提供了用来解析XML的Pull方式解析器 XmlPullParser XmlSerializer没有像XmlPullParser那样提取XML事件,而是把它们推出到数据流OutputStream或Writer中. XmlSerializer提供了很直观的API,即使用startDocument开始文档,endDocument结束文档,st

  • Android 创建与解析XML(五)——详解Dom4j方式

    1.Dom4j概述 dom4j is an easy to use, open source library for working with XML, XPath and XSLT on the Java platform using the Java Collections Framework and with full support for DOM, SAX and JAXP. dom4j官方网址:dom4j dom4j源码下载:dom4j download 本示例中,需要导入dom4j

  • 探究Android系统中解析JSON数据的方式

    前言 喜欢在前言里讲一下自己的现状,或许能有共鸣的同学,更多的是留给自己一个纪念,几个月或者几年再回来看的时候还是会很有感慨.今天说说语言,json这种数据格式之前我做服务器端的时候天天接触,天真的以为json的世界里只有php的json_encode和json_decode,今天当我做客户端的时候,竟然将近一个多小时才搞定json的解析.这里我不是抨击php好坏,只是想说多学点东西,看看你不熟悉的领域,方能开阔自己的视野,方能知道天外有天,方能知道当初你看不上的工作其实你也不一定能很好的完成,

  • Android使用Jsoup解析Html表格的方法

    本文实例讲述了Android使用Jsoup解析Html表格的方法.分享给大家供大家参考,具体如下: 看代码吧,可解析表中的label text button 自己根据需要再添加,呵呵 import java.util.ArrayList; import java.util.List; import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; import org.jsoup.J

  • Android编程简单解析JSON格式数据的方法示例

    本文实例讲述了Android编程简单解析JSON格式数据的方法.分享给大家供大家参考,具体如下: 比起XML,JSON主要优势在于它的体积更小,在网络上传输的时候可以更省流量.但缺点在于,它的语义性较差,显示不如XML直观. JSON格式 : { "name_A" : "value_A","name_B" : "value_B" } 表示: name_A = value_A; name_B = value_B; 我将对下面的J

随机推荐