3.自定义控件(三)

继承已有ViewGroup实现自定义控件

模拟ViewPager的效果:

实现步骤:

1、自定义view继承viewGroup。

2、重写onLayout方法,为每一个子View确定位置。

3、重写onTouchEvent方法,监听touch事件,并用scrollTo()或scrollBy()方法移动view,

4、监听UP事件,当手指抬起时,判断应显示的页面位置,并计算距离、滑动页面。

5、添加页面切换的监听事件。

布局

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:gravity="center_horizontal"
  6. android:orientation="vertical"
  7. tools:context=".MainActivity" >
  8. <RadioGroup
  9. android:id="@+id/radioGroup"
  10. android:layout_width="match_parent"
  11. android:layout_height="wrap_content"
  12. android:orientation="horizontal" >
  13. </RadioGroup>
  14. <com.itheima.myscrollview28.MyScrollView
  15. android:id="@+id/myscroll_view"
  16. android:layout_width="match_parent"
  17. android:layout_height="match_parent" />
  18. </LinearLayout>

还有一个页面随便添加一些控件,listview,放在中间,来练习onTouchEvent事件传递

MyScroller:计算位移距离的工具类,这个是匀速运动

new Scroller(ctx);//这个是系统的,自定义起的方法名都和它起的一样,能快速切换

  1. public class MyScroller {
  2. private int startX;
  3. private int startY;
  4. private int distanceX;
  5. private int distanceY;
  6. /**
  7. * 开始执行动画的时间
  8. */
  9. private long startTime;
  10. /**
  11. * 判断是否正在执行动画
  12. * true 是还在运行
  13. * false 已经停止
  14. */
  15. private boolean isFinish;
  16. public MyScroller(Context ctx){
  17. }
  18. /**
  19. * 开移移动:将这些信息记录下来
  20. * @param startX 开始时的X坐标
  21. * @param startY 开始时的Y坐标
  22. * @param disX X方向 要移动的距离
  23. * @param disY Y方向 要移动的距离
  24. */
  25. public void startScroll(int startX, int startY, int disX, int disY) {
  26. this.startX = startX;
  27. this.startY = startY;
  28. this.distanceX = disX;
  29. this.distanceY = disY;
  30. this.startTime = SystemClock.uptimeMillis();
  31. this.isFinish = false;
  32. }
  33. /**
  34. * 默认运行的时间
  35. * 毫秒值
  36. */
  37. private int duration = 500;
  38. /**
  39. * 当前的X值
  40. */
  41. private long currX;
  42. /**
  43. * 当前的Y值
  44. */
  45. private long currY;
  46. /**
  47. * 计算一下当前的运行状况
  48. * 返回值:
  49. * true 还在运行
  50. * false 运行结束
  51. */
  52. public boolean computeScrollOffset() {
  53. if (isFinish) {
  54. return false;
  55. }
  56. // 获得所用的时间
  57. long passTime = SystemClock.uptimeMillis() - startTime;
  58. // 如果时间还在允许的范围内
  59. if (passTime < duration) {
  60. // 当前的位置 = 开始的位置 + 移动的距离(距离 = 速度*时间)
  61. currX = startX + distanceX * passTime / duration;
  62. currY = startY + distanceY * passTime / duration;
  63. } else {
  64. currX = startX + distanceX;
  65. currY = startY + distanceY;
  66. isFinish = true;
  67. }
  68. return true;
  69. }
  70. public long getCurrX() {
  71. return currX;
  72. }
  73. public void setCurrX(long currX) {
  74. this.currX = currX;
  75. }
  76. }

MyScrollView:手势识别器其实就是onTouchEvent提供的简单工具类

  1. public class MyScrollView extends ViewGroup{
  2. private Context ctx;
  3. /**
  4. * 判断是否发生快速滑动
  5. */
  6. protected boolean isFling;
  7. public MyScrollView(Context context, AttributeSet attrs) {
  8. super(context, attrs);
  9. // TODO Auto-generated constructor stub
  10. this.ctx = context;
  11. initView();
  12. }
  13. private void initView() {
  14. // myScroller = new MyScroller(ctx);
  15. myScroller = new Scroller(ctx);//这个是系统的,自定义的方法名都和它起的的一样
  16. detector = new GestureDetector(ctx, new OnGestureListener() {
  17. @Override
  18. public boolean onSingleTapUp(MotionEvent e) {
  19. return false;
  20. }
  21. @Override
  22. public void onShowPress(MotionEvent e) {
  23. }
  24. @Override
  25. /**
  26. * 响应手指在屏幕上的滑动事件
  27. */
  28. public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
  29. float distanceY) {
  30. //移动屏幕
  31. // System.out.println("distanceX::"+distanceX);
  32. /**
  33. * 移动当前view内容 移动一段距离
  34. * disX X方向移的距离 为正是,图片向左移动,为负时,图片向右移动
  35. * disY Y方向移动的距离
  36. */
  37. scrollBy((int) distanceX, 0);
  38. /**
  39. * 将当前视图的基准点移动到某个点 坐标点
  40. * x 水平方向X坐标
  41. * Y 竖直方向Y坐标
  42. * scrollTo(x, y);
  43. */
  44. return false;
  45. }
  46. @Override
  47. public void onLongPress(MotionEvent e) {
  48. }
  49. @Override
  50. /**
  51. * 发生快速滑动时的回调
  52. */
  53. public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
  54. float velocityY) {
  55. isFling = true;
  56. if(velocityX>0 && currId>0){ // 快速向右滑动
  57. currId--;
  58. }else if(velocityX<0 && currId<getChildCount()-1){ // 快速向左滑动
  59. currId++;
  60. }
  61. moveToDest(currId);
  62. return false;
  63. }
  64. @Override
  65. public boolean onDown(MotionEvent e) {
  66. return false;
  67. }
  68. });
  69. }
  70. @Override
  71. /**
  72. * 计算 控件大小,
  73. * 做为viewGroup 还有一个责任,,:计算 子view的大小
  74. */
  75. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  76. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  77. int size = MeasureSpec.getSize(widthMeasureSpec);
  78. int mode = MeasureSpec.getMode(widthMeasureSpec);
  79. for (int i = 0; i < getChildCount(); i++) {
  80. View v = getChildAt(i);
  81. v.measure(widthMeasureSpec, heightMeasureSpec);
  82. // v.getMeasuredWidth() // 得到测量的大小
  83. }
  84. }
  85. @Override
  86. /**
  87. * 对子view进行布局,确定子view的位置
  88. * changed 若为true ,说明布局发生了变化
  89. * l\t\r\b\ 是指当前viewgroup 在其父view中的位置
  90. */
  91. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  92. for (int i = 0; i < getChildCount(); i++) {
  93. View view = getChildAt(i); // 取得下标为I的子view
  94. /**
  95. * 父view 会根据子view的需求,和自身的情况,来综合确定子view的位置,(确定他的大小)
  96. */
  97. //指定子view的位置 , 左,上,右,下,是指在viewGround坐标系中的位置
  98. view.layout(0+i*getWidth(), 0, getWidth()+i*getWidth(), getHeight());
  99. // view.getWidth(); 得到view的真实的大小。
  100. }
  101. }
  102. /**
  103. * 手势识别的工具类
  104. */
  105. private GestureDetector detector;
  106. /**
  107. * 当前的ID值
  108. * 显示在屏幕上的子View的下标
  109. */
  110. private int currId = 0;
  111. /**
  112. * down 事件时的x坐标
  113. */
  114. private int firstX = 0;
  115. @Override
  116. public boolean onTouchEvent(MotionEvent event) {
  117. super.onTouchEvent(event);
  118. detector.onTouchEvent(event);
  119. //添加自己的事件解析
  120. switch (event.getAction()) {
  121. case MotionEvent.ACTION_DOWN:
  122. firstX = (int) event.getX();
  123. break;
  124. case MotionEvent.ACTION_MOVE:
  125. break;
  126. case MotionEvent.ACTION_UP:
  127. if(!isFling){// 在没有发生快速滑动的时候,才执行按位置判断currid
  128. int nextId = 0;
  129. if(event.getX()-firstX>getWidth()/2){ // 手指向右滑动,超过屏幕的1/2 当前的currid - 1
  130. nextId = currId-1;
  131. }else if(firstX - event.getX()>getWidth()/2){ // 手指向左滑动,超过屏幕的1/2 当前的currid + 1
  132. nextId = currId+1;
  133. }else{
  134. nextId = currId;
  135. }
  136. moveToDest(nextId);
  137. // scrollTo(0, 0);
  138. }
  139. isFling = false;
  140. break;
  141. }
  142. return true;
  143. }
  144. /**
  145. * 计算位移的工具类
  146. */
  147. // private MyScroller myScroller;
  148. private Scroller myScroller;
  149. /**
  150. * 移动到指定的屏幕上
  151. * @param nextId 屏幕 的下标
  152. */
  153. public void moveToDest(int nextId) {
  154. /*
  155. * 对 nextId 进行判断 ,确保 是在合理的范围
  156. * 即 nextId >=0 && next <=getChildCount()-1
  157. */
  158. //确保 currId>=0
  159. currId = (nextId>=0)?nextId:0;
  160. //确保 currId<=getChildCount()-1
  161. currId = (nextId<=getChildCount()-1)?nextId:(getChildCount()-1);
  162. //瞬间移动
  163. // scrollTo(currId*getWidth(), 0);
  164. //触发listener事件
  165. if(pageChangedListener!=null){
  166. pageChangedListener.moveToDest(currId);
  167. }
  168. int distance = currId*getWidth() - getScrollX(); // 最终的位置 - 现在的位置 = 要移动的距离
  169. // myScroller.startScroll(getScrollX(),0,distance,0);
  170. //设置运行的时间
  171. myScroller.startScroll(getScrollX(),0,distance,0,Math.abs(distance));
  172. /*
  173. * 刷新当前view onDraw()方法 的执行
  174. */
  175. invalidate();
  176. }
  177. @Override
  178. /**
  179. * invalidate(); 会导致 computeScroll()这个方法的执行
  180. */
  181. public void computeScroll() {
  182. if(myScroller.computeScrollOffset()){
  183. int newX = (int) myScroller.getCurrX();
  184. System.out.println("newX::"+newX);
  185. scrollTo(newX, 0);
  186. invalidate();
  187. };
  188. }
  189. public MyPageChangedListener getPageChangedListener() {
  190. return pageChangedListener;
  191. }
  192. public void setPageChangedListener(MyPageChangedListener pageChangedListener) {
  193. this.pageChangedListener = pageChangedListener;
  194. }
  195. private MyPageChangedListener pageChangedListener;
  196. /**
  197. * 页面改时时的监听接口
  198. * @author leo
  199. *
  200. */
  201. public interface MyPageChangedListener{
  202. void moveToDest(int currid);
  203. }
  204. }

使用手势识别器GestureDetector


/**

 * 设置引导页的基类, 不需要在清单文件中注册, 因为不需要界面展示

 * 让其他Activity继承它就行

 * @author Kevin

 * 

 */

public abstract class BaseSetupActivity extends Activity {

 

	private GestureDetector mDectector;

	public SharedPreferences mPref;

 

	@Override

	protected void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);

		

		mPref = getSharedPreferences("config", MODE_PRIVATE);

 

		// 手势识别器

		mDectector = new GestureDetector(this, new SimpleOnGestureListener() {

 

			/**

			 * 监听手势滑动事件 e1表示滑动的起点,e2表示滑动终点 velocityX表示水平速度 velocityY表示垂直速度

			 */

			@Override

			public boolean onFling(MotionEvent e1, MotionEvent e2,

					float velocityX, float velocityY) {

 

				// 判断纵向滑动幅度是否过大, 过大的话不允许切换界面

				if (Math.abs(e2.getRawY() - e1.getRawY()) > 100) {

					Toast.makeText(BaseSetupActivity.this, "不能这样划哦!",

							Toast.LENGTH_SHORT).show();

					return true;

				}

 

				// 判断滑动是否过慢

				if (Math.abs(velocityX) < 100) {

					Toast.makeText(BaseSetupActivity.this, "滑动的太慢了!",

							Toast.LENGTH_SHORT).show();

					return true;

				}

 

				// 向右划,上一页

				if (e2.getRawX() - e1.getRawX() > 200) {

					showPreviousPage();

					return true;

				}

 

				// 向左划, 下一页

				if (e1.getRawX() - e2.getRawX() > 200) {

					showNextPage();

					return true;

				}

 

				return super.onFling(e1, e2, velocityX, velocityY);

			}

		});

	}

 

	/**

	 * 展示下一页, 子类必须实现,比如动画的逻辑可以写在这里

	 */

	public abstract void showNextPage();

 

	/**

	 * 展示上一页, 子类必须实现

	 */

	public abstract void showPreviousPage();

//几乎每个页面都有按钮,所以把按钮监听放到这里,代码会简化很多 

	// 点击下一页按钮

	public void next(View view) {

		showNextPage();

	}

 

	// 点击上一页按钮

	public void previous(View view) {

		showPreviousPage();

	}

 

	@Override

	public boolean onTouchEvent(MotionEvent event) {

		mDectector.onTouchEvent(event);// 委托手势识别器处理触摸事件

		return super.onTouchEvent(event);

	}

 

}

一定要及得委托手势识别器,否则上边的代码没用


onTouchEvent

更多看智慧北京-滑动事件处理

来自为知笔记(Wiz)

时间: 2024-08-06 16:03:32

3.自定义控件(三)的相关文章

老猪带你玩转自定义控件三——sai大神带我实现ios 8 时间滚轮控件

ios 8 的时间滚轮控件实现了扁平化,带来很好用户体验,android没有现成控件,小弟不才,数学与算法知识不过关,顾十分苦恼,幸好在github上找到sai大神实现代码,甚为欣喜,顾把学习这个控件点滴记录下来,分享给大家.项目原地址https://github.com/saiwu-bigkoo/Android-PickerView. ios 8 滚轮的效果: 而sai大神控件的效果: 哎,妈呀是不是效果95%相识啊. 好了,废话少说,谈谈我从这个控件中收获的心得. 首先,我们要高瞻远瞩看一下

android自定义控件(三) 增加内容 自定义属性 format详解

转自 http://www.gisall.com/html/35/160435-5369.html 1. reference:参考某一资源ID. (1)属性定义: <declare-styleable name = "名称"> <attr name = "background" format = "reference" /> </declare-styleable> (2)属性使用: <ImageView

android自定义控件(三) 自定义属性

书接上回 在xml里建立属性,然后java代码里用typedArray获得这些属性,得到属性后,利用属性做一些事.例:得到xml里的color,赋给paint. 1.在res/values/下新建attrs.xml [html] view plaincopy <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="CustomVie

Android笔记——Android自定义控件

目录: 1.自定义控件概述 01_什么是自定义控件 Android系统中,继承Android系统自带的View或者ViewGroup控件或者系统自带的控件,并在这基础上增加或者重新组合成我们想要的效果. 02_为什么用自定义控件 系统控件无法满足需求时,需要自定义控件. 系统的控件在不同手机长得不一样,我们希望在不同手机实现相同的效果: 有些手机上的控件长得不好看,希望好看一些. 系统控件的功能有限,需要在基础上增加功能. 03_怎么用自定义控件-三种方式 1.使用系统控件,重新组合,实现自定义

Android进阶学习

一.Android四大组件 1. Activity 生命周期: 2. Service 生命周期: Service的生命周期长,没有用户界面,可以用来开发监控程序. Service有两种调用方式: 通过Context.startService()调用Service,当调用者关闭时,Service不会关闭,只用通过Context.stopService()才能关闭. 通过Context.bindService()调用Service,当调用者关闭时,Service也会关闭,关闭Service需要使用C

谈谈-Android-PickerView系列之源码解析(二)

前言 WheelView想必大家或多或少都有一定了解, 它是一款3D滚轮控件,效果类似IOS 上面的UIpickerview .按照国际惯例,先放一张效果图: 以上是Android-PickerView 的demo演示图,它有时间选择和选项选择,并支持一二三级联动,支持自定义样式.         本篇文章的主要内容是讲解WheelView的实现原理以及源代码,大致分以下几个步骤: 一.实现原理 二.自定义控件 三.onMeasure 测量 四.onDraw 绘制 五.onTouchEvent监

自定义控件三部曲视图篇(三)——瀑布流容器WaterFallLayout实现

前言:只要在前行,梦想就不再遥远 系列文章: Android自定义控件三部曲文章索引:http://blog.csdn.net/harvic880925/article/details/50995268 前面两节讲解了有关ViewGroup的onMeasure.onLayout的知识,这节我们深入性地探讨一下,如何实现经常见到的瀑布流容器,本节将实现的效果图如下: 从效果图中可以看出这里要完成的几个功能: 1.图片随机添加 2.在添加图片时,总是将新图片插入到当前最短的列中 3.每个Item后,

Android自定义控件之自定义组合控件(三)

前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发成本,以及维护成本. 使用自定义组合控件的好处? 我们在项目开发中经常会遇见很多相似或者相同的布局,比如APP的标题栏,我们从三种方式实现标题栏来对比自定义组件带来的好处,毕竟好的东西还是以提高开发效率,降低开发成本为导向的. 1.)第一种方式:直接在每个xml布局中写相同的标题栏布局代码 <?xm

自定义控件之创建可以复用的组合控件(三)

前面已学习了两种自定义控件的实现,分别是自定义控件之对现有控件拓展(一)和 自定义控件之直接继承View创建全新视图(二),还没学习的同学可以学习下,学习了的同学也要去温习下,一定要自己完全的掌握了,再继续学习,贪多嚼不烂可不是好的学习方法,我们争取学习了一种技术就会一种技术,而且不光看了就算了,最好的方法就是看完我自己再练习下,再扩展下,在原来的基础上在添加一些东西,比如,增加一些功能实现等等. 今天我们打算学习下另外一种自定义控件,就是创建可重复使用的组合控件,那么问题来了: - 什么是可重

android自定义控件(三)ProgressBar

1.ProgressBar有两个进度,一个是android:progress,另一个是android:secondaryProgress.比如视频的缓存进度以及播放进度. 在这里缓存的进度就可以是android:secondaryProgress,而播放进度就是android:progress. 2.ProgressBar分为确定的和不确定的 二.style 1.Widget.ProgressBar.Horizontal <style name="Widget.ProgressBar.Ho