PS:

该篇博客已经deprecated,不再维护。详情请參见 

站在源代码的肩膀上全解Scroller工作机制

 http://blog.csdn.net/lfdfhl/article/details/53143114

MainActivity例如以下:

package cc.ww;

import android.os.Bundle;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams;
import android.app.Activity;
import android.content.Context; public class MainActivity extends Activity {
private Context mContext;
private int [] imagesArray;
private ScrollLauncherViewGroup mScrollLauncherViewGroup;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
} private void init(){
mContext=this;
imagesArray=new int []{R.drawable.a,R.drawable.b,R.drawable.c,R.drawable.d};
mScrollLauncherViewGroup=new ScrollLauncherViewGroup(mContext);
ImageView imageView=null;
RelativeLayout.LayoutParams layoutParams=null;
for (int i = 0; i < imagesArray.length; i++) {
imageView=new ImageView(mContext);
imageView.setScaleType(ScaleType.FIT_XY);
imageView.setImageResource(imagesArray[i]);
layoutParams=new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
imageView.setLayoutParams(layoutParams);
mScrollLauncherViewGroup.addView(imageView);
}
setContentView(mScrollLauncherViewGroup);
} }

ScrollLauncherViewGroup例如以下:

package cc.ww;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
import android.widget.Toast;
/**
* Scroller原理:
* 为了让View或者ViewGroup的内容发生移动,我们经常使用scrollTo()和scrollBy()方法.
* 但这两个方法运行的速度都非常快,瞬间完毕了移动感觉比較生硬.
* 为了使View或者ViewGroup的内容发生移动时比較平滑或者有其它的移动渐变效果
* 可採用Scroller来实现.
* 在详细实现时,我们继承并重写View或者ViewGroup时可生成一个Scroller由它来详细
* 掌控移动过程和结合插值器Interpolator调用scrollTo()和scrollBy()方法.
*
*
* Scroller的两个主要构造方法:
* 1 public Scroller(Context context) {}
* 2 public Scroller(Context context, Interpolator interpolator){}
* 採用第一个构造方法时,在移动中会採用一个默认的插值器Interpolator
* 也可採用第二个构造方法,为移动过程指定一个插值器Interpolator
*
*
* Scroller的调用过程以及View的重绘:
* 1 调用public void startScroll(int startX, int startY, int dx, int dy)
* 该方法为scroll做一些准备工作.
* 比方设置了移动的起始坐标,滑动的距离和方向以及持续时间等.
* 该方法并非真正的滑动scroll的開始,感觉叫prepareScroll()更贴切些.
*
* 2 调用invalidate()或者postInvalidate()使View(ViewGroup)树重绘
* 重绘会调用View的draw()方法
* draw()一共同拥有六步:
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
* 当中最重要的是第三步和第四步
* 第三步会去调用onDraw()绘制内容
* 第四步会去调用dispatchDraw()绘制子View
* 重绘分两种情况:
* 2.1 ViewGroup的重绘
* 在完毕第三步onDraw()以后,进入第四步ViewGroup重写了
* 父类View的dispatchDraw()绘制子View,于是这样继续调用:
* dispatchDraw()-->drawChild()-->child.computeScroll();
* 2.2 View的重绘
* 我们注意到在2提到的"调用invalidate()".那么对于View它又是怎么
* 调用到了computeScroll()呢?View没有子View的.所以在View的源代码里能够
* 看到dispatchDraw()是一个空方法.所以它的调用路径和ViewGroup是不一样的.
* 在此不禁要问:假设一个ButtonSubClass extends Button 当mButtonSubClass
* 运行mButtonSubClass.scrollTo()方法时怎么触发了ButtonSubClass类中重写
* 的computeScroll()方法??? * 在这里我也比較疑惑,仅仅有借助网上的资料和源代码去从invalidate()看起.
* 总的来说是这种:当View调用invalidate()方法时,会导致整个View树进行
* 从上至下的一次重绘.比方从最外层的Layout到里层的Layout,直到每一个子View.
* 在重绘View树时ViewGroup和View时按理都会经过onMeasure()和onLayout()以及
* onDraw()方法.当然系统会推断这三个方法是否都必须运行,假设没有必要就不会调用.
* 看到这里就明确了:当这个子View的父容器重绘时,也会调用上面提到的线路:
* onDraw()-->dispatchDraw()-->drawChild()-->child.computeScroll();
* 于是子View(比方此处举例的ButtonSubClass类)中重写的computeScroll()方法
* 就会被调用到.
*
* 3 View树的重绘会调用到View中的computeScroll()方法
*
* 4 在computeScroll()方法中
* 在View的源代码中能够看到public void computeScroll(){}是一个空方法.
* 详细的实现须要自己来写.在该方法中我们可调用scrollTo()或scrollBy()
* 来实现移动.该方法才是实现移动的核心.
* 4.1 利用Scroller的mScroller.computeScrollOffset()推断移动过程是否完毕
* 注意:该方法是Scroller中的方法而不是View中的!!!!!!
* public boolean computeScrollOffset(){ }
* 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.
* 返回true时表示还移动还没有完毕.
* 4.2 若动画没有结束,则调用:scrollTo(By)();
* 使其滑动scrolling
*
* 5 再次调用invalidate().
* 调用invalidate()方法那么又会重绘View树.
* 从而跳转到第3步,如此循环,直到computeScrollOffset返回false
*
*
*
* 详细的滑动过程,请參见示图
*
*
*
*
*
* 通俗的理解:
* 从上可见Scroller运行流程里面的三个核心方法
* mScroller.startScroll()
* mScroller.computeScrollOffset()
* view.computeScroll()
* 1 在mScroller.startScroll()中为滑动做了一些初始化准备.
* 比方:起始坐标,滑动的距离和方向以及持续时间(有默认值)等.
* 事实上除了这些,在该方法内还做了些其它事情:
* 比較重要的一点是设置了动画開始时间.
*
* 2 computeScrollOffset()方法主要是根据当前已经消逝的时间
* 来计算当前的坐标点而且保存在mCurrX和mCurrY值中.
* 由于在mScroller.startScroll()中设置了动画时间,那么
* 在computeScrollOffset()方法中根据已经消逝的时间就非常easy
* 得到当前时刻应该所处的位置并将其保存在变量mCurrX和mCurrY中.
* 除此之外该方法还可推断动画是否已经结束.
*
* 所以在该演示样例中:
* @Override
* public void computeScroll() {
* super.computeScroll();
* if (mScroller.computeScrollOffset()) {
* scrollTo(mScroller.getCurrX(), 0);
* invalidate();
* }
* }
* 先运行mScroller.computeScrollOffset()推断了滑动是否结束
* 2.1 返回false,滑动已经结束.
* 2.2 返回true,滑动还没有结束.
* 而且在该方法内部也计算了最新的坐标值mCurrX和mCurrY.
* 就是说在当前时刻应该滑动到哪里了.
* 既然computeScrollOffset()如此贴心,盛情难却啊!
* 于是我们就覆写View的computeScroll()方法,
* 调用scrollTo(By)滑动到那里!满足它的一番苦心吧.
*
*
* 备注说明:
* 1 演示样例没有做边界推断和一些优化,在这方面有bug.
* 重点是学习Scroller的流程
* 2 不用纠结getCurrX()与getScrollX()有什么区别,二者得到的值一样.
* 但要注意它们是属于不同类里的.
* getCurrX()-------> Scroller.getCurrX()
* getScrollX()-----> View.getScrollX()
*
*
*/
public class ScrollLauncherViewGroup extends ViewGroup {
private int lastX;
private int currentX;
private int distanceX;
private Context mContext;
private Scroller mScroller;
public ScrollLauncherViewGroup(Context context) {
super(context);
mContext=context;
mScroller=new Scroller(context);
} public ScrollLauncherViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
} public ScrollLauncherViewGroup(Context context, AttributeSet attrs,int defStyle) {
super(context, attrs, defStyle);
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /**
* 注意:
* 1 getWidth()和getHeight()得到是屏幕的宽和高
* 由于在布局时指定了该控件的宽和高为fill_parent
* 2 view.getScrollX(Y)()得打mScrollX(Y)
* 3 调用scrollTo(x, y)后,x和y分别被赋值给mScrollX和mScrollY
* 请注意坐标方向.
*/
@Override
protected void onLayout(boolean arg0, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
childView.layout(i*getWidth(), 0,getWidth()+ i*getWidth(),getHeight());
}
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
} @Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX=(int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
currentX=(int) event.getX();
distanceX=currentX-lastX;
mScroller.startScroll(getScrollX(), 0, -distanceX, 0);
break;
case MotionEvent.ACTION_UP:
//手指从屏幕右边往左滑动,手指抬起时滑动到下一屏
if (distanceX<0&&Math.abs(distanceX)>50) {
mScroller.startScroll(getScrollX(), 0, getWidth()-(getScrollX()%getWidth()), 0);
//手指从屏幕左边往右滑动,手指抬起时滑动到上一屏
} else if (distanceX>0&&Math.abs(distanceX)>50) {
mScroller.startScroll(getScrollX(), 0, -(getScrollX()%getWidth()), 0);
}
break; default:
break;
}
//重绘View树
invalidate();
return true;
} @Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), 0);
invalidate();
}else{
if (mScroller.getCurrX()==getWidth()*(getChildCount()-1)) {
Toast.makeText(mContext, "已滑动到最后一屏", Toast.LENGTH_SHORT).show();
}
}
} }

main.xml例如以下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" > <cc.ww.ScrollLauncherViewGroup
android:id="@+id/scrollLauncherViewGroup"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/> </RelativeLayout>

PS:

该篇博客已经deprecated。不再维护,详情请參见 

站在源代码的肩膀上全解Scroller工作机制

 http://blog.csdn.net/lfdfhl/article/details/53143114

Android学习Scroller(五)——具体解释Scroller调用过程以及View的重绘的更多相关文章

  1. 六、Android学习第五天——Handler的使用(转)

    (转自:http://wenku.baidu.com/view/af39b3164431b90d6c85c72f.html) 六.Android学习第五天——Handler的使用 注意:有很多功能是不 ...

  2. [Android FrameWork 6.0源码学习] View的重绘过程之Draw

    View绘制的三部曲,测量,布局,绘画现在我们分析绘画部分测量和布局 在前两篇文章中已经分析过了.不了解的可以去我的博客里找一下 下面进入正题,开始分析调用以及函数原理 private void pe ...

  3. [Android FrameWork 6.0源码学习] View的重绘过程之WindowManager的addView方法

    博客首页:http://www.cnblogs.com/kezhuang/p/关于Activity的contentView的构建过程,我在我的博客中已经分析过了,不了解的可以去看一下<[Andr ...

  4. [Android FrameWork 6.0源码学习] View的重绘过程

    View绘制的三部曲,  测量,布局,绘画今天我们分析测量过程 view的测量是从ViewRootImpl发起的,View需要重绘,都是发送请求给ViewRootImpl,然后他组织重绘在重绘的过程中 ...

  5. Android View的重绘过程之Draw

    博客首页:http://www.cnblogs.com/kezhuang/p/ View绘制的三部曲,测量,布局,绘画现在我们分析绘画部分测量和布局 在前两篇文章中已经分析过了.不了解的可以去我的博客 ...

  6. Android View的重绘过程之WindowManager的addView方法

    博客首页:http://www.cnblogs.com/kezhuang/p/ 关于Activity的contentView的构建过程,我在我的博客中已经分析过了,不了解的可以去看一下 <[An ...

  7. Android View的重绘过程之Measure

    博客首页:http://www.cnblogs.com/kezhuang/p/ View绘制的三部曲,  测量,布局,绘画今天我们分析测量过程 view的测量是从ViewRootImpl发起的,Vie ...

  8. 【Android】利用自己定义View的重绘实现拖动移动,获取组件的尺寸

    以下利用一个app来说明怎样利用自己定义View的重绘实现拖动移动.获取组件的尺寸. 例如以下图,触摸拖动,或者轻轻点击屏幕都能移动图片.假设碰到文字,则会弹出提示. 这里是利用自己定义View的重绘 ...

  9. Android 学习笔记之WebService实现远程调用+内部原理分析...

    PS:终于可以抽出时间写写博客了,忙着学校的三周破实训外加替考...三周了,没怎么学习...哎... 学习内容: 1.WebService 实现远程方法的调用   什么是WebService...   ...

随机推荐

  1. BZOJ4012[HNOI2015]开店——树链剖分+可持久化线段树/动态点分治+vector

    题目描述 风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到 人生哲学.最近她们灵机一动,打算在幻想乡开一家小店来做生意赚点钱.这样的 想法当然非常好啦,但是她们也发现她们面临着一个 ...

  2. 3ds max学习笔记-- 复合对象运算

    1,ProBoolean(超级布尔) 栗子: 新建一长方体,两个圆柱体,如下: 选择底部长方体,进入[复合对象],修改[操作],单击[拾取操作对象B],点击圆柱: 效果如下,线面较多: 高级布尔效果图 ...

  3. NLP 工具类库

    NLPIR http://www.nlpir.org/ HanLP https://github.com/hankcs Apache OpenNLP   https://opennlp.apache. ...

  4. poj1064 Cable master(二分查找,精度)

    https://vjudge.net/problem/POJ-1064 二分就相当于不停地折半试. C++AC,G++WA不知为何,有人说C函数ans那里爆int了,改了之后也没什么用. #inclu ...

  5. 如何移除HTML5 input在type="number"时的上下小箭头

      在chrome下: input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{    -webkit-appearance ...

  6. Taints 与 Tolerations

    节点亲和性是描述Pods如何分配到一个或一组节点的策略,亲和性的相关资料可以参考Kubernetes中的亲和性与反亲和性.与亲和性规则不同, Taints 描述节点拒绝一个或一组Pods的策略.其实现 ...

  7. golang time打印出的值是62135596800的来源

    ' 减去62135596800是将"以公元1年1月1日0点为基准"改成"以1970年1月1日0点"为基准 所以,数据库datetime的默认值 : 0000-0 ...

  8. mac的vscode配置使用zsh

    配置文件 "terminal.integrated.shell.osx": "zsh"

  9. 每天一个linux命令(8):rm

    1.命令简介 rm(Remove file 删除目录或文件)删除文件,对于链接文件,只是删除整个链接文件,而原有文件保持不变. 2.用法 rm [选项]... 文件.. 3.选项 -f, –force ...

  10. [转]PID控制算法原理

    PID控制算法是工业界使用极其广泛的一个负反馈算法,相信这个算法在做系统软件时也有用武之处,这里摘录了知乎上的一篇文章,后面学习更多后自己总结一篇 以下为原文: PID控制应该算是应用非常广泛的控制算 ...