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信息保存起来

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b); if (changed) {
// 保存可滚动对象
positionMap.clear();
saveChildRect(this);
} } /**
* 找出所有可滚动的view,并存储位置
*
* @param view
*/
private void saveChildRect(ViewGroup view) { if (view instanceof ScrollView || view instanceof HorizontalScrollView || view instanceof ScrollingView || view instanceof AbsListView) { int[] location = new int[2];
// view.getLocationOnScreen(location);
view.getLocationInWindow(location); RectF rectF = new RectF();
rectF.left = location[0];
rectF.top = location[1];
rectF.right = rectF.left + view.getMeasuredWidth();
rectF.bottom = rectF.top + view.getMeasuredHeight(); Log.d(TAG, "saveChildRect===>(" + rectF); positionMap.put(rectF, view);
} else {
int childCount = view.getChildCount();
for (int i = 0; i < childCount; i++) {
if (view.getChildAt(i) instanceof ViewGroup) {
this.saveChildRect((ViewGroup) view.getChildAt(i));
}
}
}
}

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

private View findViewByPosition(float x, float y) {

        for (RectF rectF : positionMap.keySet()) {
if (rectF.left <= x && x <= rectF.right && rectF.top <= y && y <= rectF.bottom) {
return positionMap.get(rectF);
}
} return null;
}

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

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

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

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

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

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

https://github.com/cmlbeliever/BounceLayout

主要代码:

package com.cml.newframe.scrollablebox;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Build;
import android.support.v4.view.ScrollingView;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.ScrollView; import java.util.HashMap;
import java.util.Map; /**
* Created by cmlBeliever on 2016/3/25.
* <p>弹性layout</p>
*/
public class BounceLayout extends LinearLayout { private static final String TAG = BounceLayout.class.getSimpleName();
private static final int DIRECTION_X = 1;
private static final int DIRECTION_Y = 2;
private static final int DIRECTION_NONE = 3; /**
* 可移动比率 默认为0.5
*/
private float transRatio = 0.5f; /**
* 手指按下的坐标
*/
private PointF initPoint = new PointF(); private Animator translateAnim;
private int direction = DIRECTION_NONE; //手指按下时所在的view
private View touchView;
private Map<RectF, ViewGroup> positionMap = new HashMap<>(); public BounceLayout(Context context) {
super(context);
this.init();
} public BounceLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.init();
} public BounceLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.init();
} @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public BounceLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.init();
} private void init() {
} @Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
float dx = ev.getX() - initPoint.x;
float dy = ev.getY() - initPoint.y; if (direction == DIRECTION_NONE) {
//x方向移动
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 10) {
direction = DIRECTION_X;
} else if (Math.abs(dy) > 10) {
direction = DIRECTION_Y;
}
} else {
//x方向移动
if (direction == DIRECTION_X) {
if (Math.abs(dx) <= getMeasuredWidth() * transRatio) {
ViewCompat.setTranslationX(getChildAt(0), dx);
}
} else if (direction == DIRECTION_Y) {
if (Math.abs(dy) <= getMeasuredHeight() * transRatio) {
ViewCompat.setTranslationY(getChildAt(0), dy);
}
}
return true;
} break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: dx = ViewCompat.getTranslationX(getChildAt(0));
dy = ViewCompat.getTranslationY(getChildAt(0)); if (direction == DIRECTION_X) {
startTransAnim(DIRECTION_X, dx, 0);
} else if (direction == DIRECTION_Y) {
startTransAnim(DIRECTION_Y, dy, 0);
} if (direction != DIRECTION_NONE) {
direction = DIRECTION_NONE;
return true; } break;
} return super.onTouchEvent(ev);
} @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
initPoint.x = ev.getX();
initPoint.y = ev.getY();
touchView = findViewByPosition(ev.getRawX(), ev.getRawY());
Log.d(TAG, "onInterceptTouchEvent===>touchView:" + touchView);
break;
case MotionEvent.ACTION_MOVE:
float dx = ev.getX() - initPoint.x;
float dy = ev.getY() - initPoint.y; Log.d(TAG, "onInterceptTouchEvent===>ACTION_MOVE touchView:" + touchView); if (direction == DIRECTION_NONE) {
//x方向移动
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 10) {
direction = DIRECTION_X;
} else if (Math.abs(dy) > 10) {
direction = DIRECTION_Y;
}
} //垂直方向移动
if (direction == DIRECTION_Y && canChildScrollVertically((int) -dy)) {
MotionEvent event = MotionEvent.obtain(ev);
event.setAction(MotionEvent.ACTION_CANCEL);
onTouchEvent(event);
return super.onInterceptTouchEvent(ev);
} if (direction == DIRECTION_X && canChildScrollHorizontally((int) -dx)) {
MotionEvent event = MotionEvent.obtain(ev);
event.setAction(MotionEvent.ACTION_CANCEL);
onTouchEvent(event);
return super.onInterceptTouchEvent(ev);
} break;
} return direction == DIRECTION_NONE ? super.onInterceptTouchEvent(ev) : true;
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b); if (changed) {
// 保存可滚动对象
positionMap.clear();
saveChildRect(this);
} } private View findViewByPosition(float x, float y) { for (RectF rectF : positionMap.keySet()) {
if (rectF.left <= x && x <= rectF.right && rectF.top <= y && y <= rectF.bottom) {
return positionMap.get(rectF);
}
} return null;
} /**
* 找出所有可滚动的view,并存储位置
*
* @param view
*/
private void saveChildRect(ViewGroup view) { if (view instanceof ScrollView || view instanceof HorizontalScrollView || view instanceof ScrollingView || view instanceof AbsListView) { int[] location = new int[2];
// view.getLocationOnScreen(location);
view.getLocationInWindow(location); RectF rectF = new RectF();
rectF.left = location[0];
rectF.top = location[1];
rectF.right = rectF.left + view.getMeasuredWidth();
rectF.bottom = rectF.top + view.getMeasuredHeight(); Log.d(TAG, "saveChildRect===>(" + rectF); positionMap.put(rectF, view);
} else {
int childCount = view.getChildCount();
for (int i = 0; i < childCount; i++) {
if (view.getChildAt(i) instanceof ViewGroup) {
this.saveChildRect((ViewGroup) view.getChildAt(i));
}
}
}
} private void startTransAnim(int direction, float start, float end) {
if (translateAnim != null && translateAnim.isRunning()) {
translateAnim.end();
}
String proName = direction == DIRECTION_X ? "translationX" : "translationY";
translateAnim = ObjectAnimator.ofFloat(getChildAt(0), proName, start, end);
translateAnim.setInterpolator(new AccelerateDecelerateInterpolator());
translateAnim.start();
} /**
* @param direction 负数:向上滑动 else 向下滑动
* @return
*/
private boolean canChildScrollVertically(int direction) {
return touchView == null ? false : ViewCompat.canScrollVertically(touchView, direction);
} /**
* @param direction 负数:向左滑动 else 向右滑动
* @return
*/
private boolean canChildScrollHorizontally(int direction) {
return touchView == null ? false : ViewCompat.canScrollHorizontally(touchView, direction);
} public float getTransRatio() {
return transRatio;
} /**
* 设置偏移部分百分比
*
* @param transRatio 0-1 ,1 100%距离
*/
public void setTransRatio(float transRatio) {
this.transRatio = transRatio;
}
}

弹弹弹 打造万能弹性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. [PHP]听说随机数mt_rand()比rand()速度快,闲的无聊测试了一下!

    废话不说上码 //microtime() 函数返回当前 Unix 时间戳的微秒数.//当设置为 TRUE 时,规定函数应该返回一个浮点数,否则返回一个字符串.默认为 FALSE. <?php h ...

  2. python-用户输入和while循环

    函数input() 比较大小要同类型: age=iput() 21 age=int(age) age>=10 true prompt = "If you tell us who you ...

  3. SpringBoot【新手学习记录篇】

    1. 启动方式: 在idea中的application.java右键run as 命令行进入项目目录,使用命令 mvn spring-boot:run 使用mvn install进行打包,然后进入ta ...

  4. 一款被大厂选用的 Hexo 博客主题

    首先这是一篇自吹自擂的文章,主题是由多位非前端程序员共同开发,目前经过一年半的迭代已经到达 v1.8.0 版本,并且获得大量认可,甚至某大厂员工已经选用作为内部博客,因此我决定写这篇文章向更多人安利它 ...

  5. HTTP 协议图解

    HTTP 协议是一个非常重要的网络协议,我们平时能够使用浏览器浏览网页,其中一个非常重要的条件就是HTTP 协议. 0,什么是网络协议 互联网的目的是分享信息,网络协议是互联网的重要组成部分. 在互联 ...

  6. VS2013 配置全局 VC++目录

    原文链接:https://blog.csdn.net/humanking7/article/details/80391914 也许是我VS2013安装的有问题,每次编译程序都要去 项目属性页-> ...

  7. Golang-filepath使用

    Golang-filepath 使用 获取当前目录 os.GetPWD() filepath.Abs(path) # 绝对目录 filepath.Dir(path) # 相对目录 可以 filepat ...

  8. ajax无刷新上传和下载

    关于ajax无刷新上传和下载 这是一个没什么含量但是又用的比较多又不得不说的问题,其实真的不想说,因为没什么好说的. 关于上传 1.使用Flash,ActiveX 上传 ,略... 2.自己写XMLH ...

  9. 3DMAX导出到Unity坐标轴转换问题

      这是我在3dmax中创建的1cm*1cm*1cm的立方体,右图为3dmax中的坐标系 当我们把这个立方体导入到unity中发现x轴意外的扭转了90度 为了解决这个问题,你需要对模型做出修正 1.选 ...

  10. Clickhosue 强大的函数,argMin() 和argMax()函数

    说实话,我喜欢Clickhouse 的函数,简单操作,功能强大.今天需要给大家介绍两个函数,argMin(),argMax() 1.argMax():计算 ‘arg’ 最大值 ‘val’ 价值. 如果 ...