自定义ScrollView

以下是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);
				
			}
		});
		
		
	}
 
 

 

你可能感兴趣的:(android,自定义控件,scrollview,scroller)