在学习使用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. LiteIDE 设置默认编译输出位置

    go build命令默认输出路径是当前工作路径,这个显得比较乱,我习惯于把输出文件放置在项目的build目录下,这样方便查找,已经同步备份的时候排除不必要的文件. go build命令可以指定-o 输 ...

  2. 两款不错的js甘特图控件

    dhtmlx:https://docs.dhtmlx.com/ jQuery.Gantt:http://taitems.github.io/jQuery.Gantt/

  3. 重装系统之前需要做的checklist

    1. 各浏览器 ---- 导出收藏夹 2. 备份桌面 3. 查用工具截图保存.保存使用了哪些工具 4.查看C盘有没有放置其他资料,需要备份的

  4. java读取IFC文件

    The IFC JAVA Toolbox can read IFC STEP files and IFCZIP files from any data source that implementsja ...

  5. mysql查看连接情况

    1.使用navicat进入命令行或者命令行进入mysql 2.看所有进程 show full processlist; 3.看所有连接show status like 'Threads%';

  6. Python线程池及其原理和使用(超级详细)

    系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互.在这种情形下,使用线程池可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池. 线程池在系统启动时即 ...

  7. FastJson 对json中的KEY值的大小写转换方法

    /** * json大写转小写 * * @return JSONObject */ public static JSONObject transToLowerObject(String json) { ...

  8. python的mysql数据库操作

    python操作mysql数据库 Python 标准数据库接口为 Python DB-API,Python DB-API为开发人员提供了数据库应用编程接口. Python 数据库接口支持非常多的数据库 ...

  9. Sqlserver (转载)事物与锁

    1   概述 本篇文章简要对事物与锁的分析比较详细,因此就转载了. 2   具体内容 并发可以定义为多个进程同时访问或修改共享数据的能力.处于活动状态而互不干涉的并发用户进程的数量越多,数据库系统的并 ...

  10. Flume和 Sqoop

    Sqoop简介 Sqoop是一种旨在有效地在Apache Hadoop和诸如关系数据库等结构化数据存储之间传输大量数据的工具 原理: 将导入或导出命令翻译成Mapreduce程序来实现. 在翻译出的M ...