Github: android-drag-square 分析版本:886b738
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 { 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<>(); private DraggableItemView draggingView; 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); 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(); int len = allStatus.length; for (int i = 0; i < len; i++) { DraggableItemView itemView = new DraggableItemView(getContext()); itemView.setStatus(allStatus[i]); itemView.setParentView(this); originViewPositionList.add(new Point()); 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) { int everyLength = (getMeasuredWidth() - 4 * spaceInterval) / 3; int itemLeft = 0; int itemTop = 0; int itemRight = 0; int itemBottom = 0; 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; } ViewGroup.LayoutParams lp = itemView.getLayoutParams(); lp.width = sideLength; lp.height = sideLength; itemView.setLayoutParams(lp); 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; private int sideLength; private long downTime = 0; private int downX, downY; private Thread moveAnchorThread; private Handler anchorHandler; * 这是viewdraghelper拖拽效果的主要逻辑 */ private class DragHelperCallback extends ViewDragHelper.Callback {
@Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { 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) { return Math.abs(dy) + Math.abs(dx) > mTouchSlop; } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { getParent().requestDisallowInterceptTouchEvent(true); downX = (int) ev.getX(); downY = (int) ev.getY(); downTime = System.currentTimeMillis(); 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); if (indexOfChild(itemView) != getChildCount() - 1) { bringChildToFront(itemView); } if (!itemView.isDraggable()) { return; } itemView.saveAnchorInfo(downX, downY); 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; } 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 { mDragHelper.processTouchEvent(e); } catch (Exception ex) { ex.printStackTrace(); } return true; } }
|
这里有几点值得学习和探讨:
moveAnchorThread
的作用,是在子线程中进行时间的延时,而我在做的时候直接用的 Handler 的 sendEmptyMessageDelay() 方法,如果想取消的话就 handler.removeMessage()
ViewGroup#bringChildToFront(View)
该方法将 View 放到最前面那层,赞!之前我在做的时候是 重新 new 了个 ImageView 放在 ViewGroup 的最前的
- 通过手势与 View 的在位置的坐标(相对长宽)对比来判断在哪个位置了,而我在做的时候用的是
ListView#pointToPosition(int, int)
那套代码
- 正是因为
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) { 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) { if (centerY < everyWidth) { fromChangeIndex = DraggableItemView.STATUS_RIGHT_TOP; } else if (centerY < everyWidth * 2) { fromChangeIndex = DraggableItemView.STATUS_RIGHT_MIDDLE; } else { fromChangeIndex = DraggableItemView.STATUS_RIGHT_BOTTOM; } } else if (centerY > everyWidth * 2) { if (centerX < everyWidth) { fromChangeIndex = DraggableItemView.STATUS_LEFT_BOTTOM; } else if (centerX < everyWidth * 2) { fromChangeIndex = DraggableItemView.STATUS_MIDDLE_BOTTOM; } else { fromChangeIndex = DraggableItemView.STATUS_RIGHT_BOTTOM; } }
DraggableItemView toItemView = getItemViewByStatus(fromChangeIndex); 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: 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) { if (isModify) { DraggableItemView itemView = getItemViewByStatus(imageStatus); itemView.fillImageView(imagePath); return; }
for (int i = 0; i < allStatus.length; i++) { DraggableItemView itemView = getItemViewByStatus(i); if (!itemView.isDraggable()) { itemView.fillImageView(imagePath); break; } } }
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; public static final int SCALE_LEVEL_2 = 2; public static final int SCALE_LEVEL_3 = 3; 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; 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); imageView = (ImageView) findViewById(R.id.drag_item_imageview); maskView = findViewById(R.id.drag_item_mask_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); } } }; getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (!hasSetCurrentSpringValue) { adjustImageView(); hasSetCurrentSpringValue = true; } } }); 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(); } } }); 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() { 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); setCurrentSpringPos(getLeft(), getTop()); animTo(point.x, point.y); } }
|
更新位置:
1 2 3 4 5 6 7 8 9 10 11 12
| public class DraggableItemView extends FrameLayout {
public void updateEndSpringX(int dx) { springX.setEndValue(springX.getEndValue() + dx); }
public void updateEndSpringY(int dy) { springY.setEndValue(springY.getEndValue() + dy); } }
|
总结
ViewGroup#bringChildToFront(View)
真是经典,觉得是该读一读 View 和 ViewGroup 的源码了
- Facebook 的 Rebound 这个库真棒
- 判断位置这部分也挺有意思的,一起调换位置