以下是ScrollView的滑动效果代码,其中修复了一个事件拦截的BUG. public class MyScrollView extends ViewGroup { private PageChangeListener pageChangeListener; public PageChangeListener getPageChangeListener() { return pageChangeListener; } public void setPageChangeListener(PageChangeListener pageChangeListener) { this.pageChangeListener = pageChangeListener; } /** * 页面改变的监听者,当页面改变的时候就会回调 * * @author afu * */ public interface PageChangeListener{ /** * 页面改变的时候回调,回传当前页面的下标 * @param currIndex 下标值 */ public void moveTo(int currIndex); } /** * 测量当前View * 1.如果当前view是ViewGroup,就义务测量它的孩子,孩子在测量它的孩子。形成树状测量 * 2.测量过程,测量父类,测量孩子; * 3.测量不只一次,测量多次; * 4.widthMeasureSpec很大,包含了父类给这个控件的宽和父类给的模式 * 5.模式:1.未指定;2.指定,在一定的位置内;3.至多 * * onMeasure测量过程 * 1.父类给当前View的widthMeasureSpec保护宽和父类给的 模式,根据这两个只就可以得到width和模式 * 2.给孩子的宽的计算方式,宽减掉padding的值,得到孩子该有的空间 * 3.根据width 和父类的模式MeasureSpec, 得到新的newWith如下: * MeasureSpec.makeMeasureSpec * 4.孩子再去测量还在。 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //父类给这是控件的宽 int width = MeasureSpec.getSize(widthMeasureSpec); //这个是父类给孩子的模式 int Mode = MeasureSpec.getMode(widthMeasureSpec); //得到宽,父类给的宽减掉padding值,得到该给孩子多少空间 // getWidth(); //根据当前View的width和模式,得到该给孩子多少宽度 int newWidth = MeasureSpec.makeMeasureSpec(width, Mode); // System.out.println(width+" : "+Mode); //把孩子遍历出来测量-才能得到父类给孩子的大小-当前View该显示多宽 for(int i=0;i<getChildCount();i++){ View view = getChildAt(i); //父类有多高就给多高,还包括宽 view.measure(widthMeasureSpec, heightMeasureSpec); } } // View加载到显示的过程 // 1.构造方法执行 // 2.测量-onMeaseure // 3.指定View的位置和大小onLayout // 4.绘制View,onDraw /** * 指定View的位置和大小 如果这个View是ViewGroup的孩子,它将有义务和责任指定它孩子的位置和大小 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // 指定每个孩子的位置和大小 // 遍历 for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); // 设置高和宽 child.layout(0 + i * getWidth(), 0, getWidth() + i * getWidth(), getHeight()); } } /** * 事件的分发 * 一般是不实现这个方法 */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { return super.dispatchTouchEvent(ev); } // 手势识别器的使用 // 1.定义手势识别器 // 2.实例化 // 3.使用手势识别器onTouchEnvent(); private GestureDetector detector; private Scroller scroller; private void initView(Context context){ scroller = new Scroller(context); detector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener(){ //手指触摸滚动 /** * distanceX:水平方向要移动的距离 * distanceY:竖直方向要移动的距离 */ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { /** * 根据距离移动到指定的位置 * x :在X轴上要移动的距离 * Y :在Y轴上要移动的距离 */ scrollBy((int)distanceX, 0); /** * 根据坐标移动到指定的位置 * x:在x轴要移动的坐标 * y:在y轴要移动的坐标 */ // scrollTo(x, y); return true; } /** * 滑动效果 */ @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // TODO Auto-generated method stub return super.onFling(e1, e2, velocityX, velocityY); } }); } /** * 第一次按下的坐标 */ private float startX; /** * 代表当前在那个页面,下标 */ private int currIndex; /** * 第一按下的X轴坐标 */ private float downX; /** * 第一次按下的Y轴坐标 */ private float downY; /** * 拦截onTouchEvent事件 * 但返回true,就触发当前View的onTouchEvent事件,当前View也就是myScrollView的左右滚动就起效果了 * 如果返回flase,就把这个事件传给当前View(myScrollView)的孩子,孩子得到这个事件后就可以,触发孩子的onTouchEvent事件 * 这个方法默认是传递给孩子 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean result = false; // return super.onInterceptTouchEvent(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN://按下 detector.onTouchEvent(ev); System.out.println("onInterceptTouchEvent"+": ACTION_DOWN "+result); //1.记录第一次按下的坐标 downX = ev.getX(); downY = ev.getY(); break; case MotionEvent.ACTION_MOVE://移动 System.out.println("onInterceptTouchEvent"+": ACTION_MOVE "+result); //2.来到新的坐标 float newX = ev.getX(); float newY = ev.getY(); //3.计算偏移量-计算距离-在水平方向和竖直方向的距离 float dX = Math.abs(newX - downX); float dY = Math.abs(newY - downY); //水平方向滑动大于竖直方向 if(dX > dY){ result = true; } break; case MotionEvent.ACTION_UP://离开 System.out.println("onInterceptTouchEvent"+": ACTION_UP "+result); break; default: break; } System.out.println("onInterceptTouchEvent=="+ result); return result; } @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event);//执行父类的方法 System.out.println("onTouchEvent"); detector.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN://按下 System.out.println("onTouchEvent"+": ACTION_DOWN "); //1.按下坐标记录 startX = event.getX(); break; case MotionEvent.ACTION_MOVE://移动 System.out.println("onTouchEvent"+": ACTION_MOVE "); break; case MotionEvent.ACTION_UP://离开 System.out.println("onTouchEvent"+": ACTION_UP "); //2.记录新的点的坐标 float endX = event.getX(); //3.计算偏移量 // float dX = endX - startX; int temIndex = currIndex; if((endX - startX)>getWidth()/2){ //显示上一个子页面 temIndex--; }else if((startX - endX)>getWidth()/2){ //显示下一个子页面 temIndex++; } //移动到那个要么 moveTo(temIndex); break; default: break; } return true; } /** * 根据下标位置移动到对应的页面和屏蔽非法值 * @param temIndex 当前页面的下标 */ public void moveTo(int temIndex) { if(temIndex < 0){ temIndex = 0; } if(temIndex > getChildCount()-1){ temIndex = getChildCount()-1; } //新的当前坐标 currIndex = temIndex; if(pageChangeListener!= null){ pageChangeListener.moveTo(currIndex); } //移动的是坐标currIndex*getWidth():就是要移动到的X轴的坐标 //距离=末尾-起始 int distanceX = currIndex*getWidth() - getScrollX(); // scroller.startScroll(getScrollX(), 0, distanceX, 0); scroller.startScroll(getScrollX(), 0, distanceX, 0, Math.abs(distanceX)); // scrollTo(currIndex*getWidth(), 0); //刷新View会导致onDraw方法执行 computeScroll(); invalidate(); } @Override public void computeScroll() { // super.computeScroll(); if(scroller.computeScrollOffset()){ float X = scroller.getCurrX(); //根据距离去移动 // scrollBy((int)distanceX, 0); //根据坐标去移动 scrollTo((int)X, 0); invalidate(); } } public MyScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(context); } // 在布局文件中使用,实例化时候用到它 public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } // 代码中用到 public MyScrollView(Context context) { super(context); initView(context); } }
同时,自定义了MyScroller,实现了页面跳转时的缓慢移动效果。
public class MyScroller { public MyScroller(Context context){ } /** * 开始滑动的X轴的起始坐标 */ private float startX; /** * 开始滑动的Y轴的起始坐标 */ private float startY; /** * 在X轴的移动的距离 */ private float distanceX; /** * 在Y轴的移动的距离 */ private float distanceY; /** * 开始移动一小段的起始时间 */ private long startTime; /** * 是否移动完成 * true已经移动完成了 * false没有已经完成 */ private boolean isFinish; /** * 人为指定总共距离的总共时间为500毫秒 */ private long totalTime = 500; /** * 移动到那个坐标 */ private float currX; /** * 开始滚动或者开始滚动了 * 还要计算起始时间和是否已经移动完成了 * @param startX * @param startY * @param distanceX * @param distanceY */ public void startScroll(float startX,float startY,float distanceX,float distanceY){ this.startX = startX; this.startY = startY; this.distanceX = distanceX; this.distanceY = distanceY; //记录开始时间 this.startTime =SystemClock.uptimeMillis(); this.isFinish = false; } /** * 计算一小段一小段的距离后的坐标和这一小段花的时间 * @return * true正在移动 * false已经移动结束 */ public boolean computeScrollOffset(){ if(isFinish){ return false; } //这一小段结束的时间 long endTime = SystemClock.uptimeMillis(); //这一小段花的时间 long passTime = endTime - startTime; if(passTime < totalTime){ //还在移动 //计算移动的速度(平均速度) = 距离/时间 // float volecityX = distanceX / totalTime; this.currX = startX + passTime * distanceX / totalTime; }else{ //不一定了 this.currX = startX + distanceX; isFinish = true; } return true; } /** * 得到要移动这一小段的距离 * @return */ public float getCurrX() { return currX; } public void setCurrX(float currX) { this.currX = currX; } }
在activity中,通过RadioButton来控制页面的变化
/** * 图片资源ID */ private int[] ids = { R.drawable.a1, R.drawable.a2, R.drawable.a3, R.drawable.a4, R.drawable.a5, R.drawable.a6 }; private MyScrollView msv; private RadioGroup rg_point_group; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); msv = (MyScrollView) findViewById(R.id.msv); rg_point_group = (RadioGroup) findViewById(R.id.rg_point_group); //添加子者六个页面 for(int i=0;i<ids.length;i++){ ImageView imageView = new ImageView(this); imageView.setBackgroundResource(ids[i]); msv.addView(imageView); } //添加测试页面 View test = View.inflate(this, R.layout.test, null); msv.addView(test, 2); //有多少个页面就添加多少个指示点 for(int i=0;i<msv.getChildCount();i++){ RadioButton rb = new RadioButton(this); rb.setId(i); if(i ==0){ rb.setChecked(true); }else{ rb.setChecked(false); } rg_point_group.addView(rb); } //设置页面改变的监听 msv.setPageChangeListener(new PageChangeListener() { //当前的ID和下标值是一样 @Override public void moveTo(int currIndex) { //把全部设置为默认的 // for(int i =0;i<rg_point_group.getChildCount();i++){ // RadioButton radioButton = (RadioButton) rg_point_group.getChildAt(i); // radioButton.setChecked(false); // } // //把当前RadioButton设置为选中状态 // RadioButton radioButton = (RadioButton) rg_point_group.getChildAt(currIndex); // radioButton.setChecked(true); rg_point_group.check(currIndex); } }); //设置RadioGroup的状态,监听选中那个孩子 rg_point_group.setOnCheckedChangeListener(new OnCheckedChangeListener() { //id和指示点的下标一样 @Override public void onCheckedChanged(RadioGroup group, int checkedId) { //移动到指定的页面 msv.moveTo(checkedId); } }); }