demo地址:https://github.com/cmlbeliever/BounceLayout

最近任务比较少,闲来时间就来研究了android事件传播机制。根据总结分析的结果,打造出万能弹性layout,支持内嵌可滚动view!

先看图片(笔记本分辨率不兼容,将就看看)

核心内容分析

  1. 当手指移动时,判断移动方向,如果水平或垂直方向移动超过10个像素,则表示为移动事件,需要拦截!
  2. 判断手机按下时所在的view是否可以滚动
  3. 根据手指移动方向,与手指按下时view是否可以移动判断是否拦截事件
  4. 根据viewgroup事件拦截顺序onInterceptTouchEvent->onTouchEvent进行对应的逻辑处理

可滚动view坐标保存

1、由于内嵌可滚动view会导致事件冲突,所以在在移动时需要判断事件是否由内嵌的viewgroup消费。

2、在view布局处理好后,将可滚动的view信息保存起来

  1. @Override
  2. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  3. super.onLayout(changed, l, t, r, b);
  4. if (changed) {
  5. // 保存可滚动对象
  6. positionMap.clear();
  7. saveChildRect(this);
  8. }
  9. }
  10. /**
  11. * 找出所有可滚动的view,并存储位置
  12. *
  13. * @param view
  14. */
  15. private void saveChildRect(ViewGroup view) {
  16. if (view instanceof ScrollView || view instanceof HorizontalScrollView || view instanceof ScrollingView || view instanceof AbsListView) {
  17. int[] location = new int[2];
  18. // view.getLocationOnScreen(location);
  19. view.getLocationInWindow(location);
  20. RectF rectF = new RectF();
  21. rectF.left = location[0];
  22. rectF.top = location[1];
  23. rectF.right = rectF.left + view.getMeasuredWidth();
  24. rectF.bottom = rectF.top + view.getMeasuredHeight();
  25. Log.d(TAG, "saveChildRect===>(" + rectF);
  26. positionMap.put(rectF, view);
  27. } else {
  28. int childCount = view.getChildCount();
  29. for (int i = 0; i < childCount; i++) {
  30. if (view.getChildAt(i) instanceof ViewGroup) {
  31. this.saveChildRect((ViewGroup) view.getChildAt(i));
  32. }
  33. }
  34. }
  35. }

3、当用户按下时,根据按下坐标查找是否在可滚动viewgroup内

  1. private View findViewByPosition(float x, float y) {
  2. for (RectF rectF : positionMap.keySet()) {
  3. if (rectF.left <= x && x <= rectF.right && rectF.top <= y && y <= rectF.bottom) {
  4. return positionMap.get(rectF);
  5. }
  6. }
  7. return null;
  8. }

注意调用时需要传入相对屏幕的坐标而不是相对于viewgroup的坐标 touchView = findViewByPosition(ev.getRawX(), ev.getRawY());

4、当用户在垂直方向移动,则判断touchView是否可以在垂直方向移动,如果可以,则事件交给touchView处理,否则进行layout的移动

  1. //垂直方向移动
  2. if (direction == DIRECTION_Y && canChildScrollVertically((int) -dy)) {
  3. MotionEvent event = MotionEvent.obtain(ev);
  4. event.setAction(MotionEvent.ACTION_CANCEL);
  5. onTouchEvent(event);
  6. return super.onInterceptTouchEvent(ev);
  7. }

5、当用户在水平方向移动,则判断touchView是否可以在水平方向移动,如果可以,则事件交给touchView处理,否则进行layout的移动

  1. //水平方向移动处理
  2. if (direction == DIRECTION_X && canChildScrollHorizontally((int) -dx)) {
  3. MotionEvent event = MotionEvent.obtain(ev);
  4. event.setAction(MotionEvent.ACTION_CANCEL);
  5. onTouchEvent(event);
  6. return super.onInterceptTouchEvent(ev);
  7. }

这样弹性layout的核心内容就分析完毕了,demo地址:

https://github.com/cmlbeliever/BounceLayout

主要代码:

  1. package com.cml.newframe.scrollablebox;
  2. import android.animation.Animator;
  3. import android.animation.ObjectAnimator;
  4. import android.annotation.TargetApi;
  5. import android.content.Context;
  6. import android.graphics.PointF;
  7. import android.graphics.RectF;
  8. import android.os.Build;
  9. import android.support.v4.view.ScrollingView;
  10. import android.support.v4.view.ViewCompat;
  11. import android.util.AttributeSet;
  12. import android.util.Log;
  13. import android.view.MotionEvent;
  14. import android.view.View;
  15. import android.view.ViewGroup;
  16. import android.view.animation.AccelerateDecelerateInterpolator;
  17. import android.widget.AbsListView;
  18. import android.widget.HorizontalScrollView;
  19. import android.widget.LinearLayout;
  20. import android.widget.ScrollView;
  21. import java.util.HashMap;
  22. import java.util.Map;
  23. /**
  24. * Created by cmlBeliever on 2016/3/25.
  25. * <p>弹性layout</p>
  26. */
  27. public class BounceLayout extends LinearLayout {
  28. private static final String TAG = BounceLayout.class.getSimpleName();
  29. private static final int DIRECTION_X = 1;
  30. private static final int DIRECTION_Y = 2;
  31. private static final int DIRECTION_NONE = 3;
  32. /**
  33. * 可移动比率 默认为0.5
  34. */
  35. private float transRatio = 0.5f;
  36. /**
  37. * 手指按下的坐标
  38. */
  39. private PointF initPoint = new PointF();
  40. private Animator translateAnim;
  41. private int direction = DIRECTION_NONE;
  42. //手指按下时所在的view
  43. private View touchView;
  44. private Map<RectF, ViewGroup> positionMap = new HashMap<>();
  45. public BounceLayout(Context context) {
  46. super(context);
  47. this.init();
  48. }
  49. public BounceLayout(Context context, AttributeSet attrs) {
  50. super(context, attrs);
  51. this.init();
  52. }
  53. public BounceLayout(Context context, AttributeSet attrs, int defStyleAttr) {
  54. super(context, attrs, defStyleAttr);
  55. this.init();
  56. }
  57. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  58. public BounceLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
  59. super(context, attrs, defStyleAttr, defStyleRes);
  60. this.init();
  61. }
  62. private void init() {
  63. }
  64. @Override
  65. public boolean onTouchEvent(MotionEvent ev) {
  66. switch (ev.getAction()) {
  67. case MotionEvent.ACTION_MOVE:
  68. float dx = ev.getX() - initPoint.x;
  69. float dy = ev.getY() - initPoint.y;
  70. if (direction == DIRECTION_NONE) {
  71. //x方向移动
  72. if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 10) {
  73. direction = DIRECTION_X;
  74. } else if (Math.abs(dy) > 10) {
  75. direction = DIRECTION_Y;
  76. }
  77. } else {
  78. //x方向移动
  79. if (direction == DIRECTION_X) {
  80. if (Math.abs(dx) <= getMeasuredWidth() * transRatio) {
  81. ViewCompat.setTranslationX(getChildAt(0), dx);
  82. }
  83. } else if (direction == DIRECTION_Y) {
  84. if (Math.abs(dy) <= getMeasuredHeight() * transRatio) {
  85. ViewCompat.setTranslationY(getChildAt(0), dy);
  86. }
  87. }
  88. return true;
  89. }
  90. break;
  91. case MotionEvent.ACTION_UP:
  92. case MotionEvent.ACTION_CANCEL:
  93. dx = ViewCompat.getTranslationX(getChildAt(0));
  94. dy = ViewCompat.getTranslationY(getChildAt(0));
  95. if (direction == DIRECTION_X) {
  96. startTransAnim(DIRECTION_X, dx, 0);
  97. } else if (direction == DIRECTION_Y) {
  98. startTransAnim(DIRECTION_Y, dy, 0);
  99. }
  100. if (direction != DIRECTION_NONE) {
  101. direction = DIRECTION_NONE;
  102. return true;
  103. }
  104. break;
  105. }
  106. return super.onTouchEvent(ev);
  107. }
  108. @Override
  109. public boolean onInterceptTouchEvent(MotionEvent ev) {
  110. switch (ev.getAction()) {
  111. case MotionEvent.ACTION_DOWN:
  112. initPoint.x = ev.getX();
  113. initPoint.y = ev.getY();
  114. touchView = findViewByPosition(ev.getRawX(), ev.getRawY());
  115. Log.d(TAG, "onInterceptTouchEvent===>touchView:" + touchView);
  116. break;
  117. case MotionEvent.ACTION_MOVE:
  118. float dx = ev.getX() - initPoint.x;
  119. float dy = ev.getY() - initPoint.y;
  120. Log.d(TAG, "onInterceptTouchEvent===>ACTION_MOVE touchView:" + touchView);
  121. if (direction == DIRECTION_NONE) {
  122. //x方向移动
  123. if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 10) {
  124. direction = DIRECTION_X;
  125. } else if (Math.abs(dy) > 10) {
  126. direction = DIRECTION_Y;
  127. }
  128. }
  129. //垂直方向移动
  130. if (direction == DIRECTION_Y && canChildScrollVertically((int) -dy)) {
  131. MotionEvent event = MotionEvent.obtain(ev);
  132. event.setAction(MotionEvent.ACTION_CANCEL);
  133. onTouchEvent(event);
  134. return super.onInterceptTouchEvent(ev);
  135. }
  136. if (direction == DIRECTION_X && canChildScrollHorizontally((int) -dx)) {
  137. MotionEvent event = MotionEvent.obtain(ev);
  138. event.setAction(MotionEvent.ACTION_CANCEL);
  139. onTouchEvent(event);
  140. return super.onInterceptTouchEvent(ev);
  141. }
  142. break;
  143. }
  144. return direction == DIRECTION_NONE ? super.onInterceptTouchEvent(ev) : true;
  145. }
  146. @Override
  147. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  148. super.onLayout(changed, l, t, r, b);
  149. if (changed) {
  150. // 保存可滚动对象
  151. positionMap.clear();
  152. saveChildRect(this);
  153. }
  154. }
  155. private View findViewByPosition(float x, float y) {
  156. for (RectF rectF : positionMap.keySet()) {
  157. if (rectF.left <= x && x <= rectF.right && rectF.top <= y && y <= rectF.bottom) {
  158. return positionMap.get(rectF);
  159. }
  160. }
  161. return null;
  162. }
  163. /**
  164. * 找出所有可滚动的view,并存储位置
  165. *
  166. * @param view
  167. */
  168. private void saveChildRect(ViewGroup view) {
  169. if (view instanceof ScrollView || view instanceof HorizontalScrollView || view instanceof ScrollingView || view instanceof AbsListView) {
  170. int[] location = new int[2];
  171. // view.getLocationOnScreen(location);
  172. view.getLocationInWindow(location);
  173. RectF rectF = new RectF();
  174. rectF.left = location[0];
  175. rectF.top = location[1];
  176. rectF.right = rectF.left + view.getMeasuredWidth();
  177. rectF.bottom = rectF.top + view.getMeasuredHeight();
  178. Log.d(TAG, "saveChildRect===>(" + rectF);
  179. positionMap.put(rectF, view);
  180. } else {
  181. int childCount = view.getChildCount();
  182. for (int i = 0; i < childCount; i++) {
  183. if (view.getChildAt(i) instanceof ViewGroup) {
  184. this.saveChildRect((ViewGroup) view.getChildAt(i));
  185. }
  186. }
  187. }
  188. }
  189. private void startTransAnim(int direction, float start, float end) {
  190. if (translateAnim != null && translateAnim.isRunning()) {
  191. translateAnim.end();
  192. }
  193. String proName = direction == DIRECTION_X ? "translationX" : "translationY";
  194. translateAnim = ObjectAnimator.ofFloat(getChildAt(0), proName, start, end);
  195. translateAnim.setInterpolator(new AccelerateDecelerateInterpolator());
  196. translateAnim.start();
  197. }
  198. /**
  199. * @param direction 负数:向上滑动 else 向下滑动
  200. * @return
  201. */
  202. private boolean canChildScrollVertically(int direction) {
  203. return touchView == null ? false : ViewCompat.canScrollVertically(touchView, direction);
  204. }
  205. /**
  206. * @param direction 负数:向左滑动 else 向右滑动
  207. * @return
  208. */
  209. private boolean canChildScrollHorizontally(int direction) {
  210. return touchView == null ? false : ViewCompat.canScrollHorizontally(touchView, direction);
  211. }
  212. public float getTransRatio() {
  213. return transRatio;
  214. }
  215. /**
  216. * 设置偏移部分百分比
  217. *
  218. * @param transRatio 0-1 ,1 100%距离
  219. */
  220. public void setTransRatio(float transRatio) {
  221. this.transRatio = transRatio;
  222. }
  223. }

弹弹弹 打造万能弹性layout的更多相关文章

  1. 简单实现弹出弹框页面背景半透明灰,弹框内容可滚动原页面内容不可滚动的效果(JQuery)

    弹出弹框 效果展示 实现原理 html结构比较简单,即: <div>遮罩层 <div>弹框</div> </div> 先写覆盖显示窗口的遮罩层div.b ...

  2. android打造万能的适配器(转)

    荒废了两天,今天与大家分享一个ListView的适配器 前段时间在学习慕课网的视频,觉得这种实现方式较好,便记录了下来,最近的项目中也使用了多次,节省了大量的代码,特此拿来与大家分享一下. 还是先看图 ...

  3. Android 快速开发系列 打造万能的ListView GridView 适配器

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38902805 ,本文出自[张鸿洋的博客] 1.概述 相信做Android开发的写 ...

  4. 安卓开发笔记——打造万能适配器(Adapter)

    为什么要打造万能适配器? 在安卓开发中,用到ListView和GridView的地方实在是太多了,系统默认给我们提供的适配器(ArrayAdapter,SimpleAdapter)经常不能满足我们的需 ...

  5. Path特效之PathMeasure打造万能路径动效

    前面两篇文章主要讲解了 Path 的概念和基本使用,今天我们一起利用 Path 做个比较实用的小例子: 上一篇我们使用 Path 绘制了一个小桃心,我们这一篇继续围绕着这个小桃心进行展开: ----- ...

  6. salesforce零基础学习(九十四)classic下pagelayout引入的vf page弹出内容更新此page layout

    我们在classic环境中,有时针对page layout不能实现的地方,可以引入 一个vf page去增强标准的 page layout 功能,有时可能要求这个 vf page的部分修改需要更新此 ...

  7. 一款jQuery实现重力弹动模拟效果特效,弹弹弹,弹走IE6

    一款jQuery实现重力弹动模拟效果特效 鼠标经过两块黑色div中间的红色线时,下方的黑快会突然掉落, 并在掉落地上那一刻出现了弹跳的jquery特效效果,非常不错,还兼容所有的浏览器, 适用浏览器: ...

  8. Android中EditTex焦点设置和弹不弹出输入法的问题(转)

    今天编程碰到了一个问题:有一款平板,打开一个有EditText的Activity会默认弹出输入法.为了解决这个问题就深入研究了下android中焦点Focus和弹出输入法的问题.在网上看了些例子都不够 ...

  9. jquery layer插件弹出弹层 结构紧凑,功能强大

    /* 去官方网站下载最新的js http://sentsin.com/jquery/layer/ ①引用jquery ②引用layer.min.js */ 事件触发炸弹层可以自由绑定,例如: $('# ...

随机推荐

  1. 理解java容器底层原理--手动实现ArrayList

    为了照顾初学者,我分几分版本发出来 版本一:基础版本 实现对象创建.元素添加.重新toString() 方法 package com.xzlf.collection; /** * 自定义一个Array ...

  2. php json接口demo

    <?php class Student { public $no; public $username; public $password; } $student=new Student(); $ ...

  3. Zabbix备份数据文件

    mysql自带的工具mysqldump,当数据量大了之后进行全备所花的时间比较长,这样将会造成数据库的锁读.从而zabbix服务的监控告警不断,想着做下配置文件的备份.刚好有这么个脚本.满足了需求. ...

  4. nginx 配置多个 https 域名访问

    需要此操作的原因 在服务器上部署了 halo blog 以后,这次需要部署另外一个项目,但是又不想使用 ip + port,因此选择使用 nginx 配置多个域名访问. nginx 配置 server ...

  5. python读取txt批量创建文件

    python读取txt批量创建文件 pythonbatchfile 前几天有个小问题, 需要批量建立很多文件夹,, 所以手动写了个小的脚本, 后续可以直接使用 读取目录文件, 然后直接创建相应的文件 ...

  6. 非阻塞同步机制和CAS

    目录 什么是非阻塞同步 悲观锁和乐观锁 CAS 非阻塞同步机制和CAS 我们知道在java 5之前同步是通过Synchronized关键字来实现的,在java 5之后,java.util.concur ...

  7. Linux系统管理第四次作业 磁盘管理 文件系统

    1.为主机新增两块30GB的SCSI硬盘 2.划分3个主分区,各5GB,剩余空间作为扩展分区 [root@localhost ~]# fdisk /dev/sdb 欢迎使用 fdisk (util-l ...

  8. Django入门4: ORM 数据库操作

    大纲 一.DjangoORM 创建基本类型及生成数据库表结构 1.简介 2.创建数据库 表结构 二.Django ORM基本增删改查 1.表数据增删改查 2.表结构修改 三.Django ORM 字段 ...

  9. Alpine Linux 3.9.2 发布,轻量级 Linux 发行版

    开发四年只会写业务代码,分布式高并发都不会还做程序员?   Alpine Linux 3.9.2 已发布,Alpine Linux 是一款面向安全的轻量级 Linux 发行版,体积十分的小. Alpi ...

  10. 【20180129】java进程经常OOM,扩容swap。

    导读:线上一台服务器专门做为公司内部apk打包服务,由于app的业务和功能与时俱增,apk打包需要依赖的资源越来越多,最近这几天每次apk打包的时候都会由于OOM导致打包失败.由于apk打包业务并不是 ...