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. JS 函数表达式

    定义函数的方式有两种, 一种是函数声明,一种就是函数表达式了 函数声明最常见了, sayHi(); // 函数声明会发生提升 function sayHi () { alert('Hi') } 函数表 ...

  2. 深入理解JVM(6)——JVM性能调优实战

    如何在高性能服务器上进行JVM调优:以便充分利用高性能服务器的硬件资源,有两种JVM调优方案. 一.        采用64位操作系统,并为JVM分配大内存 分析:如果JVM中堆内存太小,那么就会频繁 ...

  3. RFC-TCP

    RFC: 793 TRANSMISSION CONTROL PROTOCOL DARPA INTERNET PROGRAM PROTOCOL SPECIFICATION September 1981 ...

  4. 轻量对象存储服务——minio

    minio Minio是一个非常轻量的对象存储服务. Github: minio 它本身不支持文件的版本管理.如果有这个需求,可以用 s3git 搭配使用. Github: s3git 安装 mini ...

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

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

  6. [CentOS7]redis设置开机启动,设置密码

    简介 上篇文章介绍了如何安装redis,但每次重启服务器之后redis不会自启,这里将介绍如何进行自启设置,以及如何设置redis的密码,进行密码验证登陆. 上篇文章: Centos7安装Redis ...

  7. Vue.JS React 精彩文章汇总

    JavaScript深入系列  [干货] JavaScript数组所有API全解密  [干货] 移动端:页面->手淘互动动效的探索 - IT大咖说 - 大咖干货,不再错过 [扫盲] Jonath ...

  8. HIVE开发总结

    基本数据类型 查看所有函数 搜索函数 搜索表 查看函数使用方法 关键字补全 显示表头 SET环境变量 查看建表语句.数据文件置 执行外部命令 NVL CONCAT IF CASE TRIM SUBST ...

  9. python3 cookie

    最近再次学习python,本来就是一个菜鸟,我按照 Python CGI编程 发现读cookie 读取不了,后来发现它这种写的方式也不怎么靠谱. Python中Cookie模块(python3中为ht ...

  10. 微信小程序生成海报分享:canvas绘制文字溢出如何换行

    主要思路: 1.先分割为字符串数组,然后一个字一个字绘图,利用ctx.measureText(string) 方法,获取绘制后的宽度,判断宽度在多少内就另起一行,再将各行拼成一个字符串 2.计算另起的 ...