我们先来看看优酷的控件是怎么回事?

仅仅响应最后也就是最顶部的卡片的点击事件,假设点击的不是最顶部的卡片那么就先把它放到最顶部。然后在移动到最前面来。重复如次。

知道了这几条那么我们就非常好做了。

里面的技术细节可能就是child的放置到前面来的动画问题把。

先看看我们实现得效果:

然后细致分析一下我们要实现怎么样的效果:

我也是放置了一个button和两个view在控件上面。仅仅有当控件在最前面也就是最里面的时候才会响应事件。

然后我们就动手来实现这个控件。

我们继承一个ViewGroup而且命名为ExchageCarldView。最開始的当然是它的onMeasure和onLayout方法了。这里贴出代码然后一一解说。

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
  3. {
  4. measureChildren(widthMeasureSpec, heightMeasureSpec);
  5. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  6. }
  1. @Override
  2. protected void onLayout(boolean changed, int l, int t, int r, int b)
  3. {
  4. int count = getChildCount();
  5. if (mIsExchageAnimation) // 动画路径
  6. {
  7. for (int i = 0; i < count; i++)
  8. {
  9. if (mTouchIndex > i) // 当点击的头部以上的不须要改变layout
  10. continue;
  11. View view = getChildAt(i);
  12. // 缓存第一次view的信息。就是动画刚開始的信息
  13. cacheViewTopAndBottomIfneed(i, view);
  14. if (count - 1 == i) // 最上层的布局
  15. {
  16. // 计算它究竟该走多少高度总高度
  17. int total_dis = view.getHeight() / 2 * (count - 1 - mTouchIndex);
  18. // 计算当前的线性距离
  19. int now_dis = (int) (total_dis * (System.currentTimeMillis() - mAnimationStartTime) / Default_animtion_time);
  20. // 回归不能超过total_dis这个值
  21. int dis = Math.min(now_dis, total_dis);
  22.  
  23. view.layout(view.getLeft(), mViewsTopCache.get(i) + dis, view.getRight(), mViewsBottomCache.get(i) + dis);
  24. }
  25. else
  26. {
  27. // 除去最上层的那个那个布局
  28. // 每一个卡片都应该移动view.height的1/2
  29. int total_dis = view.getHeight() / 2;
  30. // 计算当前的线性距离
  31. int now_dis = (int) (total_dis * (System.currentTimeMillis() - mAnimationStartTime) / Default_animtion_time);
  32. // 回归不能超过total_dis这个值
  33. int dis = Math.min(now_dis, total_dis);
  34. // 放置布局的位置
  35. view.layout(view.getLeft(), mViewsTopCache.get(i) - dis, view.getRight(), mViewsBottomCache.get(i) - dis);
  36. }
  37.  
  38. // 检測动画是否结束
  39. checkAnimation();
  40. }
  41. }
  42. else
  43. {
  44. // 初始化的时候初始化我们的卡片
  45. mTotalHight = 0;
  46. for (int i = 0; i < count; i++)
  47. {
  48. View view = getChildAt(i);
  49. view.layout(getPaddingLeft(), mTotalHight, view.getMeasuredWidth(), mTotalHight + view.getMeasuredHeight());
  50. mTotalHight += view.getMeasuredHeight() / 2; // 这里取的是一半的布局
  51. }
  52. }
  53. }

能够看到在onMeasure方法里面我什么也没做,仅仅是调用了自带的測量方法,最基本的就是在onlayout这种方法里面了,能够看到它有两个分支。一个分支是当他动画的时候调用的分支。一个是精巧的时候调用的分支。能够看到。我这里取的是高度的一半来作为遮盖的地方。当然可能还有人问我为什么我这里要用layout来做动画呢?这里我先不解答这个问题,先跟着往以下走。里面有个缓存的函数,我们来还是先贴出来。

  1. /**
  2. * 缓存view的顶部和底部信息
  3. *
  4. * @param i
  5. * @param view
  6. */
  7. void cacheViewTopAndBottomIfneed(int i, View view)
  8. {
  9. int viewtop = mViewsTopCache.get(i, -1);
  10.  
  11. if (viewtop == -1)
  12. {
  13. mViewsTopCache.put(i, view.getTop());
  14. }
  15.  
  16. int viewbttom = mViewsBottomCache.get(i, -1);
  17. if (viewbttom == -1)
  18. {
  19. mViewsBottomCache.put(i, view.getBottom());
  20. }
  21. }

为什么我们须要缓存这个?由于在重复的调用layout的时候我们去调用gettop等方法获取的每次都会变化没有一个对齐的点,所以我们须要缓存一下開始移动的初始化位置。

位置都放置好了那么我们就能够来看看我们的Touch事件是怎么处理的了。

贴上我们的代码

  1. @Override
  2. public boolean dispatchTouchEvent(MotionEvent event)
  3. {
  4. if (mIsExchageAnimation) // 当有动画的时候我们吃掉这个事件
  5. return true;
  6. if (event.getAction() == MotionEvent.ACTION_DOWN)
  7. {
  8. mTouchIndex = getTouchChildIndex(event); // 获取点击视图的index
  9. if (mTouchIndex != -1 && mTouchIndex != getChildCount() - 1) // 点击的是最后的一个的时候不用开启动画
  10. {
  11. startAnimation();
  12. }
  13. }
  14. // return super.dispatchTouchEvent(event);
  15. // // 仅仅响应最后一个卡片的点击的事件
  16. if (mTouchIndex == getChildCount() - 1)
  17. {
  18. return super.dispatchTouchEvent(event);
  19. }
  20. else
  21. {
  22. // 其它的点击事件吃掉
  23. return true;
  24. }
  25. }

这里的代码也非常easy就是在点击的时候推断是不是在动画假设在动画就返回,然后获取到点击的child的index调用startAnimation开启动画。后面的推断就是推断仅仅对应最后一个卡片的点击事件。

以下也挨着来看看其它两个函数的代码。

  1. /***
  2. * 依据点击的事件获取child的index
  3. *
  4. * @param event
  5. * @return
  6. */
  7. int getTouchChildIndex(MotionEvent event)
  8. {
  9. for (int i = 0; i < getChildCount(); i++)
  10. {
  11. View view = getChildAt(i);
  12. Rect r = new Rect();
  13. view.getGlobalVisibleRect(r);
  14. r = new Rect(r.left, r.top, r.right, r.bottom - view.getHeight() / 2); // 须要注意的是这里我们是取的上半部分来做点推断
  15. if (r.contains((int) event.getRawX(), (int) event.getRawY()))
  16. {
  17. return i;
  18. }
  19. }
  20. return -1;
  21. }

这个函数就是依据点击的区域来区分点击到哪个孩子上面的。注意的是取的是上半部分来判定。

然后就是我们的开启动画的代码了。

  1. /**
  2. * 開始动画
  3. */
  4. private void startAnimation()
  5. {
  6. mIsExchageAnimation = true;
  7. mViewsBottomCache.clear();
  8. mViewsTopCache.clear();
  9. mAnimationStartTime = System.currentTimeMillis();
  10.  
  11. View view = getChildAt(mTouchIndex);
  12. view.bringToFront(); // 这一句代码是基本的代码
  13.  
  14. timer = new Timer();
  15. timer.schedule(new TimerTask()
  16. {
  17. @Override
  18. public void run()
  19. {
  20. mAnimationHandler.sendEmptyMessage(0);
  21. }
  22. }, 0, 24);
  23. }

这里的方法也非常简答。初始化一些变量清空缓存。然后开启一个定时任务去发送消息到handler里面事实上这个handler什么事情也没有做,仅仅是不停的在调用requstlayout让他去掉用我们的onLayout方法,最基本的一句代码就是view.bringToFront()这句代码就是会把当前的孩子放在顶层来,事实上就是放在孩子数组里面的最后一个来。这里就是为什么我们要用onlayout去做动画。我们仅仅须要不停的改变onlayout的位置不须要去管ondraw里面假设绘制。事实上底层也是这样绘制的。先绘制前面的孩子,然后在绘制后面。

总结一下这个demo:

1:卡片显示的多少我是直接取的这个控件的一半

2:通过layout来改变动画

3:最重要的就是理解bringtofront里面孩子的排列

4:缓存view的top和bottom

贴上全部代码。凝视都应该非常具体了。

  1. /**
  2. *
  3. * @author edsheng
  4. * @filename ExchageCarldView.java
  5. * @date 2015/3/12
  6. * @version v1.0
  7. */
  8. public class ExchageCarldView extends ViewGroup
  9. {
  10.  
  11. private int mTotalHight = 0; // 总高度
  12. private boolean mIsExchageAnimation = false; // 是否在做交换动画
  13. private SparseIntArray mViewsTopCache = new SparseIntArray(); // 卡片顶部边界的cache
  14. private SparseIntArray mViewsBottomCache = new SparseIntArray();// 卡片底部边界的cache
  15.  
  16. private long mAnimationStartTime = 0; // 动画開始的时间
  17. private long Default_animtion_time = 250;// 动画时间
  18. private Timer timer; // 动画定时器
  19. private int mTouchIndex = -1;// touchindex
  20.  
  21. Handler mAnimationHandler = new Handler()
  22. {
  23. public void dispatchMessage(android.os.Message msg)
  24. {
  25. requestLayout(); // 更新界面布局动画
  26. };
  27. };
  28.  
  29. public ExchageCarldView(Context context)
  30. {
  31. super(context);
  32. }
  33.  
  34. /**
  35. * 缓存view的顶部和底部信息
  36. *
  37. * @param i
  38. * @param view
  39. */
  40. void cacheViewTopAndBottomIfneed(int i, View view)
  41. {
  42. int viewtop = mViewsTopCache.get(i, -1);
  43.  
  44. if (viewtop == -1)
  45. {
  46. mViewsTopCache.put(i, view.getTop());
  47. }
  48.  
  49. int viewbttom = mViewsBottomCache.get(i, -1);
  50. if (viewbttom == -1)
  51. {
  52. mViewsBottomCache.put(i, view.getBottom());
  53. }
  54. }
  55.  
  56. @Override
  57. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
  58. {
  59. measureChildren(widthMeasureSpec, heightMeasureSpec);
  60. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  61. }
  62.  
  63. /**
  64. * 检測并停止动画
  65. */
  66. private void checkAnimation()
  67. {
  68. // 当时间到了停止动画
  69. if (Math.abs((System.currentTimeMillis() - mAnimationStartTime)) >= Default_animtion_time)
  70. {
  71. mAnimationHandler.removeMessages(0);
  72. timer.cancel();
  73. mIsExchageAnimation = false;
  74. // postDelayed(new Runnable()
  75. // {
  76. //
  77. // @Override
  78. // public void run()
  79. // {
  80. // requestLayout();
  81. // }
  82. // }, 50);
  83. }
  84. }
  85.  
  86. @Override
  87. protected void onLayout(boolean changed, int l, int t, int r, int b)
  88. {
  89. int count = getChildCount();
  90. if (mIsExchageAnimation) // 动画路径
  91. {
  92. for (int i = 0; i < count; i++)
  93. {
  94. if (mTouchIndex > i) // 当点击的头部以上的不须要改变layout
  95. continue;
  96. View view = getChildAt(i);
  97. // 缓存第一次view的信息,就是动画刚開始的信息
  98. cacheViewTopAndBottomIfneed(i, view);
  99. if (count - 1 == i) // 最上层的布局
  100. {
  101. // 计算它究竟该走多少高度总高度
  102. int total_dis = view.getHeight() / 2 * (count - 1 - mTouchIndex);
  103. // 计算当前的线性距离
  104. int now_dis = (int) (total_dis * (System.currentTimeMillis() - mAnimationStartTime) / Default_animtion_time);
  105. // 回归不能超过total_dis这个值
  106. int dis = Math.min(now_dis, total_dis);
  107.  
  108. view.layout(view.getLeft(), mViewsTopCache.get(i) + dis, view.getRight(), mViewsBottomCache.get(i) + dis);
  109. }
  110. else
  111. {
  112. // 除去最上层的那个那个布局
  113. // 每一个卡片都应该移动view.height的1/2
  114. int total_dis = view.getHeight() / 2;
  115. // 计算当前的线性距离
  116. int now_dis = (int) (total_dis * (System.currentTimeMillis() - mAnimationStartTime) / Default_animtion_time);
  117. // 回归不能超过total_dis这个值
  118. int dis = Math.min(now_dis, total_dis);
  119. // 放置布局的位置
  120. view.layout(view.getLeft(), mViewsTopCache.get(i) - dis, view.getRight(), mViewsBottomCache.get(i) - dis);
  121. }
  122.  
  123. // 检測动画是否结束
  124. checkAnimation();
  125. }
  126. }
  127. else
  128. {
  129. // 初始化的时候初始化我们的卡片
  130. mTotalHight = 0;
  131. for (int i = 0; i < count; i++)
  132. {
  133. View view = getChildAt(i);
  134. view.layout(getPaddingLeft(), mTotalHight, view.getMeasuredWidth(), mTotalHight + view.getMeasuredHeight());
  135. mTotalHight += view.getMeasuredHeight() / 2; // 这里取的是一半的布局
  136. }
  137. }
  138. }
  139.  
  140. /**
  141. * 開始动画
  142. */
  143. private void startAnimation()
  144. {
  145. mIsExchageAnimation = true;
  146. mViewsBottomCache.clear();
  147. mViewsTopCache.clear();
  148. mAnimationStartTime = System.currentTimeMillis();
  149.  
  150. View view = getChildAt(mTouchIndex);
  151. view.bringToFront(); // 这一句代码是基本的代码
  152.  
  153. timer = new Timer();
  154. timer.schedule(new TimerTask()
  155. {
  156. @Override
  157. public void run()
  158. {
  159. mAnimationHandler.sendEmptyMessage(0);
  160. }
  161. }, 0, 24);
  162. }
  163.  
  164. @Override
  165. public boolean dispatchTouchEvent(MotionEvent event)
  166. {
  167. if (mIsExchageAnimation) // 当有动画的时候我们吃掉这个事件
  168. return true;
  169. if (event.getAction() == MotionEvent.ACTION_DOWN)
  170. {
  171. mTouchIndex = getTouchChildIndex(event); // 获取点击视图的index
  172. if (mTouchIndex != -1 && mTouchIndex != getChildCount() - 1) // 点击的是最后的一个的时候不用开启动画
  173. {
  174. startAnimation();
  175. }
  176. }
  177. // return super.dispatchTouchEvent(event);
  178. // // 仅仅响应最后一个卡片的点击的事件
  179. if (mTouchIndex == getChildCount() - 1)
  180. {
  181. return super.dispatchTouchEvent(event);
  182. }
  183. else
  184. {
  185. // 其它的点击事件吃掉
  186. return true;
  187. }
  188. }
  189.  
  190. /***
  191. * 依据点击的事件获取child的index
  192. *
  193. * @param event
  194. * @return
  195. */
  196. int getTouchChildIndex(MotionEvent event)
  197. {
  198. for (int i = 0; i < getChildCount(); i++)
  199. {
  200. View view = getChildAt(i);
  201. Rect r = new Rect();
  202. view.getGlobalVisibleRect(r);
  203. r = new Rect(r.left, r.top, r.right, r.bottom - view.getHeight() / 2); // 须要注意的是这里我们是取的上半部分来做点推断
  204. if (r.contains((int) event.getRawX(), (int) event.getRawY()))
  205. {
  206. return i;
  207. }
  208. }
  209. return -1;
  210. }
  211. }

最后是測试代码。

  1. public class CardExchageDemo extends Activity
  2. {
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState)
  5. {
  6. // TODO Auto-generated method stub
  7. super.onCreate(savedInstanceState);
  8. requestWindowFeature(Window.FEATURE_NO_TITLE);
  9.  
  10. ExchageCarldView exchageView = new ExchageCarldView(this);
  11.  
  12. View view = new View(this);
  13. view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 300));
  14. view.setBackgroundColor(Color.YELLOW);
  15. exchageView.addView(view);
  16.  
  17. View view1 = new View(this);
  18. view1.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 300));
  19. view1.setBackgroundColor(Color.BLUE);
  20. exchageView.addView(view1);
  21.  
  22. Button view2 = new Button(this);
  23. view2.setOnClickListener(new OnClickListener()
  24. {
  25.  
  26. @Override
  27. public void onClick(View v)
  28. {
  29. Toast.makeText(CardExchageDemo.this, "hello", 0).show();
  30. }
  31. });
  32. view2.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 300));
  33. view2.setBackgroundColor(Color.RED);
  34. view2.setText("hello");
  35. exchageView.addView(view2);
  36.  
  37. exchageView.setBackgroundColor(Color.GREEN);
  38. setContentView(exchageView);
  39. }
  40. }

这里开启下载的传送门:点击这里

android自己定义控件系列教程-----仿新版优酷评论剧集卡片滑动控件的更多相关文章

  1. 强烈推荐android初学者,android进阶者看看这个系列教程

    强烈推荐android初学者,android进阶者看看这个系列教程 转载 2015年05月30日 23:05:44 695 为什么要研究Android,是因为它够庞大,它够复杂,他激起了我作为一个程序 ...

  2. android自己定义控件系列教程----视图

    理解android视图 对于android设备我们所示区域事实上和它在底层的绘制有着非常大的关系,非常多时候我们都仅仅关心我们所示,那么在底层一点它究竟是怎么样的一个东西呢?让我们先来看看这个图. w ...

  3. webpack4 系列教程(六): 处理SCSS

    这节课讲解webpack4中处理scss.只需要在处理css的配置上增加编译scss的 LOADER 即可.了解更多处理css的内容 >>> >>> 本节课源码 & ...

  4. sentinel 集群流控原理

    为什么需要集群流控呢?假设需要将某个API的总qps限制在100,机器数可能为50,这时很自然的想到使用一个专门的server来统计总的调用量,其他实例与该server通信来判断是否可以调用,这就是基 ...

  5. Sentinel 发布里程碑版本,添加集群流控功能

    自去年10月底发布GA版本后,Sentinel在近期发布了另一个里程碑版本v1.4(最新的版本号是v1.4.1),加入了开发者关注的集群流控功能. 集群流控简介 为什么要使用集群流控呢?假设我们希望给 ...

  6. Android自己定义控件系列五:自己定义绚丽水波纹效果

    尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551 今天我们来利用Android自己定义控件实现一个比較有趣的效果 ...

  7. Android自己定义控件系列案例【五】

    案例效果: 案例分析: 在开发银行相关client的时候或者开发在线支付相关client的时候常常要求用户绑定银行卡,当中银行卡号一般须要空格分隔显示.最常见的就是每4位数以空格进行分隔.以方便用户实 ...

  8. Android控件系列之CheckBox

    学习目的: 1.掌握在Android中如何建立CheckBox 2.掌握CheckBox的常用属性 3.掌握CheckBox选中状态变换的事件(监听器) CheckBox简介: CheckBox和Bu ...

  9. Android控件GridView之仿支付宝钱包首页带有分割线的GridView九宫格的完美实现

    Android控件GridView之仿支付宝钱包首页带有分割线的GridView九宫格的完美实现 2015-03-10 22:38 28419人阅读 评论(17) 收藏 举报  分类: Android ...

随机推荐

  1. 我的DBDA类

    <?php class DBDA { public $host="localhost"; public $uid="root"; public $pwd= ...

  2. JNDI链接SQLServer数据库步骤

    1.配置context.xml文件 在我们的WebRoot目录下,就是和WEB-INF同级的目录下,新建一个META-INF的目录(假如不存在),在该目录下创建一个context.xml文件,并且在c ...

  3. BackboneJS and UnderscoreJS

     介绍 来自API(backbone能做什么?) 当我们开发含有大量Javascript的web应用程序时,首先你需要做的事情之一便是停止向DOM对象附加数据. 通过复杂多变的jQuery选择符和回调 ...

  4. ThinkPHP---thinkphp框架介绍

    目录: (1)简述: (2)下载: (3)文件结构: (4)部署: (5)细节问题: 主体: (1)简述 ThinkPHP诞生于2006年初,最初叫FSC.于2007年元旦更名为PHP,同时官网上线. ...

  5. Java 数组中寻找最大子数组

    程序设计思想: 依次将数组划分开,先判断一个元素的单个数组大小,接下来两个,依次上升,最后将所得结果进行比较赋值,输出最大结果. 1 package ketangTest; //张生辉,康治家 201 ...

  6. python爬虫21 | 对于b站这样的滑动验证码,不好意思,照样自动识别

    今天 要来说说滑动验证码了 大家应该都很熟悉 点击滑块然后移动到图片缺口进行验证 现在越来越多的网站使用这样的验证方式 为的是增加验证码识别的难度 那么 对于这种验证码 应该怎么破呢 接下来就是 学习 ...

  7. LINUX-系统信息

    系统信息 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIOS ...

  8. gnulpot

    gnulpot Table of Contents 1. Label position 2. coordinates 3. Symbols 4. key 4.1. key position 4.2. ...

  9. vuex----------state的基础用法

    先使用vue cli构建一个自己的vue项目 1.npm i -g vue-cli 2.vue init webpack sell (sell是你的项目名) 3.一路回车(在这个过程中会提示你是否安装 ...

  10. Python基础之生成器、迭代器

    一.字符串格式化进阶 Python的字符串格式化有两种方式: 百分号方式.format方式,由于百分号的方式相对来说比较老,在社区里讨论format方式有望取代百分号方式,下面我们分别介绍一下这两种方 ...