在学习使用Scroller之前,需要明白scrollTo()、scrollBy()方法。

一、View的scrollTo()、scrollBy()

scrollTo、scrollBy方法是View中的,因此任何的View都可以通过这两种方法进行移动。首先要明白的是,scrollTo、scrollBy滑动的是View中的内容(而且还是整体滑动),而不是View本身。我们的滑动控件如SrollView可以限定宽、高大小,以及在布局中的位置,但是滑动控件中的内容(或者里面的childView)可以是无限长、宽的,我们调用View的scrollTo、scrollBy方法,相当于是移动滑动控件中的画布Canvas,然后进行重绘,屏幕上也就显示相应的内容。如下:
1、getScrollX()、getScrollY()
在学习scrollTo()、scrollBy()之前,先来了解一下getScrollX()、getScrollY()方法。
getScrollX()、getScrollY()得到的是偏移量,是相对自己初始位置的滑动偏移距离,只有当有scroll事件发生时,这两个方法才能有值,否则getScrollX()、getScrollY()都是初始时的值0,而不管你这个滑动控件在哪里。所谓自己初始位置是指,控件在刚开始显示时、没有滑动前的位置。以getScrollX()为例,其源码如下:
public final int getScrollX() {
return mScrollX;
}

可以看到getScrollX()直接返回的就是mScrollX,代表水平方向上的偏移量,getScrollY()也类似。偏移量mScrollX的正、负代表着,滑动控件中的内容相对于初始位置在水平方向上偏移情况,mScrollX为正代表着当前内容相对于初始位置向左偏移了mScrollX的距离,mScrollX为负表示当前内容相对于初始位置向右偏移了mScrollX的距离。

    这里的坐标系和我们平常的认知正好相反。为了以后更方便的处理滑动相关坐标和偏移,在处理偏移、滑动相关的功能时,我们就可以把坐标反过来看,如下图:
 
因为滑动控件中的内容是整体进行滑动的,同时也是相对于自己显示时的初始位置的偏移,对于View中内容在偏移时的参考坐标原点(注意是内容视图的坐标原点,不是图中说的滑动控件的原点),可以选择初始位置的某一个地方,因为滑动时整体行为,在进行滑动的时候从这个选择的原点出进行分析即可。
 
2、scrollTo()、scrollBy()
scrollTo(int x,int y)移动的是View中的内容,而滑动控件中的内容都是整体移动的,scrollTo(int x,int y)中的参数表示View中的内容要相对于内容初始位置移动x和y的距离,即将内容移动到距离内容初始位置x和y的位置。正如前面所说,在处理偏移、滑动问题时坐标系和平常认知的坐标系是相反的。以一个例子说明scrollTo():
 

说明:图中黄色矩形区域表示的是一个可滑动的View控件,绿色虚线矩形为滑动控件中的滑动内容。注意这里的坐标是相反的。(例子来源于:http://blog.csdn.net/bigconvience/article/details/26697645

 
(1)调用scrollTo(100,0)表示将View中的内容移动到距离内容初始显示位置的x=100,y=0的地方,效果如下图:
(2)调用scrollTo(0,100)效果如下图:
(3)调用scrollTo(100,100)效果如下图:
(4)调用scrollTo(-100,0)效果如下图:
通过上面几个图,可以清楚看到scrollTo的作用和滑动坐标系的关系。在实际使用中,我们一般是在onTouchEvent()方法中处理滑动事件,在MotionEvent.ACTION_MOVE时调用scrollTo(int x,int y)进行滑动,在调用scrollTo(int x,int y)前,我们先要计算出两个参数值,即水平和垂直方向需要滑动的距离,如下:
[java] view plain copy
  1. @Override
  2. public boolean onTouchEvent(MotionEvent event) {
  3. int y = (int) event.getY();
  4. int action = event.getAction();
  5. switch (action){
  6. case MotionEvent.ACTION_DOWN:
  7. mLastY = y;
  8. break;
  9. case MotionEvent.ACTION_MOVE:
  10. int dy = mLastY - y;//本次手势滑动了多大距离
  11. int oldScrollY = getScrollY();//先计算之前已经偏移了多少距离
  12. int scrollY = oldScrollY + dy;//本次需要偏移的距离=之前已经偏移的距离+本次手势滑动了多大距离
  13. if(scrollY < 0){
  14. scrollY = 0;
  15. }
  16. if(scrollY > getHeight() - mScreenHeight){
  17. scrollY = getHeight() - mScreenHeight;
  18. }
  19. scrollTo(getScrollX(),scrollY);
  20. mLastY = y;
  21. break;
  22. }
  23. return true;
  24. }
上面在计算参数时,分为了三步。第一是,通过int dy = mLastY - y;得到本次手势在屏幕上滑动了多少距离,这里要特别注意这个相减顺序,因为这里的坐标与平常是相反的,因此,手势滑动距离是按下时的坐标mLastY - 当前的坐标y;第二是,通过oldScrollY
= getScrollY();获得滑动内容之前已经距初始位置便宜了多少;第三是,计算本次需要偏移的参数int scrollY =
oldScrollY + dy;
后面通过两个if条件进行了边界处理,然后调用scrollTo进行滑动。调用完scrollTo后,新的偏移量又重新产生了。从scrollTo源码中可以看到:
[java] view plain copy
  1. public void scrollTo(int x, int y) {
  2. if (mScrollX != x || mScrollY != y) {
  3. int oldX = mScrollX;
  4. int oldY = mScrollY;
  5. mScrollX = x;//赋值新的x偏移量
  6. mScrollY = y;//赋值新的y偏移量
  7. invalidateParentCaches();
  8. onScrollChanged(mScrollX, mScrollY, oldX, oldY);
  9. if (!awakenScrollBars()) {
  10. postInvalidateOnAnimation();
  11. }
  12. }
  13. }
scrollTo是相对于初始位置来进行移动的,而scrollBy(int x ,int y)则是相对于上一次移动的距离来进行本次移动。scrollBy其实还是依赖于scrollTo的,如下源码:
[java] view plain copy
  1. public void scrollBy(int x, int y) {
  2. scrollTo(mScrollX + x, mScrollY + y);
  3. }
可以看到,使用scrollBy其实就是省略了我们在计算scrollTo参数时的第三步而已,因为scrollBy内部已经自己帮我加上了第三步的计算。因此scrollBy的作用就是相当于在上一次的偏移情况下进行本次的偏移。
 
一个完整的水平方向滑动的例子:
[java] view plain copy
  1. public class MyViewPager extends ViewGroup {
  2. private int mLastX;
  3. public MyViewPager(Context context) {
  4. super(context);
  5. init(context);
  6. }
  7. public MyViewPager(Context context, AttributeSet attrs) {
  8. super(context, attrs);
  9. init(context);
  10. }
  11. public MyViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
  12. super(context, attrs, defStyleAttr);
  13. init(context);
  14. }
  15. private void init(Context context) {
  16. }
  17. @Override
  18. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  19. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  20. int count = getChildCount();
  21. for(int i = 0; i < count; i++){
  22. View child = getChildAt(i);
  23. child.measure(widthMeasureSpec,heightMeasureSpec);
  24. }
  25. }
  26. @Override
  27. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  28. int count = getChildCount();
  29. Log.d("TAG","--l-->"+l+",--t-->"+t+",-->r-->"+r+",--b-->"+b);
  30. for(int i = 0; i < count; i++){
  31. View child = getChildAt(i);
  32. child.layout(i * getWidth(), t, (i+1) * getWidth(), b);
  33. }
  34. }
  35. @Override
  36. public boolean onTouchEvent(MotionEvent ev) {
  37. int x = (int) ev.getX();
  38. switch (ev.getAction()){
  39. case MotionEvent.ACTION_DOWN:
  40. mLastX = x;
  41. break;
  42. case MotionEvent.ACTION_MOVE:
  43. int dx = mLastX - x;
  44. int oldScrollX = getScrollX();//原来的偏移量
  45. int preScrollX = oldScrollX + dx;//本次滑动后形成的偏移量
  46. if(preScrollX > (getChildCount() - 1) * getWidth()){
  47. preScrollX = (getChildCount() - 1) * getWidth();
  48. }
  49. if(preScrollX < 0){
  50. preScrollX = 0;
  51. }
  52. scrollTo(preScrollX,getScrollY());
  53. mLastX = x;
  54. break;
  55. }
  56. return true;
  57. }
  58. }
布局文件:
[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical">
  6. <com.scu.lly.viewtest.view.MyViewPager
  7. android:layout_width="match_parent"
  8. android:layout_height="300dp"
  9. >
  10. <ImageView
  11. android:layout_width="match_parent"
  12. android:layout_height="match_parent"
  13. android:scaleType="fitXY"
  14. android:src="@drawable/test1" />
  15. <ImageView
  16. android:layout_width="match_parent"
  17. android:layout_height="match_parent"
  18. android:scaleType="fitXY"
  19. android:src="@drawable/test2" />
  20. <ImageView
  21. android:layout_width="match_parent"
  22. android:layout_height="match_parent"
  23. android:scaleType="fitXY"
  24. android:src="@drawable/test3" />
  25. <ImageView
  26. android:layout_width="match_parent"
  27. android:layout_height="match_parent"
  28. android:scaleType="fitXY"
  29. android:src="@drawable/test4" />
  30. </com.scu.lly.viewtest.view.MyViewPager>
  31. </LinearLayout>
效果如图:
 

二、Scroller滑动辅助类

根据我们上面的分析,可知View的scrollTo()、scrollBy()是瞬间完成的,当我们的手指在屏幕上移动时,内容会跟着手指滑动,但是当我们手指一抬起时,滑动就会停止,如果我们想要有一种惯性的滚动过程效果和回弹效果,此时就需要使用Scroller辅助类。

但是注意的是,Scroller本身不会去移动View,它只是一个移动计算辅助类,用于跟踪控件滑动的轨迹,只相当于一个滚动轨迹记录工具,最终还是通过View的scrollTo、scrollBy方法完成View的移动的。

在使用Scroller类之前,先了解其重要的两个方法:
(1)startScroll()

public void startScroll(int startX, int startY, int dx, int dy, int duration)
开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达偏移坐标为(startX+dx , startY+dy)处。
(2)computeScrollOffset()
public boolean computeScrollOffset()
滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中。
上面两个方法的源码如下:
public class Scroller {
private int mStartX;//水平方向,滑动时的起点偏移坐标
private int mStartY;//垂直方向,滑动时的起点偏移坐标
private int mFinalX;//滑动完成后的偏移坐标,水平方向
private int mFinalY;//滑动完成后的偏移坐标,垂直方向 private int mCurrX;//滑动过程中,根据消耗的时间计算出的当前的滑动偏移距离,水平方向
private int mCurrY;//滑动过程中,根据消耗的时间计算出的当前的滑动偏移距离,垂直方向
private int mDuration; //本次滑动的动画时间
private float mDeltaX;//滑动过程中,在达到mFinalX前还需要滑动的距离,水平方向
private float mDeltaY;//滑动过程中,在达到mFinalX前还需要滑动的距离,垂直方向 public void startScroll(int startX, int startY, int dx, int dy) {
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
} /**
* 开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达偏移坐标为(startX+dx , startY+dy)处
*/
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;
} /**
* 滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中
* @return
*/
public boolean computeScrollOffset() {
if (mFinished) {//已经完成了本次动画控制,直接返回为false
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);//计算出当前的滑动偏移位置,x轴
mCurrY = mStartY + Math.round(x * mDeltaY);//计算出当前的滑动偏移位置,y轴
break;
...
}
}else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
...
}
Scroller类中最重要的两个方法就是startScroll()和computeScrollOffset(),但是Scroller类只是一个滑动计算辅助类,它的startScroll()和computeScrollOffset()方法中也只是对一些轨迹参数进行设置和计算,真正需要进行滑动还是得通过View的scrollTo()、scrollBy()方法。为此,View中提供了computeScroll()方法来控制这个滑动流程。computeScroll()方法会在绘制子视图的时候进行调用。其源码如下:
[java] view plain copy
  1. /**
  2. * Called by a parent to request that a child update its values for mScrollX
  3. * and mScrollY if necessary. This will typically be done if the child is
  4. * animating a scroll using a {@link android.widget.Scroller Scroller}
  5. * object.
  6. * 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制
  7. */
  8. public void computeScroll() { //空方法 ,自定义滑动功能的ViewGroup必须实现方法体
  9. }
因此Scroller类的基本使用流程可以总结如下:
(1)首先通过Scroller类的startScroll()开始一个滑动动画控制,里面进行了一些轨迹参数的设置和计算;
(2)在调用startScroll()的后面调用invalidate();引起视图的重绘操作,从而触发ViewGroup中的computeScroll()被调用;
(3)在computeScroll()方法中,先调用Scroller类中的computeScrollOffset()方法,里面根据当前消耗时间进行轨迹坐标的计算,然后取得计算出的当前滑动的偏移坐标,调用View的scrollTo()方法进行滑动控制,最后也需要调用invalidate();进行重绘。
如下的一个简单代码示例:   
[java] view plain copy
  1. @Override
  2. public boolean onTouchEvent(MotionEvent ev) {
  3. initVelocityTrackerIfNotExists();
  4. mVelocityTracker.addMovement(ev);
  5. int x = (int) ev.getX();
  6. switch (ev.getAction()){
  7. case MotionEvent.ACTION_DOWN:
  8. if(!mScroller.isFinished()){
  9. mScroller.abortAnimation();
  10. }
  11. mLastX = x;
  12. break;
  13. case MotionEvent.ACTION_MOVE:
  14. int dx = mLastX - x;
  15. int oldScrollX = getScrollX();//原来的偏移量
  16. int preScrollX = oldScrollX + dx;//本次滑动后形成的偏移量
  17. if(preScrollX > (getChildCount() - 1) * getWidth()){
  18. preScrollX = (getChildCount() - 1) * getWidth();
  19. }
  20. if(preScrollX < 0){
  21. preScrollX = 0;
  22. }
  23. //开始滑动动画
  24. mScroller.startScroll(mScroller.getFinalX(),mScroller.getFinalY(),dx,0);//第一步
  25. //注意,一定要进行invalidate刷新界面,触发computeScroll()方法,因为单纯的startScroll()是属于Scroller的,只是一个辅助类,并不会触发界面的绘制
  26. invalidate();
  27. mLastX = x;
  28. break;
  29. }
  30. return true;
  31. }
  32. @Override
  33. public void computeScroll() {
  34. super.computeScroll();
  35. if(mScroller.computeScrollOffset()){//第二步
  36. scrollTo(mScroller.getCurrX(),mScroller.getCurrY());//第三步
  37. invalidate();
  38. }
  39. }
 
下面是一个完整的例子:一个类似ViewPager的Demo,效果图如下:
代码如下:
[java] view plain copy
  1. public class MyViewPager3 extends ViewGroup {
  2. private int mLastX;
  3. private Scroller mScroller;
  4. private VelocityTracker mVelocityTracker;
  5. private int mTouchSlop;
  6. private int mMaxVelocity;
  7. /**
  8. * 当前显示的是第几个屏幕
  9. */
  10. private int mCurrentPage = 0;
  11. public MyViewPager3(Context context) {
  12. super(context);
  13. init(context);
  14. }
  15. public MyViewPager3(Context context, AttributeSet attrs) {
  16. super(context, attrs);
  17. init(context);
  18. }
  19. public MyViewPager3(Context context, AttributeSet attrs, int defStyleAttr) {
  20. super(context, attrs, defStyleAttr);
  21. init(context);
  22. }
  23. private void init(Context context) {
  24. mScroller = new Scroller(context);
  25. ViewConfiguration config = ViewConfiguration.get(context);
  26. mTouchSlop = config.getScaledPagingTouchSlop();
  27. mMaxVelocity = config.getScaledMinimumFlingVelocity();
  28. }
  29. @Override
  30. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  31. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  32. int count = getChildCount();
  33. for(int i = 0; i < count; i++){
  34. View child = getChildAt(i);
  35. child.measure(widthMeasureSpec, heightMeasureSpec);
  36. }
  37. }
  38. @Override
  39. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  40. int count = getChildCount();
  41. Log.d("TAG","--l-->"+l+",--t-->"+t+",-->r-->"+r+",--b-->"+b);
  42. for(int i = 0; i < count; i++){
  43. View child = getChildAt(i);
  44. child.layout(i * getWidth(), t, (i + 1) * getWidth(), b);
  45. }
  46. }
  47. @Override
  48. public boolean onTouchEvent(MotionEvent ev) {
  49. initVelocityTrackerIfNotExists();
  50. mVelocityTracker.addMovement(ev);
  51. int x = (int) ev.getX();
  52. switch (ev.getAction()){
  53. case MotionEvent.ACTION_DOWN:
  54. if(!mScroller.isFinished()){
  55. mScroller.abortAnimation();
  56. }
  57. mLastX = x;
  58. break;
  59. case MotionEvent.ACTION_MOVE:
  60. int dx = mLastX - x;
  61. /* 注释的里面是使用startScroll()来进行滑动的
  62. int oldScrollX = getScrollX();//原来的偏移量
  63. int preScrollX = oldScrollX + dx;//本次滑动后形成的偏移量
  64. if (preScrollX > (getChildCount() - 1) * getWidth()) {
  65. preScrollX = (getChildCount() - 1) * getWidth();
  66. dx = preScrollX - oldScrollX;
  67. }
  68. if (preScrollX < 0) {
  69. preScrollX = 0;
  70. dx = preScrollX - oldScrollX;
  71. }
  72. mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, 0);
  73. //注意,使用startScroll后面一定要进行invalidate刷新界面,触发computeScroll()方法,因为单纯的startScroll()是属于Scroller的,只是一个辅助类,并不会触发界面的绘制
  74. invalidate();
  75. */
  76. //但是一般在ACTION_MOVE中我们直接使用scrollTo或者scrollBy更加方便
  77. scrollBy(dx,0);
  78. mLastX = x;
  79. break;
  80. case MotionEvent.ACTION_UP:
  81. final VelocityTracker velocityTracker = mVelocityTracker;
  82. velocityTracker.computeCurrentVelocity(1000);
  83. int initVelocity = (int) velocityTracker.getXVelocity();
  84. if(initVelocity > mMaxVelocity && mCurrentPage > 0){//如果是快速的向右滑,则需要显示上一个屏幕
  85. Log.d("TAG","----------------快速的向右滑--------------------");
  86. scrollToPage(mCurrentPage - 1);
  87. }else if(initVelocity < -mMaxVelocity && mCurrentPage < (getChildCount() - 1)){//如果是快速向左滑动,则需要显示下一个屏幕
  88. Log.d("TAG","----------------快速的向左滑--------------------");
  89. scrollToPage(mCurrentPage + 1);
  90. }else{//不是快速滑动的情况,此时需要计算是滑动到
  91. Log.d("TAG","----------------慢慢的滑动--------------------");
  92. slowScrollToPage();
  93. }
  94. recycleVelocityTracker();
  95. break;
  96. }
  97. return true;
  98. }
  99. /**
  100. * 缓慢滑动抬起手指的情形,需要判断是停留在本Page还是往前、往后滑动
  101. */
  102. private void slowScrollToPage() {
  103. //当前的偏移位置
  104. int scrollX = getScrollX();
  105. int scrollY = getScrollY();
  106. //判断是停留在本Page还是往前一个page滑动或者是往后一个page滑动
  107. int whichPage = (getScrollX() + getWidth() / 2 ) / getWidth() ;
  108. scrollToPage(whichPage);
  109. }
  110. /**
  111. * 滑动到指定屏幕
  112. * @param indexPage
  113. */
  114. private void scrollToPage(int indexPage) {
  115. mCurrentPage = indexPage;
  116. if(mCurrentPage > getChildCount() - 1){
  117. mCurrentPage = getChildCount() - 1;
  118. }
  119. //计算滑动到指定Page还需要滑动的距离
  120. int dx = mCurrentPage * getWidth() - getScrollX();
  121. mScroller.startScroll(getScrollX(),0,dx,0,Math.abs(dx) * 2);//动画时间设置为Math.abs(dx) * 2 ms
  122. //记住,使用Scroller类需要手动invalidate
  123. invalidate();
  124. }
  125. @Override
  126. public void computeScroll() {
  127. Log.d("TAG", "---------computeScrollcomputeScrollcomputeScroll--------------");
  128. super.computeScroll();
  129. if(mScroller.computeScrollOffset()){
  130. scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
  131. invalidate();
  132. }
  133. }
  134. private void recycleVelocityTracker() {
  135. if (mVelocityTracker != null) {
  136. mVelocityTracker.recycle();
  137. mVelocityTracker = null;
  138. }
  139. }
  140. private void initVelocityTrackerIfNotExists() {
  141. if(mVelocityTracker == null){
  142. mVelocityTracker = VelocityTracker.obtain();
  143. }
  144. }
  145. }
布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> <com.lusheep.viewtest.view.MyViewPager3
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#999" >
<ImageView
android:layout_width="300dp"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/test1" /> <ImageView
android:layout_width="300dp"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/test2" /> <ImageView
android:layout_width="300dp"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/test3" /> <ImageView
android:layout_width="300dp"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/test4" />
</com.lusheep.viewtest.view.MyViewPager3>
</LinearLayout>
 
 
简单总结:
(1)Scroller类能够帮助我们实现高级的滑动功能,如手指抬起后的惯性滑动功能。使用流程为,首先通过Scroller类的startScroll()+invalidate()触发View的computeScroll(),在computeScroll()中让Scroller类去计算最新的坐标信息,拿到最新的坐标偏移信息后还是要调用View的scrollTo来实现滑动。可以看到,使用Scroller的整个流程比较简单,关键的是控制滑动的一些逻辑计算,比如上面例子中的计算什么时候该往哪一页滑动...
(2)Android后面推出了OverScroller类,OverScroller在整体功能上和Scroller类似,使用也相同。OverScroller类可以完全代替Scroller,相比Scroller,OverScroller主要是增加了对滑动到边界的一些控制,如增加一些回弹效果等,功能更加强大。

Android Scroller详解的更多相关文章

  1. Android Notification 详解(一)——基本操作

    Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...

  2. Android Notification 详解——基本操作

    Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...

  3. Android ActionBar详解

    Android ActionBar详解 分类: Android2014-04-30 15:23 1094人阅读 评论(0) 收藏 举报 androidActionBar   目录(?)[+]   第4 ...

  4. Android 签名详解

    Android 签名详解 AndroidOPhoneAnt设计模式Eclipse  在Android 系统中,所有安装 到 系统的应用程序都必有一个数字证书,此数字证书用于标识应用程序的作者和在应用程 ...

  5. Android编译系统详解(一)

    ++++++++++++++++++++++++++++++++++++++++++ 本文系本站原创,欢迎转载! 转载请注明出处: http://blog.csdn.net/mr_raptor/art ...

  6. Android布局详解之一:FrameLayout

      原创文章,如有转载,请注明出处:http://blog.csdn.net/yihui823/article/details/6702273 FrameLayout是最简单的布局了.所有放在布局里的 ...

  7. 【整理修订】Android.mk详解

    Android.mk详解 1. Android.mk 的应用范围 Android.mk文件是GNU Makefile的一小部分,它用来对Android程序进行编译. 一个Android.mk文件可以编 ...

  8. Android菜单详解(四)——使用上下文菜单ContextMenu

    之前在<Android菜单详解(二)——创建并响应选项菜单>和<Android菜单详解(三)——SubMenu和IconMenu>中详细讲解了选项菜单,子菜单和图标菜单.今天接 ...

  9. Android签名详解(debug和release)

    Android签名详解(debug和release)   1. 为什么要签名 1) 发送者的身份认证 由于开发商可能通过使用相同的Package Name来混淆替换已经安装的程序,以此保证签名不同的包 ...

随机推荐

  1. 【iCore4 双核心板_FPGA】实验二十:NIOS II之UART串口通信实验

    实验指导书及源代码下载地址: 链接:https://pan.baidu.com/s/1g_tWYYJxh4EgiGvlfkVu1Q 提取码:dwwa 复制这段内容后打开百度网盘手机App,操作更方便哦 ...

  2. Look Further to Recognize Better: Learning Shared Topics and Category-Specific Dictionaries for Open-Ended 3D Object Recognition

    张宁 Look Further to Recognize Better: Learning Shared Topics and Category-Specific Dictionaries for O ...

  3. Ant Design Pro Vue 时间段查询 问题

    <a-form-item label="起止日期" :labelCol="{lg: {span: 7}, sm: {span: 7}}" :wrapper ...

  4. python基础】——python添加模块搜索路径和包的导入

    方法一:函数添加1 import sys2 查看sys.path3 添加sys.path.append("c:\\") 方法二:修改环境变量w用户可以修改系统环境变量PYTHONP ...

  5. [转]casperjs截图出现黑色背景

    原文地址:https://my.oschina.net/tuxpy/blog/879509?utm_medium=referral 如果默认没有指定 body的background-color就会出现 ...

  6. [Arch] 域名解析常用两步设置

    主站   A 记录        (对应IPv4)   主机记录: @ (表示解析到不带 www 主域名)   值为 IP 主站 CNAME 记录 (表示别名) 主机记录: www   (表示解析到带 ...

  7. 多分类评测标准(micro 和 macro)

  8. 局域网-断网&劫持(kali)

    1.查看局域网中的主机 fping –asg 192.168.1.0/24 2.断网 arpspoof -i wlan0 -t 192.168.100 192.168.1.1 (arpspoof  - ...

  9. P4Merge的使用

    (官网: https://www.perforce.com/products/helix-core-apps/merge-diff-tool-p4merge 可以作为一个stand alone app ...

  10. netcore 步骤

    1.创建工程目录 d:\project 2.进入目录,创建解决方案 dotnet new sln 3.确定开发版本 dotnet --list-sdks //列出sdk版本 dotnet new gl ...