package com.itheima.myscrollview28;import android.content.Context;import android.util.AttributeSet;import android.view.GestureDetector;import android.view.GestureDetector.OnGestureListener;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.widget.Scroller;/*** 自定义一个MyScrollView继承ViewGroup类,可以实现视图滚动的效果,只不过系统控件中ViewPager类也可以实现该效果* 这里只是要理解系统中滚动视图动画的原理* @author 丁空华* 2015-3-5下午3:39:16*/public class MyScrollView extends ViewGroup{private Context ctx;//上下文//private MyScroller myScroller;//计算位移的工具类,这是自己定义的,帮助理解滑动动画的原理private Scroller myScroller;//系统计算位移的工具类private GestureDetector detector;//手势识别的工具类private int firstX = 0;//down 事件时的x坐标private int currId = 0;//当前的ID值,显示在屏幕上的子View的下标private MyPageChangedListener pageChangedListener;protected boolean isFling;//判断是否发生快速滑动的变量,true表示快速滑动,反之相反public MyScrollView(Context context, AttributeSet attrs) {super(context, attrs);this.ctx = context;initView();}/*** 初始化控件,实例化变量*/private void initView() {//myScroller = new MyScroller(ctx);myScroller = new Scroller(ctx);//给手势识别器定义手势识别的监听器detector = new GestureDetector(ctx, new OnGestureListener() {/*** 手指离开屏幕的事件*/@Overridepublic boolean onSingleTapUp(MotionEvent e) {return false;}/*** 手指在屏幕上按下的事件*/@Overridepublic void onShowPress(MotionEvent e) {}/*** 手指在屏幕上滑动的事件* @param e1 滑动事件开始时的事件对象* @param e2 滑动时的事件对象* @param distanceX 横坐标滑动的距离* @param distanceY 纵坐标滑动的距离*/@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,float distanceY) {//设置滑动的距离,该方法会调用scrollTo(mScrollX + x, mScrollY + y)方法,设置滚动的坐标scrollBy((int) distanceX, 0);return false;}/*** 手指在屏幕上长按下的事件*/@Overridepublic void onLongPress(MotionEvent e) {}/*** 手指在屏幕上发生快速滑动的事件* @param e1 滑动事件开始时的事件对象* @param e2 滑动时的事件对象* @param velocityX 横坐标滑动的距离* @param velocityY 纵坐标滑动的距离*/@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY) {//将控制是否快速滑动的变量至为trueisFling = true;if(velocityX>0 && currId>0){ // 快速向右滑动,要将显示的子View对象的表示值currId减1currId--;}else if(velocityX<0 && currId<getChildCount()-1){ // 快速向左滑动,要将显示的子View对象的表示值currId加1currId++;}moveToDest(currId);return false;}/*** 手指在屏幕上按下的事件*/@Overridepublic boolean onDown(MotionEvent e) {return false;}});}/*** 计算 控件大小,* 做为viewGroup 还有一个责任,计算 子view的大小,否则只会显示该父组件而不会显示其子控件* 若是在该控件中显示出一个ViewGroup对象,那么该ViewGroup还有责任计算该ViewGroup中的子View控件的宽和高** 由于每一个子View的宽高是由父View决定的,会根据子View的宽高模式来给定子View的宽高* MeasureSpec.UNSPECIFIED 该模式父类不会设置子类的宽高,根据onMeasure(widthMeasureSpec, heightMeasureSpec)方法传递的值来确定* MeasureSpec.AT_MOST 和MeasureSpec.EXACTLY 这两种模式会限制子View的宽高,分别是最大和精确*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int size = MeasureSpec.getSize(widthMeasureSpec);int mode = MeasureSpec.getMode(widthMeasureSpec);for (int i = 0; i < getChildCount(); i++) {View v = getChildAt(i);v.measure(widthMeasureSpec, heightMeasureSpec);// v.getMeasuredWidth() // 得到测量的大小}}@Override/*** 对子view进行布局,确定子view的位置* changed变量 若为true ,说明布局发生了变化* l\t\r\b\ 是指当前viewgroup 在其父view中的位置*/protected void onLayout(boolean changed, int l, int t, int r, int b) {for (int i = 0; i < getChildCount(); i++) {// 取得下标为i的子viewView view = getChildAt(i);//父view 会根据子view的需求,和自身的情况,来综合确定子view的位置,(确定他的大小)//指定子view的位置 , 左,上,右,下,是指在viewGroup坐标系中的位置view.layout(0+i*getWidth(), 0, getWidth()+i*getWidth(), getHeight());}}/*** 给该控件设置触摸事件*/@Overridepublic boolean onTouchEvent(MotionEvent event) {super.onTouchEvent(event);//将事件对象传递给手势识别器,手势识别器会走动解析该事件detector.onTouchEvent(event);//添加自己的事件解析switch (event.getAction()) {case MotionEvent.ACTION_DOWN:firstX = (int) event.getX();break;case MotionEvent.ACTION_MOVE:break;case MotionEvent.ACTION_UP:if(!isFling){// 在没有发生快速滑动的时候,才执行按位置判断curridint nextId = 0;if(event.getX()-firstX>getWidth()/2){ // 手指向右滑动,超过屏幕的1/2 当前的currid - 1nextId = currId-1;}else if(firstX - event.getX()>getWidth()/2){ // 手指向左滑动,超过屏幕的1/2 当前的currid + 1nextId = currId+1;}else{nextId = currId;}//移动到指定的屏幕上moveToDest(nextId);}//将快速滑动的变量至为falseisFling = false;break;}return true;}/*** 移动到指定的屏幕上* @param nextId 屏幕 的下标*/public void moveToDest(int nextId) {//对 nextId 进行判断 ,确保 是在合理的范围 即 nextId >=0 && next <=getChildCount()-1currId = (nextId>=0)?nextId:0;currId = (nextId<=getChildCount()-1)?nextId:(getChildCount()-1);//瞬间移动//scrollTo(currId*getWidth(), 0);//触发listener事件if(pageChangedListener!=null){pageChangedListener.moveToDest(currId);}//计算出移动的距离 要移动的距离 = 最终的位置 - 现在的位置int distance = currId*getWidth() - getScrollX();//调用系统图的Scoller设置滑动运行的时间myScroller.startScroll(getScrollX(),0,distance,0,Math.abs(distance));//刷新当前view,该方法会调用onDraw()方法 的执行invalidate();}/*** invalidate(); 会导致 computeScroll()这个方法的执行,执行完这个方法之后才会执行onDraw()方法* 计算滚动的坐标*/@Overridepublic void computeScroll() {if(myScroller.computeScrollOffset()){//计算动画偏移量,若是返回true的话动画会一直执行//获取需要滑动的距离int newX = (int) myScroller.getCurrX();//设置滑动的坐标scrollTo(newX, 0);//滑动完之后再重新调用invalidate()方法,invalidate()方法又继续调用该方法,又去computeScrollOffset()计算偏移量,//一直循环下去直到偏移量computeScrollOffset()返回为false,此时偏移量应该是0invalidate();};}/*** 页面更改时的监听接口*/public interface MyPageChangedListener{void moveToDest(int currid);}public MyPageChangedListener getPageChangedListener() {return pageChangedListener;}public void setPageChangedListener(MyPageChangedListener pageChangedListener) {this.pageChangedListener = pageChangedListener;}}
自定义MyScrolle类,理解系统类Scoller和动画滑动的原理
package com.itheima.myscrollview28;import android.content.Context;import android.os.SystemClock;/*** 计算位移的工具类* @author 丁空华* 2015-3-5下午3:40:56*/public class MyScroller {private int startX;private int startY;private int distanceX;private int distanceY;//开始执行动画的时间private long startTime;//判断是否正在执行动画,true 是还在运行,false 已经停止private boolean isFinish;//默认动画运行的时长,单位为毫秒值private int duration = 500;//当前的横坐标private long currX;//当前的纵坐标private long currY;/*** 无参数构造方法* @param ctx 上下文*/public MyScroller(Context ctx){}/*** 初始化下面的值,并设置isFinish变量为false* @param startX 开始时的X坐标* @param startY 开始时的Y坐标* @param disX X方向 要移动的距离* @param disY Y方向 要移动的距离*/public void startScroll(int startX, int startY, int disX, int disY) {this.startX = startX;this.startY = startY;this.distanceX = disX;this.distanceY = disY;this.startTime = SystemClock.uptimeMillis();//获取动画开始的时间this.isFinish = false;}/*** 计算一下当前的运行状况,也就是计算动画的偏移量* 根据滑动的快慢计算出动画的滑动效果,若是滑动时长在duration设置的范围之内则为快速滑动,否则为慢速滑动* return true 还在运行动画* return false 运行结束动画*/public boolean computeScrollOffset() {//判断动画是否在运行if (isFinish) {return false;}// 获得动画运行所用的时间long passTime = SystemClock.uptimeMillis() - startTime;if (passTime < duration) {// 如果滑动时间还在duration允许的范围内则定义为慢速滑动// 当前的坐标位置 = 开始的位置 + 移动的距离(距离 = 速度*时间)currX = startX + distanceX * passTime / duration;currY = startY + distanceY * passTime / duration;} else {//快速滑动currX = startX + distanceX;currY = startY + distanceY;//设置决定滑动快慢的变量为trueisFinish = true;}return true;}public long getCurrX() {return currX;}public void setCurrX(long currX) {this.currX = currX;}}
本文介绍了一个自定义的MyScrollView控件,它通过继承ViewGroup实现了视图滚动效果,并详细解释了内部滑动动画的工作原理。文章还提供了一个自定义的MyScroller类,帮助读者更好地理解系统中滑动动画的实现。
4656

被折叠的 条评论
为什么被折叠?



