弹弹弹 打造万能弹性layout
demo地址:https://github.com/cmlbeliever/BounceLayout
最近任务比较少,闲来时间就来研究了android事件传播机制。根据总结分析的结果,打造出万能弹性layout,支持内嵌可滚动view!
先看图片(笔记本分辨率不兼容,将就看看)
核心内容分析
- 当手指移动时,判断移动方向,如果水平或垂直方向移动超过10个像素,则表示为移动事件,需要拦截!
- 判断手机按下时所在的view是否可以滚动
- 根据手指移动方向,与手指按下时view是否可以移动判断是否拦截事件
- 根据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的更多相关文章
- 简单实现弹出弹框页面背景半透明灰,弹框内容可滚动原页面内容不可滚动的效果(JQuery)
弹出弹框 效果展示 实现原理 html结构比较简单,即: <div>遮罩层 <div>弹框</div> </div> 先写覆盖显示窗口的遮罩层div.b ...
- android打造万能的适配器(转)
荒废了两天,今天与大家分享一个ListView的适配器 前段时间在学习慕课网的视频,觉得这种实现方式较好,便记录了下来,最近的项目中也使用了多次,节省了大量的代码,特此拿来与大家分享一下. 还是先看图 ...
- Android 快速开发系列 打造万能的ListView GridView 适配器
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38902805 ,本文出自[张鸿洋的博客] 1.概述 相信做Android开发的写 ...
- 安卓开发笔记——打造万能适配器(Adapter)
为什么要打造万能适配器? 在安卓开发中,用到ListView和GridView的地方实在是太多了,系统默认给我们提供的适配器(ArrayAdapter,SimpleAdapter)经常不能满足我们的需 ...
- Path特效之PathMeasure打造万能路径动效
前面两篇文章主要讲解了 Path 的概念和基本使用,今天我们一起利用 Path 做个比较实用的小例子: 上一篇我们使用 Path 绘制了一个小桃心,我们这一篇继续围绕着这个小桃心进行展开: ----- ...
- salesforce零基础学习(九十四)classic下pagelayout引入的vf page弹出内容更新此page layout
我们在classic环境中,有时针对page layout不能实现的地方,可以引入 一个vf page去增强标准的 page layout 功能,有时可能要求这个 vf page的部分修改需要更新此 ...
- 一款jQuery实现重力弹动模拟效果特效,弹弹弹,弹走IE6
一款jQuery实现重力弹动模拟效果特效 鼠标经过两块黑色div中间的红色线时,下方的黑快会突然掉落, 并在掉落地上那一刻出现了弹跳的jquery特效效果,非常不错,还兼容所有的浏览器, 适用浏览器: ...
- Android中EditTex焦点设置和弹不弹出输入法的问题(转)
今天编程碰到了一个问题:有一款平板,打开一个有EditText的Activity会默认弹出输入法.为了解决这个问题就深入研究了下android中焦点Focus和弹出输入法的问题.在网上看了些例子都不够 ...
- jquery layer插件弹出弹层 结构紧凑,功能强大
/* 去官方网站下载最新的js http://sentsin.com/jquery/layer/ ①引用jquery ②引用layer.min.js */ 事件触发炸弹层可以自由绑定,例如: $('# ...
随机推荐
- Servlet 和 Servlet容器
Servlet 很多同学可能跟我一样始终没有搞清楚到底什么是 Servlet,什么是 Servlet 容器.网上看了很多帖子,或许人家说的很清楚,但是自己的那个弯弯就是拐不过来. 想了很久说一下自己的 ...
- ubuntu16.04-交叉编译opencv3.4.6
0.前言 在要移植opecv和SeetaFaceEngine-master到ARM板子上运行的所有步骤之前,有几点需要注意的: 查看板子运行的Kernel版本 交叉编译工具链的gcc版本,关键就是工具 ...
- Java 自定义注解及注解读取解析--模拟框架生成SQL语句
假设们使用一张简单的表,结构如下: 定义注解: 表注解: package com.xzlf.annotation; import java.lang.annotation.ElementType; i ...
- shell脚本:备份数据库、代码上线
备份MySQL数据库场景:一台MySQL服务器,跑着5个数据库,在没有做主从的情况下,需要对这5个库进行备份 需求:1)每天备份一次,需要备份所有的库2)把备份数据存放到/data/backup/下3 ...
- python慎用os.getcwd() ,除非你知道【文件路径与当前工作路径的区别】
当你搜索 "获取当前文件路径" 时,有的文章会提到用os.getcwd(),但是这玩意要慎用! 废话不多说,直接上例子: E:\program_software\Pycharm\y ...
- php 超全局变量(整理)
来源:https://www.cnblogs.com/wsybky/p/8745286.html 一.$GLOBALS 在GLOBALS数组中,每一个变量为一个元素,键名对于变量名,值对于变量的内. ...
- vs code 打开文件时,取消文件目录的自动定位跟踪
文件-->首选项-->设置-->在搜索栏中搜索:explorer.autoReveal; 去掉勾选即可.
- 【已解决】React项目中按需引入ant-design报错TypeError: injectBabelPlugin is not a function
react项目中ant-design按需加载,使用react-app-rewired的时候报错 运行npm start或者yarn start报如下错误: TypeError: injectBabel ...
- [每日短篇] 1C - Spring Data JPA (0)
2019独角兽企业重金招聘Python工程师标准>>> 准备把 Spring Data JPA 完整看一遍,顺便把关键要点记录一下.在写这篇文章的今天,再不用 Spring Boot ...
- Next.js 7发布,构建速度提升40%
Next.js团队发布了其开源React框架的7版本.该版本的Next.js主要是改善整体的开发体验,包括启动速度提升57%.开发时的构建速度提升40%.改进错误报告和WebAssembly支持. \ ...