android-drag-square源码解析

  1. 1. android-drag-square
  2. 2. 使用
  3. 3. 源码
    1. 3.1. DraggableSquareView
    2. 3.2. DraggableItemView
  4. 4. 总结

Github: android-drag-square 分析版本:886b738

android-drag-square 是一个仿探探的『个人编辑资料页』的头像拖动排序的开源库。

android-drag-square

android-drag-square

使用

将代码加入到工程,然后在 layout 布局中加入控件

1
2
3
4
<com.stone.dragsquare.DraggableSquareView
android:id="@+id/drag_square"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

添加图片

1
2
DraggableSquareView dragSquare = (DraggableSquareView) findViewById(R.id.drag_square);
dragSquare.fillItemImage(imageStatus, imagePath, isModify);

第一个参数是指图片的位置,第二个参数是图片的地址,第三个图片是判断图片是新添加还是只是修改。

源码

DraggableSquareView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
public class DraggableSquareView extends ViewGroup {
// ACTION_DOWN按下后超过这个时间,就直接touch拦截,不会调用底层view的onClick事件
private static final int INTERCEPT_TIME_SLOP = 100;
//分别是指左上角的大图、右上角、右边中间、右下角,下边中间、左下角
private final int[] allStatus = {DraggableItemView.STATUS_LEFT_TOP, DraggableItemView.STATUS_RIGHT_TOP,
DraggableItemView.STATUS_RIGHT_MIDDLE, DraggableItemView.STATUS_RIGHT_BOTTOM,
DraggableItemView.STATUS_MIDDLE_BOTTOM, DraggableItemView.STATUS_LEFT_BOTTOM};

private int mTouchSlop = 5; // 判定为滑动的阈值,单位是像素
private int spaceInterval = 4; // 小方块之间的间隔
private final ViewDragHelper mDragHelper;
private GestureDetectorCompat moveDetector;

private List<Point> originViewPositionList = new ArrayList<>(); // 保存最初状态时每个itemView的坐标位置
private DraggableItemView draggingView; // 正在拖拽的view

public DraggableSquareView(Context context) {
this(context, null);
}

public DraggableSquareView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public DraggableSquareView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//ViewDragHelper
mDragHelper = ViewDragHelper
.create(this, 10f, new DragHelperCallback());
//手势判断
moveDetector = new GestureDetectorCompat(context,
new MoveDetector());
moveDetector.setIsLongpressEnabled(false); // 不能处理长按事件,否则违背最初设计的初衷
spaceInterval = (int) getResources().getDimension(R.dimen.drag_square_interval); // 小方块之间的间隔

// 滑动的距离阈值由系统提供
ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();

anchorHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (draggingView != null) {
// 开始移动重心的动画
draggingView.startAnchorAnimation();
}
}
};
}

@Override
protected void onFinishInflate() {
super.onFinishInflate();
//初始化View
int len = allStatus.length;
for (int i = 0; i < len; i++) {
DraggableItemView itemView = new DraggableItemView(getContext());
//告诉当前DraggableItemView是哪个位置
itemView.setStatus(allStatus[i]);
//将自己设置给DraggableItemView
itemView.setParentView(this);
//添加位置信息,但是是空的
originViewPositionList.add(new Point());
//将DraggableItemView添加为子View
addView(itemView);
}
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//测量
measureChildren(widthMeasureSpec, widthMeasureSpec);
int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
int width = resolveSizeAndState(maxWidth, widthMeasureSpec, 0);
setMeasuredDimension(width, width);
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//spaceInterval是间隔
int everyLength = (getMeasuredWidth() - 4 * spaceInterval) / 3;
int itemLeft = 0;
int itemTop = 0;
int itemRight = 0;
int itemBottom = 0;
// 每个view的边长是everyLength * 2 + spaceInterval
sideLength = everyLength * 2 + spaceInterval;
int halfSideLength = sideLength / 2; // 边长的一半
int rightCenter = r - spaceInterval - everyLength / 2;
int bottomCenter = b - spaceInterval - everyLength / 2;

float scaleRate = (float) everyLength / sideLength;
int num = getChildCount();
for (int i = 0; i < num; i++) {
DraggableItemView itemView = (DraggableItemView) getChildAt(i);
itemView.setScaleRate(scaleRate);
switch (itemView.getStatus()) {
case DraggableItemView.STATUS_LEFT_TOP:
int centerPos = spaceInterval + everyLength + spaceInterval / 2;
itemLeft = centerPos - halfSideLength;
itemRight = centerPos + halfSideLength;
itemTop = centerPos - halfSideLength;
itemBottom = centerPos + halfSideLength;
break;
case DraggableItemView.STATUS_RIGHT_TOP:
itemLeft = rightCenter - halfSideLength;
itemRight = rightCenter + halfSideLength;
int hCenter1 = spaceInterval + everyLength / 2;
itemTop = hCenter1 - halfSideLength;
itemBottom = hCenter1 + halfSideLength;
break;
case DraggableItemView.STATUS_RIGHT_MIDDLE:
itemLeft = rightCenter - halfSideLength;
itemRight = rightCenter + halfSideLength;
int hCenter2 = t + getMeasuredHeight() / 2;
itemTop = hCenter2 - halfSideLength;
itemBottom = hCenter2 + halfSideLength;
break;
case DraggableItemView.STATUS_RIGHT_BOTTOM:
itemLeft = rightCenter - halfSideLength;
itemRight = rightCenter + halfSideLength;
itemTop = bottomCenter - halfSideLength;
itemBottom = bottomCenter + halfSideLength;
break;
case DraggableItemView.STATUS_MIDDLE_BOTTOM:
int vCenter1 = l + getMeasuredWidth() / 2;
itemLeft = vCenter1 - halfSideLength;
itemRight = vCenter1 + halfSideLength;
itemTop = bottomCenter - halfSideLength;
itemBottom = bottomCenter + halfSideLength;
break;
case DraggableItemView.STATUS_LEFT_BOTTOM:
int vCenter2 = l + spaceInterval + everyLength / 2;
itemLeft = vCenter2 - halfSideLength;
itemRight = vCenter2 + halfSideLength;
itemTop = bottomCenter - halfSideLength;
itemBottom = bottomCenter + halfSideLength;
break;
}
//通过设置LayoutParams来设置高宽
ViewGroup.LayoutParams lp = itemView.getLayoutParams();
lp.width = sideLength;
lp.height = sideLength;
itemView.setLayoutParams(lp);
//更新Point,该值为左上角的坐标值
Point itemPoint = originViewPositionList.get(itemView.getStatus());
itemPoint.x = itemLeft;
itemPoint.y = itemTop;
//固定位置
itemView.layout(itemLeft, itemTop, itemRight, itemBottom);
}
}
}

DraggableSquareView 继承于 ViewGroup ,那么肯定得重新构造方法和 onLayout 方法,那么照着 View 的生命周期来看代码,发现在构造函数的时候申明了一个 ViewDragHelper 变量,这个类是拖动 View 操作的类;同时还有手势操作的类 GestureDetectorCompat ,以及一个 Handler ,而这个 Handler 的左右是判断手势意图是移动还是单击。

接着在 onFinishInflate 的时候将 DraggableItemView 都添加进去。

接着在 onMeasure 中测量大小,其中 resolveSizeAndState 方法与 getDefaultSize 方法类似,其内部实现的逻辑是一样的,但是又有区别,getDefaultSize 仅仅返回最终量算的尺寸信息,但 resolveSizeAndState 除了返回最终尺寸信息还会有可能返回量算的 state 标志位信息。通过看 resolveSizeAndState 代码发现,当 specMode 为AT_MOST ,并且父控件指定的尺寸 specSize 小于 View 自己想要的尺寸时,我们就会用掩码 MEASURED_STATE_TOO_SMALL 向量算结果加入尺寸太小的标记,这样其父 ViewGroup 就可以通过该标记其给子 View 的尺寸太小了,然后可能分配更大一点的尺寸给子 View 调用 resolveSizeAndState 方法的情况主要有两种:Android 中的许多 layout 类都调用了 resolveSizeAndState 方法,比如 LinearLayout 在量算过程中会调用 resolveSizeAndState 方法而非 getDefaultSize 方法。 我们自己在实现自定义的 View 或 ViewGroup 时,我们可以重写 onMeasure 方法,并在该方法内调用 resolveSizeAndState 方法。

紧接着在 onLayout 中设置 View 的位置,大小以及更新位置坐标。

那么看看这个 DraggableSquareView 的手势操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
public class DraggableSquareView extends ViewGroup {

private DraggableItemView draggingView; // 正在拖拽的view
private int sideLength; // 每一个小方块的边长
private long downTime = 0; // 按下的时间
private int downX, downY; // 按下时的坐标位置
private Thread moveAnchorThread; // 按下的时候,itemView的重心移动,此为对应线程
private Handler anchorHandler; // itemView需要移动重心,此为对应的Handler

/**
* 这是viewdraghelper拖拽效果的主要逻辑
*/

private class DragHelperCallback extends ViewDragHelper.Callback {

@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
//如果changedView是当前正在drag的View
if (changedView == draggingView) {
DraggableItemView changedItemView = (DraggableItemView) changedView;
//换位置
switchPositionIfNeeded(changedItemView);
}
}

@Override
public boolean tryCaptureView(View child, int pointerId) {
// 按下的时候,缩放到最小的级别
draggingView = (DraggableItemView) child;
return draggingView.isDraggable();
}

@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
//释放
DraggableItemView itemView = (DraggableItemView) releasedChild;
itemView.onDragRelease();
}

@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
//横向的移动
DraggableItemView itemView = (DraggableItemView) child;
itemView.updateEndSpringX(dx);
System.out.println("clampViewPositionHorizontal dx=" + dx);
return left + dx;
}

@Override
public int clampViewPositionVertical(View child, int top, int dy) {
//纵向的移动
DraggableItemView itemView = (DraggableItemView) child;
itemView.updateEndSpringY(dy);
System.out.println("clampViewPositionVertical dy=" + dy);
return top + dy;
}
}

class MoveDetector extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx,
float dy)
{

// 拖动了,touch不往下传递
return Math.abs(dy) + Math.abs(dx) > mTouchSlop;
}
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//拦截父View事件
getParent().requestDisallowInterceptTouchEvent(true);
downX = (int) ev.getX();
downY = (int) ev.getY();
downTime = System.currentTimeMillis();
// 手指按下的时候,需要把某些view bringToFront,否则的话,tryCapture将不按预期工作
bringToFrontWhenTouchDown(downX, downY);
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
//释放
if (draggingView != null) {
draggingView.onDragRelease();
}
draggingView = null;
//阻断
if (null != moveAnchorThread) {
moveAnchorThread.interrupt();
moveAnchorThread = null;
}
}
return super.dispatchTouchEvent(ev);
}

/**
* 按下时根据触点的位置,将某个view bring到前台
*/

private void bringToFrontWhenTouchDown(final int downX, final int downY) {
int statusIndex = getStatusByDownPoint(downX, downY);
final DraggableItemView itemView = getItemViewByStatus(statusIndex);
//如果该View不是在最前面,放到最前面
if (indexOfChild(itemView) != getChildCount() - 1) {
bringChildToFront(itemView);
}
//判断该View能不能drag
if (!itemView.isDraggable()) {
return;
}
//保存位置
itemView.saveAnchorInfo(downX, downY);
//判断到底是移动还是单击的一个判断Thread
moveAnchorThread = new Thread() {
@Override
public void run() {
//判断时间来做处理
try {
sleep(INTERCEPT_TIME_SLOP);
} catch (InterruptedException e) {
e.printStackTrace();
}

Message msg = anchorHandler.obtainMessage();
msg.sendToTarget();
}
};
moveAnchorThread.start();
}

/**
* 通过downX和downY来判断当前的status
*
* @param downX
* @param downY
* @return
*/

private int getStatusByDownPoint(int downX, int downY) {
int everyWidth = getMeasuredWidth() / 3;
if (downX < everyWidth) {
if (downY < everyWidth * 2) {
return DraggableItemView.STATUS_LEFT_TOP;
} else {
return DraggableItemView.STATUS_LEFT_BOTTOM;
}
} else if (downX < everyWidth * 2) {
if (downY < everyWidth * 2) {
return DraggableItemView.STATUS_LEFT_TOP;
} else {
return DraggableItemView.STATUS_MIDDLE_BOTTOM;
}
} else {
if (downY < everyWidth) {
return DraggableItemView.STATUS_RIGHT_TOP;
} else if (downY < everyWidth * 2) {
return DraggableItemView.STATUS_RIGHT_MIDDLE;
} else {
return DraggableItemView.STATUS_RIGHT_BOTTOM;
}
}
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//拦截
if (downTime > 0 && System.currentTimeMillis() - downTime > INTERCEPT_TIME_SLOP) {
return true;
}
//ViewDragHelper
boolean shouldIntercept = mDragHelper.shouldInterceptTouchEvent(ev);
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
mDragHelper.processTouchEvent(ev);
}
//是否要有移动意图
boolean moveFlag = moveDetector.onTouchEvent(ev);
if (moveFlag) {
if (null != moveAnchorThread) {
moveAnchorThread.interrupt();
moveAnchorThread = null;
}

if (null != draggingView) {
draggingView.startAnchorAnimation();
}
}
return shouldIntercept && moveFlag;
}

@Override
public boolean onTouchEvent(MotionEvent e) {
try {
// 该行代码可能会抛异常,正式发布时请将这行代码加上try catch
mDragHelper.processTouchEvent(e);
} catch (Exception ex) {
ex.printStackTrace();
}
return true;
}
}

这里有几点值得学习和探讨:

  1. moveAnchorThread 的作用,是在子线程中进行时间的延时,而我在做的时候直接用的 Handler 的 sendEmptyMessageDelay() 方法,如果想取消的话就 handler.removeMessage()
  2. ViewGroup#bringChildToFront(View) 该方法将 View 放到最前面那层,赞!之前我在做的时候是 重新 new 了个 ImageView 放在 ViewGroup 的最前的
  3. 通过手势与 View 的在位置的坐标(相对长宽)对比来判断在哪个位置了,而我在做的时候用的是 ListView#pointToPosition(int, int) 那套代码
  4. 正是因为 bringChildToFront 这部分,我在写 drag 的时候通过 View#layout(int, int, int, int) 来实现的移动

调换位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
public class DraggableSquareView extends ViewGroup {

private class DragHelperCallback extends ViewDragHelper.Callback {

@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
//如果changedView是当前正在drag的View
if (changedView == draggingView) {
DraggableItemView changedItemView = (DraggableItemView) changedView;
//换位置
switchPositionIfNeeded(changedItemView);
}
}
}

/**
* view拖动的时候,看看是否需要互换位置
*/

private void switchPositionIfNeeded(DraggableItemView draggingView) {
int centerX = draggingView.getLeft() + sideLength / 2;
int centerY = draggingView.getTop() + sideLength / 2;
int everyWidth = getMeasuredWidth() / 3;

int fromStatus = -1, toStatus = draggingView.getStatus();

switch (draggingView.getStatus()) {
case DraggableItemView.STATUS_LEFT_TOP:// 拖动的是左上角的大图, 依次将小图向上顶
int fromChangeIndex = 0;
if (centerX > everyWidth * 2) { // 大图往右越过了位置,一定会跟右侧的三个View交换位置才行
if (centerY < everyWidth) {// 跟右上角的View交换位置
fromChangeIndex = DraggableItemView.STATUS_RIGHT_TOP;
} else if (centerY < everyWidth * 2) {// 跟右边中间的View交换位置
fromChangeIndex = DraggableItemView.STATUS_RIGHT_MIDDLE;
} else {// 跟右边下面的View交换位置
fromChangeIndex = DraggableItemView.STATUS_RIGHT_BOTTOM;
}
} else if (centerY > everyWidth * 2) {//大图往下越过了位置,跟下面的3个View交换位置
if (centerX < everyWidth) {//左下角的View
fromChangeIndex = DraggableItemView.STATUS_LEFT_BOTTOM;
} else if (centerX < everyWidth * 2) {//下边中间的View
fromChangeIndex = DraggableItemView.STATUS_MIDDLE_BOTTOM;
} else {//右边的位置
fromChangeIndex = DraggableItemView.STATUS_RIGHT_BOTTOM;
}
}

DraggableItemView toItemView = getItemViewByStatus(fromChangeIndex);
//如果该位置不可drag,也就是没有图
if (!toItemView.isDraggable()) {
return;
}

synchronized (this) {
for (int i = 1; i <= fromChangeIndex; i++) {
switchPosition(i, i - 1);
}
draggingView.setStatus(fromChangeIndex);
}
return;
case DraggableItemView.STATUS_RIGHT_TOP://拖动的是右上角的View
if (centerX < everyWidth * 2) {//与大图换
fromStatus = DraggableItemView.STATUS_LEFT_TOP;
} else if (centerY > everyWidth) {//与下边的那个图换
fromStatus = DraggableItemView.STATUS_RIGHT_MIDDLE;
}
break;

case DraggableItemView.STATUS_RIGHT_MIDDLE://拖动的是右边中间的图
if (centerX < everyWidth * 2 && centerY < everyWidth * 2) {//与大图换
fromStatus = DraggableItemView.STATUS_LEFT_TOP;
} else if (centerY < everyWidth) {//与上边的图换,也就是右上角那个
fromStatus = DraggableItemView.STATUS_RIGHT_TOP;
} else if (centerY > everyWidth * 2) {//与下边的图换,也就是右下角那个
fromStatus = DraggableItemView.STATUS_RIGHT_BOTTOM;
}
break;
case DraggableItemView.STATUS_RIGHT_BOTTOM://拖动的是右下角的图
if (centerX < everyWidth * 2) {//与左边的图换,就是下边中间那个
fromStatus = DraggableItemView.STATUS_MIDDLE_BOTTOM;
} else if (centerY < everyWidth * 2) {//与上边的图换,就是右边中间那个
fromStatus = DraggableItemView.STATUS_RIGHT_MIDDLE;
}
break;
case DraggableItemView.STATUS_MIDDLE_BOTTOM://拖动的是下边中间那个
if (centerX < everyWidth) {
fromStatus = DraggableItemView.STATUS_LEFT_BOTTOM;//与左边那个换,也就是左下角那个
} else if (centerX > everyWidth * 2) {
fromStatus = DraggableItemView.STATUS_RIGHT_BOTTOM;//与右边那个换,也就是右下角那个
} else if (centerY < everyWidth * 2) {
fromStatus = DraggableItemView.STATUS_LEFT_TOP;//与大图换
}
break;
case DraggableItemView.STATUS_LEFT_BOTTOM://拖动的是左下角那个
if (centerX > everyWidth) {
fromStatus = DraggableItemView.STATUS_MIDDLE_BOTTOM;//与右边的换,也就是下边中间那个
} else if (centerY < everyWidth * 2) {
fromStatus = DraggableItemView.STATUS_LEFT_TOP;//与大图换
}
break;
default:
break;
}
//换位置
synchronized (synObj) {
if (fromStatus > 0) {
if (switchPosition(fromStatus, toStatus)) {
draggingView.setStatus(fromStatus);
}
} else if (fromStatus == 0) {
for (int i = toStatus - 1; i >= 0; i--) {
switchPosition(i, i + 1);
}
draggingView.setStatus(fromStatus);
}
}
}

/**
* 调换位置
*/

private boolean switchPosition(int fromStatus, int toStatus) {
DraggableItemView itemView = getItemViewByStatus(fromStatus);
if (itemView.isDraggable()) {
itemView.switchPosition(toStatus);
return true;
}
return false;
}

/**
* 通过status来找到DraggableItemView
*
* @param status
* @return
*/

private DraggableItemView getItemViewByStatus(int status) {
int num = getChildCount();
for (int i = 0; i < num; i++) {
DraggableItemView itemView = (DraggableItemView) getChildAt(i);
if (itemView.getStatus() == status) {
return itemView;
}
}
return null;
}
}

调换位置这部分看代码其实很简单,每个 View 都有他自己应该换的位置。

那么再看看暴露出去那几个 API :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class DraggableSquareView extends ViewGroup {

/**
* 给imageView添加图片
*/

public void fillItemImage(int imageStatus, String imagePath, boolean isModify) {
// 1. 如果是修改图片,直接填充就好
if (isModify) {
DraggableItemView itemView = getItemViewByStatus(imageStatus);
itemView.fillImageView(imagePath);
return;
}

// 2. 新增图片
for (int i = 0; i < allStatus.length; i++) {
DraggableItemView itemView = getItemViewByStatus(i);
if (!itemView.isDraggable()) {
itemView.fillImageView(imagePath);
break;
}
}
}

// 删除某一个ImageView
public void onDedeleteImage(DraggableItemView deleteView) {
int status = deleteView.getStatus();
int lastDraggableViewStatus = -1;
//一个循环来处理
for (int i = status + 1; i < allStatus.length; i++) {
DraggableItemView itemView = getItemViewByStatus(i);
if (itemView.isDraggable()) {
lastDraggableViewStatus = i;
switchPosition(i, i - 1);
} else {
break;
}
}
if (lastDraggableViewStatus > 0) {
deleteView.switchPosition(lastDraggableViewStatus);
}
}

/**
* 通过status来找到DraggableItemView
*
* @param status
* @return
*/

private DraggableItemView getItemViewByStatus(int status) {
int num = getChildCount();
for (int i = 0; i < num; i++) {
DraggableItemView itemView = (DraggableItemView) getChildAt(i);
if (itemView.getStatus() == status) {
return itemView;
}
}
return null;
}
}

DraggableItemView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
public class DraggableItemView extends FrameLayout {
public static final int STATUS_LEFT_TOP = 0;
public static final int STATUS_RIGHT_TOP = 1;
public static final int STATUS_RIGHT_MIDDLE = 2;
public static final int STATUS_RIGHT_BOTTOM = 3;
public static final int STATUS_MIDDLE_BOTTOM = 4;
public static final int STATUS_LEFT_BOTTOM = 5;

public static final int SCALE_LEVEL_1 = 1; // 最大状态,缩放比例是100%
public static final int SCALE_LEVEL_2 = 2; // 中间状态,缩放比例scaleRate
public static final int SCALE_LEVEL_3 = 3; // 最小状态,缩放比例是smallerRate

private ImageView imageView;
private View maskView;
private int status;
private float scaleRate = 0.5f;
private Spring springX, springY;
private boolean hasSetCurrentSpringValue = false;
private DraggableSquareView parentView;
//这两个参数可以参考http://facebook.github.io/rebound/
private SpringConfig springConfigCommon = SpringConfig.fromOrigamiTensionAndFriction(140, 7);
private SpringConfig springConfigDragging = SpringConfig.fromOrigamiTensionAndFriction(400, 7);

public DraggableItemView(Context context) {
this(context, null);
}

public DraggableItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public DraggableItemView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//添加布局
inflate(context, R.layout.drag_item, this);
//找到drag的时候的ImageView
imageView = (ImageView) findViewById(R.id.drag_item_imageview);
//找到显示的ImageView
maskView = findViewById(R.id.drag_item_mask_view);
//找到那个『+』的View
addView = findViewById(R.id.add_view);
//单击的时候的对话框
dialogListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v.getId() == R.id.pick_image) {
// 从相册选择图片
pickImage();
} else {
// 删除
imagePath = null;
imageView.setImageBitmap(null);
addView.setVisibility(View.VISIBLE);
parentView.onDedeleteImage(DraggableItemView.this);
}
}
};
//调整View的大小
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (!hasSetCurrentSpringValue) {
adjustImageView();
hasSetCurrentSpringValue = true;
}
}
});
//显示的View的点击事件
maskView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isDraggable()) {
pickImage();
} else {
CustDialog dialog = new CustDialog(getContext());
dialog.setClickListener(dialogListener);
dialog.show();
}
}
});
//初始化Facebook的库
initSpring();
}

/**
* 调整ImageView的宽度和高度
*/

private void adjustImageView() {
if (status != STATUS_LEFT_TOP) {
imageView.setScaleX(scaleRate);
imageView.setScaleY(scaleRate);

maskView.setScaleX(scaleRate);
maskView.setScaleY(scaleRate);
}

setCurrentSpringPos(getLeft(), getTop());
}

/**
* 设置当前spring位置
*/

private void setCurrentSpringPos(int xPos, int yPos) {
springX.setCurrentValue(xPos);
springY.setCurrentValue(yPos);
}

/**
* 初始化Spring相关
*/

private void initSpring() {
SpringSystem mSpringSystem = SpringSystem.create();
springX = mSpringSystem.createSpring();
springY = mSpringSystem.createSpring();

springX.addListener(new SimpleSpringListener() {
@Override
public void onSpringUpdate(Spring spring) {
int xPos = (int) spring.getCurrentValue();
setScreenX(xPos);
}
});

springY.addListener(new SimpleSpringListener() {
@Override
public void onSpringUpdate(Spring spring) {
int yPos = (int) spring.getCurrentValue();
setScreenY(yPos);
}
});

springX.setSpringConfig(springConfigCommon);
springY.setSpringConfig(springConfigCommon);
}

/**
* 横向的移动
*
* @param screenX
*/

public void setScreenX(int screenX) {
this.offsetLeftAndRight(screenX - getLeft());
}

/**
* 纵向的移动
*
* @param screenY
*/

public void setScreenY(int screenY) {
this.offsetTopAndBottom(screenY - getTop());
}
}

Facebook 的库可以看看这里 Rebound ,一个很棒的库,把一开始 drag 和释放 drag 的动画都省略了,之前我做的时候都是用的 AnimatorSet 等属性动画来实现的。

看看暴露出去个 API :

当满足时间超过 100 ms,就进行往手指方向移动且缩小的动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class DraggableItemView extends FrameLayout {

/**
* 保存坐标
* MotionEvent.ACTION_DOWN 的时候调用
*
* @param downX
* @param downY
*/

public void saveAnchorInfo(int downX, int downY) {
int halfSide = getMeasuredWidth() / 2;
anchorX = downX - halfSide;
anchorY = downY - halfSide;
}

/**
* 真正开始动画
*/

public void startAnchorAnimation() {
//没有值的话就不做操作
if (anchorX == Integer.MIN_VALUE || anchorY == Integer.MIN_VALUE) {
return;
}

springX.setOvershootClampingEnabled(true);
springY.setOvershootClampingEnabled(true);
springX.setSpringConfig(springConfigDragging);
springY.setSpringConfig(springConfigDragging);
//移动到那个位置
animTo(anchorX, anchorY);
//缩放
scaleSize(DraggableItemView.SCALE_LEVEL_3);
}

/**
* 与 springX.addListener 和 springY.addListener 相接应
*
* @param xPos
* @param yPos
*/

public void animTo(int xPos, int yPos) {
springX.setEndValue(xPos);
springY.setEndValue(yPos);
}

/**
* 设置缩放大小
*/

public void scaleSize(int scaleLevel) {
float rate = scaleRate;
if (scaleLevel == SCALE_LEVEL_1) {
rate = 1.0f;
} else if (scaleLevel == SCALE_LEVEL_3) {
rate = smallerRate;
}

if (scaleAnimator != null && scaleAnimator.isRunning()) {
scaleAnimator.cancel();
}

scaleAnimator = ObjectAnimator
.ofFloat(this, "custScale", imageView.getScaleX(), rate)
.setDuration(200);
scaleAnimator.setInterpolator(new DecelerateInterpolator());
scaleAnimator.start();
}
}

切换位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DraggableItemView extends FrameLayout {

/**
* 从一个位置切换到另一个位置
*/

public void switchPosition(int toStatus) {
if (this.status == toStatus) {
throw new RuntimeException("程序错乱");
}

if (toStatus == STATUS_LEFT_TOP) {
scaleSize(SCALE_LEVEL_1);
} else if (this.status == STATUS_LEFT_TOP) {
scaleSize(SCALE_LEVEL_2);
}

this.status = toStatus;
Point point = parentView.getOriginViewPos(status);
animTo(point.x, point.y);
}
}

填充图片:

1
2
3
4
5
6
7
public class DraggableItemView extends FrameLayout {
public void fillImageView(String imagePath) {
this.imagePath = imagePath;
addView.setVisibility(View.GONE);
ImageLoader.getInstance().displayImage(imagePath, imageView);
}
}

当 drag 结束释放的时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DraggableItemView extends FrameLayout {
/**
* 释放
*/

public void onDragRelease() {
//通过status来判断要改变的size的大小
if (status == DraggableItemView.STATUS_LEFT_TOP) {
scaleSize(DraggableItemView.SCALE_LEVEL_1);
} else {
scaleSize(DraggableItemView.SCALE_LEVEL_2);
}

springX.setOvershootClampingEnabled(false);
springY.setOvershootClampingEnabled(false);
springX.setSpringConfig(springConfigCommon);
springY.setSpringConfig(springConfigCommon);
//得到将要移动过去的位置
Point point = parentView.getOriginViewPos(status);
//给facebook的库说动画起始的位置
setCurrentSpringPos(getLeft(), getTop());
//给facebook的库说位置移动到point.x和point.y的位置上
animTo(point.x, point.y);
}
}

更新位置:

1
2
3
4
5
6
7
8
9
10
11
12
public class DraggableItemView extends FrameLayout {

//与 springX.addListener 和 springY.addListener 相接应
public void updateEndSpringX(int dx) {
springX.setEndValue(springX.getEndValue() + dx);
}

//与 springX.addListener 和 springY.addListener 相接应
public void updateEndSpringY(int dy) {
springY.setEndValue(springY.getEndValue() + dy);
}
}

总结

  • ViewGroup#bringChildToFront(View) 真是经典,觉得是该读一读 View 和 ViewGroup 的源码了
  • Facebook 的 Rebound 这个库真棒
  • 判断位置这部分也挺有意思的,一起调换位置