由于项目的需要,自定义个view,控制父控件,既可以缩放放大,滑动边界检测。自己实现了通过父控件就能操作子控件的自定义view,直接引用就可以使用。
下面是我实现的一个思路,贴了部分代码,大家可以参考。具体的核心代码我都上传到了我的github上了,大家可以下载试试,感觉好的话,大家给个star
在MainActivity调用这个方法去初始化就可以了 :
//parentView对应的是父控件,groupView对应的是子控件
GestureViewManager bind = GestureViewManager.bind(this, groupView, parentView);
缩放放大手势
这块我主要是继承原生的ScaleGestureDetector并实现OnScaleGestureListener的事件去处理的。
- 1、在缩放的同时获取detector.getScaleFactor()的值,由于控件大小不停在改变,所以该值会出现误差导致比例出现异常,因为缩放平移肯定需要有一个边界,所以目前我通过外面包裹ViewGroup,通过给ViewGruop设置手势的方式解决这个问题。因为外面的ViewGroup大小是不会改变的,所以系统计算的比例不会出现问题。有一个ViewGruop也符合我们的需求。
- 2、如果View为可点击的View(Button)或我们给它设置了点击事件,这时View是没办法再监听缩放手势的,我们需要额外处理一下,在缩放手势执行前屏蔽点击事件,在结束后再设置允许点击。
- 3、detector.getScaleFactor()得到的缩放比例都是相对于目前view的大小进行计算的,而我们缩放view时,view的比例都是从原始大小开始的,所以我们必须记录上次缩放的比例,下次开始缩放时,从上次的比例大小开始缩放。
上面的三点是我通过查资料总结的方案,下面是我实现的代码
/**
* Created by wdh.
* Description :放大缩放手势的监听器
*/
public class ScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener/*, GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener */ {
private View targetView;
private float scale = 1;
private float scaleTemp = 1;
private boolean isFullGroup = false;
ScaleGestureListener(View targetView, ViewGroup viewGroup) {
this.targetView = targetView;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
scale = scaleTemp * detector.getScaleFactor();
targetView.setScaleX(scale);
targetView.setScaleY(scale);
return false;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
scaleTemp = scale;
}
float getScale() {
return scale;
}
public boolean isFullGroup() {
return isFullGroup;
}
void setFullGroup(boolean fullGroup) {
isFullGroup = fullGroup;
}
void onActionUp() {
if (isFullGroup && scaleTemp < 1) {
scale = 1;
targetView.setScaleX(scale);
targetView.setScaleY(scale);
scaleTemp = scale;
}
}
}
到这里就基本实现了缩放和放大的功能,很简单。
2、平移的手势以及移动边界计算
这块我也是用的原生的控件去处理的GestureDetector,并继承SimpleOnGestureListener的监听事件
- view大小是不能小于viewGroup的大小的,小于group会回弹到充满viewGroup(后面改成了可选设置)。
- view小于group大小时需要居中显示。并且缩放时,如果view不在中心,需要虽然比例慢慢回到中心。
- 放大平移时,不能移出group边界。
- 滑动和缩放的手势会起冲突,无法同时监听。所以在控件的onTouchEvent方法中,我们需要自己处理一下,我这里是通过判断按在屏幕上的手指数量,来返回不同手势回调的。一根手指按下,则为滑动手势,两根手指,则为缩放手势。
- 接下来就是计算边界,我们先不考虑缩放的情况,只考虑控件本身可移动的大小。能够移动的距离分别为控件距左、上、右、下的距离。也就是,如果控件向左移动,则判断控件距离左边的距离是否大于0,如果大于0,才允许向左移动。以此类推上、右和下。如下图
通过上面的需求,下面是我的主要代码处理
/**
* Description :滑动手势的监听器
*/
public class ScrollGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
distanceX = -distanceX;
distanceY = -distanceY;
if (isFullGroup || scale > 1) {
if (viewWidthReal > groupWidth) {
translationXOnScrollEvent(distanceX);
}
if (viewHeightReal > groupHeight) {
translationYOnScrollEvent(distanceY);
}
} else {
translationXOnScrollEvent(distanceX);
translationYOnScrollEvent(distanceY);
}
return super.onScroll(e1, e2, distanceX, distanceY);
}
private void translationXOnScrollEvent(float distanceX) {
//最大移动距离全部为正数,所以需要通过判断distanceX的正负,来判断是向左移动还是向右移动,
// 然后通过取distanceX的绝对值来和相应移动方向的最大移动距离比较
if ((distanceX < 0 && Math.abs(distanceXTemp + distanceX) < maxTranslationLeft)
|| (distanceX > 0 && distanceXTemp + distanceX < maxTranslationRight)) {
distanceXTemp += distanceX;
targetView.setTranslationX(distanceXTemp);
//如果超出边界,就移动到最大距离,防止边界有剩余量
} else if ((distanceX < 0 && Math.abs(distanceXTemp + distanceX) > maxTranslationLeft)) {
distanceXTemp = -maxTranslationLeft;
targetView.setTranslationX(-maxTranslationLeft);
} else if ((distanceX > 0 && distanceXTemp + distanceX > maxTranslationRight)) {
distanceXTemp = maxTranslationRight;
targetView.setTranslationX(maxTranslationRight);
}
}
private void translationYOnScrollEvent(float distanceY) {
if ((distanceY < 0 && Math.abs(distanceYTemp + distanceY) < maxTranslationTop)
|| (distanceY > 0 && distanceYTemp + distanceY < maxTranslationBottom)) {
distanceYTemp += distanceY;
targetView.setTranslationY(distanceYTemp);
//如果超出边界,就移动到最大距离,防止边界有剩余量
} else if ((distanceY < 0 && Math.abs(distanceYTemp + distanceY) > maxTranslationTop)) {
distanceYTemp = -maxTranslationTop;
targetView.setTranslationY(-maxTranslationTop);
} else if ((distanceY > 0 && distanceYTemp + distanceY > maxTranslationBottom)) {
distanceYTemp = maxTranslationBottom;
targetView.setTranslationY(maxTranslationBottom);
}
}