仿Launcher文件夹效果的距离算法
需求背景是要实现类似于桌面的图标合并的文件夹需求,但是参考谷歌桌面实现差异过大移植成本很高。github上找到了一个类似的实现库,比较简单,成本较低:https://github.com/AlphaBoom/ClassifyView。但是判断合并还是移动的方法在小屏幕下体验不佳,极易误操作。
一、原方法包括网上一些文档的做法都是通过移动速度和重合的距离判断,如果移动速度较小且距离小于图标一半则判断是移动。如果距离小于1/3则判断是合并。但是从一个图标移向另外一个图标距离一定是先到一半,然后再1/3。如果要达到合并效果,则必须是比较大的速度达到1/2距离,跳过判断移动,然后再达到1/3的距离。这样就有一定概率导致误判,在小屏幕上,这个速度一直都会比较小,阈值更不好取。体验不佳。
(小米早期桌面以及目前发现的华为部分机型桌面也是这种做法)
二、体验了一些上述之外的机型,总结出了下面这种方法。
假设A在B的左上方,将A拖动到B,如下图,其实是达到1/3的重合距离才触发合并,之前都不需要任何操作,其次是远离,直到1/2才开始触发移动。因为A在B的左上方的时候,其实第一次达到1/2距离时是不需要移动的。所以我们只要结合拖动的图标和目标的图标的相对位置 加上 重合距离判断也可以达到效果。
距离判断的部分代码如下:
public int getCurrentState(View selectedView, View targetView, int x, int y,
VelocityTracker velocityTracker, int selectedPosition,
int targetPosition) {
if (velocityTracker == null) return ClassifyView.MOVE_STATE_NONE;
int left = x;
int top = y;
int selectX= left + selectedView.getWidth()/2;
int selectY= top + selectedView.getHeight()/2;
int targetX= targetView.getLeft() + targetView.getWidth()/2;
int targetY= targetView.getTop() + targetView.getHeight()/2;
/**
* 距离算法:
* 屏幕太小 速度很难控制
* 假设一个正方形A从左到右依次靠近并离开另一个正方形B。
* 状态依次是 None>merge>move>None
*/
boolean canMerge = canMergeItem(selectedPosition, targetPosition);
int distance = getDistance(selectX, selectY, targetX, targetY);
//距离小于1/3宽度
if(canMerge && distance < targetView.getWidth()/3){
return ClassifyView.MOVE_STATE_MERGE;
}
//距离大于1/3宽度小于1/2宽度
if(distance < targetView.getWidth()/2){
if(selectedPosition <= targetPosition){
//select原位置在target左上方
if(canMerge && (targetX-selectX+targetY-selectY) > 0){
//select目前在target左上方
return ClassifyView.MOVE_STATE_NONE;
}else {
return ClassifyView.MOVE_STATE_MOVE;
}
}else {
//selectet原位置在target右下方
if(canMerge && (targetX-selectX+targetY-selectY) < 0){
//select目前在target右下方
return ClassifyView.MOVE_STATE_NONE;
}else {
return ClassifyView.MOVE_STATE_MOVE;
}
}
}
//距离大于1/2宽度小于宽度
if(distance < targetView.getWidth()){
if(selectedPosition <= targetPosition){
if((selectX-targetX+selectY-targetY) > 0){
//select原位置在target左上方,目前在target右下方
return ClassifyView.MOVE_STATE_MOVE;
}
}else {
if((targetX-selectX+targetY-selectY) > 0){
//select原位置在target右下方,目前在target左上方
return ClassifyView.MOVE_STATE_MOVE;
}
}
}
return ClassifyView.MOVE_STATE_NONE;
}
private int getDistance(int selectX,int selectY,int targetX,int targetY){
return (int) Math.hypot(selectX-targetX,selectY-targetY);
}