`
liming1022
  • 浏览: 8484 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

自定义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反弹效果

    为了实现如同iOS那样具有真实感的滚动回弹效果,开发者通常需要自定义ScrollView。下面将详细介绍如何在Android中实现自定义ScrollView的反弹效果。 首先,我们需要创建一个新的ScrollView子类,例如命名为`...

    Android,自定义ScrollView,实现过度滑动时回弹效果

    要实现回弹效果,我们需要创建一个新的自定义ScrollView类,继承自Android的ScrollView,并重写其滚动相关的函数。关键在于计算当前滑动的位置,判断是否超过边界,然后模拟出回弹的动画效果。以下是一些核心步骤: ...

    自定义ScrollView实现阻尼效果+控件悬浮

    本话题主要探讨如何自定义ScrollView以实现阻尼效果和控件悬浮这两个功能。 一、自定义ScrollView实现阻尼效果 阻尼效果通常是指在滚动过程中,滚动速度会随着手指离开屏幕而逐渐减慢,直到停止,这种模拟真实物理...

    Android 自定义ScrollView ListView 体验各种纵向滑动的需求

    在Android开发中,自定义ScrollView和ListView是实现复杂布局和流畅滚动效果的重要手段。这两个组件在许多场景下都有着广泛的应用,比如长列表、嵌套滚动等。本篇将深入探讨如何根据具体需求来定制这两个组件。 ...

    Android中自定义ScrollView的滑动监听事件

    然而,系统默认的ScrollView并不直接提供滑动事件的监听接口,这就需要我们自定义ScrollView来实现这一功能。下面我们将详细探讨如何在Android中自定义ScrollView并添加滑动监听事件。 首先,我们要了解ScrollView...

    android自定义scrollview

    自定义scrollview来实现滑动列表项。PS:使用时在xml中调用,只能有一个直接子组件

    Android自定义ScrollView:实现滑动顶部停靠

    在Android开发中,自定义ScrollView是一项常见的需求,特别是在构建具有复杂滚动行为的应用时。"Android自定义ScrollView:实现滑动顶部停靠"这个主题聚焦于如何创建一个在用户上滑时能够固定在顶部的组件,这通常被...

    ios-自定义scrollview collection.com

    自定义的collectionView 仿照scrollview的侧滑效果,每次只能滑动一张图片。

    自定义Scrollview--实现仿淘宝Toolbar透明度渐变效果

    本示例探讨的主题是如何自定义ScrollView以实现类似淘宝应用中的Toolbar透明度渐变效果。这种效果通常被称为“沉浸式”或“半透明”状态栏,它在用户滚动页面时会改变Toolbar的透明度,从而增强视觉体验。 首先,...

    自定义ScrollView实现弹簧效果

    首先,我们来看一下如何自定义ScrollView。在Android中,自定义控件通常涉及继承已有的View或ViewGroup类,然后重写其中的方法来实现特定的功能。对于ScrollView,我们需要继承`android.widget.ScrollView`,并重写...

    自定义ScrollView与ListView结合使用

    自定义ScrollView和ListView可以实现更多的功能和效果,例如: 1. **添加滚动监听**:通过重写滚动事件处理,可以实现定制的滚动动画或通知其他组件滚动状态。 2. **自定义Item布局**:对于ListView,可以创建多种...

    自定义ScrollView,实现QQ空间阻尼下拉刷新和渐变菜单栏效果

    Android UI设计之&lt;十三&gt;自定义ScrollView,实现QQ空间阻尼下拉刷新和渐变菜单栏效果,详http://blog.csdn.net/llew2011/article/details/52626148

    iOS自定义Scrollview滑动间距page

    本篇文章将深入探讨如何实现自定义ScrollView的滑动间距,使得每次滑动的距离小于整个屏幕的宽度,以此来创建类似“日报推送”效果。 首先,我们需要了解UIScrollView的基本工作原理。UIScrollView内部包含一个...

    自定义ScrollView,实现回弹效果

    本教程将详细介绍如何在Android中自定义ScrollView,以实现类似的效果。 首先,我们需要创建一个新的Java类,继承自Android的ScrollView。在这个自定义的ScrollView中,我们将重写onTouchEvent()方法来处理触摸事件...

    自定义ScrollView 实现图片放大,上滑修改标题栏状态

    本教程将重点讲解如何通过自定义ScrollView来实现图片的拉伸放大效果,并在用户上滑时动态修改标题栏的状态。这一功能常用于诸如新闻详情页或者电商商品详情页等场景,能够提升用户的浏览体验。 首先,我们需要创建...

    Android自定义ScrollView下拉二楼效果和上滑改变Title背景透明度

    仿招行手机银行APP8.1首页下拉效果...下拉震动出现二楼效果、上滑改变Title背景透明度效果,通过自定义ScrollView监听onTouchEvent下拉震动出现二楼效果,通过监听ScrollView滚动事件实现上滑改变Title背景透明度效果。

    Android自定义ScrollView仿团购顶部标题

    Android自定义ScrollView仿团购顶部标题,下拉过程中设置显示顶部布局,详细信息可以参考博客地址:http://blog.csdn.net/lr809174917/article/details/53728622

    Android中自定义ScrollView的滑动监听事件,并在滑动时渐变标题栏背景颜色

    然而,原生的ScrollView并未提供直接的滑动监听事件,为了实现特定的交互效果,如标题栏背景颜色随滑动渐变,我们需要自定义ScrollView并添加相应的滑动监听。本教程将详细介绍如何实现这一功能。 首先,我们要理解...

    下拉头部放大自定义ScrollView,弹性回复原型

    自定义下拉ScrollView头部变大,动画缩回原型的

Global site tag (gtag.js) - Google Analytics