弹弹弹 打造万能弹性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 */ 事件触发炸弹层可以自由绑定,例如: $('# ...
随机推荐
- [PHP]听说随机数mt_rand()比rand()速度快,闲的无聊测试了一下!
废话不说上码 //microtime() 函数返回当前 Unix 时间戳的微秒数.//当设置为 TRUE 时,规定函数应该返回一个浮点数,否则返回一个字符串.默认为 FALSE. <?php h ...
- python-用户输入和while循环
函数input() 比较大小要同类型: age=iput() 21 age=int(age) age>=10 true prompt = "If you tell us who you ...
- SpringBoot【新手学习记录篇】
1. 启动方式: 在idea中的application.java右键run as 命令行进入项目目录,使用命令 mvn spring-boot:run 使用mvn install进行打包,然后进入ta ...
- 一款被大厂选用的 Hexo 博客主题
首先这是一篇自吹自擂的文章,主题是由多位非前端程序员共同开发,目前经过一年半的迭代已经到达 v1.8.0 版本,并且获得大量认可,甚至某大厂员工已经选用作为内部博客,因此我决定写这篇文章向更多人安利它 ...
- HTTP 协议图解
HTTP 协议是一个非常重要的网络协议,我们平时能够使用浏览器浏览网页,其中一个非常重要的条件就是HTTP 协议. 0,什么是网络协议 互联网的目的是分享信息,网络协议是互联网的重要组成部分. 在互联 ...
- VS2013 配置全局 VC++目录
原文链接:https://blog.csdn.net/humanking7/article/details/80391914 也许是我VS2013安装的有问题,每次编译程序都要去 项目属性页-> ...
- Golang-filepath使用
Golang-filepath 使用 获取当前目录 os.GetPWD() filepath.Abs(path) # 绝对目录 filepath.Dir(path) # 相对目录 可以 filepat ...
- ajax无刷新上传和下载
关于ajax无刷新上传和下载 这是一个没什么含量但是又用的比较多又不得不说的问题,其实真的不想说,因为没什么好说的. 关于上传 1.使用Flash,ActiveX 上传 ,略... 2.自己写XMLH ...
- 3DMAX导出到Unity坐标轴转换问题
这是我在3dmax中创建的1cm*1cm*1cm的立方体,右图为3dmax中的坐标系 当我们把这个立方体导入到unity中发现x轴意外的扭转了90度 为了解决这个问题,你需要对模型做出修正 1.选 ...
- Clickhosue 强大的函数,argMin() 和argMax()函数
说实话,我喜欢Clickhouse 的函数,简单操作,功能强大.今天需要给大家介绍两个函数,argMin(),argMax() 1.argMax():计算 ‘arg’ 最大值 ‘val’ 价值. 如果 ...