Android学习Scroller(五)——详解Scroller调用过程以及View的重绘

简介: PS:该篇博客已经deprecated,不再维护,详情请参见 站在源码的肩膀上全解Scroller工作机制 http://blog.csdn.net/lfdfhl/article/details/53143114MainActivity如下:package cc.



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()方法中依据已经消逝的时间就很容易
 *   得到当前时刻应该所处的位置并将其保存在变量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

相关文章
|
1月前
|
XML 前端开发 Android开发
Android:UI:Drawable:View/ImageView与Drawable
通过本文的介绍,我们详细探讨了Android中Drawable、View和ImageView的使用方法及其相互关系。Drawable作为图像和图形的抽象表示,提供了丰富的子类和自定义能力,使得开发者能够灵活地实现各种UI效果。View和ImageView则通过使用Drawable实现了各种图像和图形的显示需求。希望本文能为您在Android开发中使用Drawable提供有价值的参考和指导。
40 2
|
1月前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
37 5
|
2月前
|
缓存 数据处理 Android开发
在 Android 中使用 RxJava 更新 View
【10月更文挑战第20天】使用 RxJava 来更新 View 可以提供更优雅、更高效的解决方案。通过合理地运用操作符和订阅机制,我们能够轻松地处理异步数据并在主线程中进行 View 的更新。在实际应用中,需要根据具体情况进行灵活运用,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在 Android 中使用 RxJava 更新 View 的技巧和方法,为开发高质量的 Android 应用提供有力支持。
|
2月前
|
缓存 调度 Android开发
Android 在子线程更新 View
【10月更文挑战第21天】在 Android 开发中,虽然不能直接在子线程更新 View,但通过使用 Handler、AsyncTask 或 RxJava 等方法,可以实现子线程操作并在主线程更新 View 的目的。在实际应用中,需要根据具体情况选择合适的方法,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在子线程更新 View 的技巧和方法,为开发高质量的 Android 应用提供支持。
38 2
|
2月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
2月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
27 2
|
2月前
|
Web App开发 编解码 视频直播
视频直播技术干货(十二):从入门到放弃,快速学习Android端直播技术
本文详细介绍了Android端直播技术的全貌,涵盖了从实时音视频采集、编码、传输到解码与播放的各个环节。文章还探讨了直播中音视频同步、编解码器选择、传输协议以及直播延迟优化等关键问题。希望本文能为你提供有关Andriod端直播技术的深入理解和实践指导。
56 0
|
Android开发 容器
Android官方开发文档Training系列课程中文版:手势处理之滚动动画及Scroller
原文地址:http://android.xsoftlab.net/training/gestures/scroll.html 在Android中,滑动经常由ScrollView类来实现。
840 0
|
Android开发
Android开发之Scroller
什么是Scroller? 翻译为弹性滑动对象,可以实现View的弹性滑动动画,与Scroller相关的就是大家比较熟悉的scrollTo和scrollBy方法,可以用来实现View的滑动,但是它们的缺点就是瞬间完成,无法很平滑地过渡,而Scroller可以帮助我们很平滑地进行弹性滑动。
811 0
|
15天前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
40 19