您的位置:首页 > 其它

弹弹弹 打造万能弹性layout

2016-03-28 12:34 447 查看
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;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: