因爲項目中有需要實現控件可任意拖拽的需求,所以簡單寫了個自定義OnTouchListener,以作拋磚引玉,歡迎大家提議反饋。
完整實現類如下,代碼中有詳細註釋:
用戶可以決定是否開啓自動拖拽邊緣功能,可以監聽控件的拖拽和點擊事件
public class OnDragTouchListener implements View.OnTouchListener {
private int mScreenWidth, mScreenHeight;//屏幕寬高
private float mOriginalX, mOriginalY;//手指按下時的初始位置
private float mDistanceX, mDistanceY;//記錄手指與view的左上角的距離
private int left, top, right, bottom;
private OnDraggableClickListener mListener;
private boolean hasAutoPullToBorder;//標記是否開啓自動拉到邊緣功能
public OnDragTouchListener() {
}
public OnDragTouchListener(boolean isAutoPullToBorder) {
hasAutoPullToBorder = isAutoPullToBorder;
}
@Override
public boolean onTouch(final View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mScreenWidth = v.getResources().getDisplayMetrics().widthPixels;
mScreenHeight = v.getResources().getDisplayMetrics().heightPixels;
mOriginalX = event.getRawX();
mOriginalY = event.getRawY();
mDistanceX = event.getRawX() - v.getLeft();
mDistanceY = event.getRawY() - v.getTop();
break;
case MotionEvent.ACTION_MOVE:
left = (int) (event.getRawX() - mDistanceX);
top = (int) (event.getRawY() - mDistanceY);
right = left + v.getWidth();
bottom = top + v.getHeight();
if (left < 0) {
left = 0;
right = left + v.getWidth();
}
if (top < 0) {
top = 0;
bottom = top + v.getHeight();
}
if (right > mScreenWidth) {
right = mScreenWidth;
left = right - v.getWidth();
}
if (bottom > mScreenHeight) {
bottom = mScreenHeight;
top = bottom - v.getHeight();
}
v.layout(left, top, right, bottom);
break;
case MotionEvent.ACTION_UP:
//在拖動過按鈕後,如果其他view刷新導致重繪,會讓按鈕重回原點,所以需要更改佈局參數
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
startAutoPull(v, lp);
//如果移動距離過小,則判定爲點擊
if (Math.abs(event.getRawX() - mOriginalX) <
ScreenUtils.getPxFromDp(v.getResources(), 5) &&
Math.abs(event.getRawY() - mOriginalY) <
ScreenUtils.getPxFromDp(v.getResources(), 5)) {
if (mListener != null) {
mListener.onClick(v);
}
}
//消除警告
v.performClick();
break;
}
return true;
}
public OnDraggableClickListener getOnDraggableClickListener() {
return mListener;
}
public void setOnDraggableClickListener(OnDraggableClickListener listener) {
mListener = listener;
}
public boolean isHasAutoPullToBorder() {
return hasAutoPullToBorder;
}
public void setHasAutoPullToBorder(boolean hasAutoPullToBorder) {
this.hasAutoPullToBorder = hasAutoPullToBorder;
}
/**
* 開啓自動拖拽
*
* @param v 拉動控件
* @param lp 控件佈局參數
*/
private void startAutoPull(final View v, final ViewGroup.MarginLayoutParams lp) {
if (!hasAutoPullToBorder) {
v.layout(left, top, right, bottom);
lp.setMargins(left, top, 0, 0);
v.setLayoutParams(lp);
if (mListener != null) {
mListener.onDragged(v, left, top);
}
return;
}
//當用戶拖拽完後,讓控件根據遠近距離回到最近的邊緣
float end = 0;
if ((left + v.getWidth() / 2) >= mScreenWidth / 2) {
end = mScreenWidth - v.getWidth();
}
ValueAnimator animator = ValueAnimator.ofFloat(left, end);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int leftMargin = (int) ((float) animation.getAnimatedValue());
v.layout(leftMargin, top, right, bottom);
lp.setMargins(leftMargin, top, 0, 0);
v.setLayoutParams(lp);
}
});
final float finalEnd = end;
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (mListener != null) {
mListener.onDragged(v, (int) finalEnd, top);
}
}
});
animator.setDuration(400);
animator.start();
}
/**
* 控件拖拽監聽器
*/
public interface OnDraggableClickListener {
/**
* 當控件拖拽完後回調
*
* @param v 拖拽控件
* @param left 控件左邊距
* @param top 控件右邊距
*/
void onDragged(View v, int left, int top);
/**
* 當可拖拽控件被點擊時回調
*
* @param v 拖拽控件
*/
void onClick(View v);
}
}
後期有時間會做更多優化和擴展功能,如果有什麼更好的方式或建議,歡迎大家反饋。