网上一位大神写的控件, 觉得写得很好,再三品味,加上了一些自己的思考、注释和补充。
import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.FrameLayout;
public class FloatingMagnetView extends FrameLayout {
public static final int MARGIN_EDGE = 33;
private float mOriginalRawX;
private float mOriginalRawY;
private float mOriginalX;
private float mOriginalY;
private MagnetViewListener mMagnetViewListener;
private static final int TOUCH_TIME_THRESHOLD = 150;
private long mLastTouchDownTime;
protected MoveAnimator mMoveAnimator;
protected int mScreenWidth;
private int mScreenHeight;
private int mStatusBarHeight;
private boolean isNearestLeft = true;
private float mPortraitY;
private boolean dragEnable = true;
private boolean autoMoveToEdge = true;
private float originX;
private float originY;
private float moveX;
private float moveY;
public void setMagnetViewListener(MagnetViewListener magnetViewListener) {
this.mMagnetViewListener = magnetViewListener;
}
public FloatingMagnetView(Context context) {
this(context, null);
}
public FloatingMagnetView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FloatingMagnetView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mMoveAnimator = new MoveAnimator();
mStatusBarHeight = SystemUtils.getStatusBarHeight(getContext());
setClickable(true);
// updateSize();
}
/**
* @param dragEnable 是否可拖动
*/
public void updateDragState(boolean dragEnable) {
this.dragEnable = dragEnable;
}
/**
* @param autoMoveToEdge 是否自动到边缘
*/
public void setAutoMoveToEdge(boolean autoMoveToEdge) {
this.autoMoveToEdge = autoMoveToEdge;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event == null) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//记录下当前按下点的坐标相对父容器的位置
originY = event.getY();
originX = event.getX();
dealDownEvent();
break;
case MotionEvent.ACTION_MOVE:
updateViewPosition(event);
break;
case MotionEvent.ACTION_UP:
clearPortraitY();
if (autoMoveToEdge) {
moveToEdge();
}
if (isOnClickEvent()) {
dealClickEvent();
} else {
dealUpEvent();
}
break;
}
return true;
}
protected void dealUpEvent() {
if (mMagnetViewListener != null) {
mMagnetViewListener.onUp(this);
}
}
protected void dealClickEvent() {
if (mMagnetViewListener != null) {
mMagnetViewListener.onClick(this);
}
}
protected void dealDownEvent() {
if (mMagnetViewListener != null) {
mMagnetViewListener.onDown(this);
}
}
protected boolean isOnClickEvent() {
return System.currentTimeMillis() - mLastTouchDownTime < TOUCH_TIME_THRESHOLD;
}
private void updateViewPosition(MotionEvent event) {
//dragEnable
if (!dragEnable) return;
// *********************移动ViewGroup方法一 :layout *****************/
// float y = event.getY();
// float x = event.getX();
//
// moveX = x - originX;
// moveY = y - originY;
//
// //view移动前的上下左右位置
// float left = getLeft() + moveX;
// float top = getTop() + moveY;
// float right = getRight() + moveX;
// float bottom = getBottom() + moveY;
//
// if ((left) < 0) {
// left = 0;
// right = getWidth();
// }
//
// if (top < mStatusBarHeight) {
// top = mStatusBarHeight;
// bottom = getHeight()+mStatusBarHeight;
// }
// layout((int) (left), (int) (top), (int) (right), (int) (bottom));
// **********************移动ViewGroup方法二:setX,setY ******************//
//限制不可超出屏幕宽度
float desX = mOriginalX + event.getRawX() - mOriginalRawX;
if (desX < 0) {
desX = MARGIN_EDGE;
}
if (desX > mScreenWidth) {
desX = mScreenWidth - MARGIN_EDGE;
}
setX(desX);
// 限制不可超出屏幕高度
float desY = mOriginalY + event.getRawY() - mOriginalRawY;
if (desY < mStatusBarHeight) {
desY = mStatusBarHeight;
}
if (desY > mScreenHeight - getHeight()) {
desY = mScreenHeight - getHeight();
}
setY(desY);
}
private void changeOriginalTouchParams(MotionEvent event) {
mOriginalX = getX();
mOriginalY = getY();
mOriginalRawX = event.getRawX();
mOriginalRawY = event.getRawY();
mLastTouchDownTime = System.currentTimeMillis();
}
protected void updateSize() {
ViewGroup viewGroup = (ViewGroup) getParent();
if (viewGroup != null) {
mScreenWidth = viewGroup.getWidth() - getWidth();
mScreenHeight = viewGroup.getHeight();
}
// mScreenWidth = (SystemUtils.getScreenWidth(getContext()) - this.getWidth());
// mScreenHeight = SystemUtils.getScreenHeight(getContext());
}
public void moveToEdge() {
//dragEnable
if (!dragEnable) return;
moveToEdge(isNearestLeft(), false);
}
public void moveToEdge(boolean isLeft, boolean isLandscape) {
float moveDistance = isLeft ? MARGIN_EDGE : mScreenWidth - MARGIN_EDGE;
float y = getY();
if (!isLandscape && mPortraitY != 0) {
y = mPortraitY;
clearPortraitY();
}
mMoveAnimator.start(moveDistance, Math.min(Math.max(0, y), mScreenHeight - getHeight()));
}
private void clearPortraitY() {
mPortraitY = 0;
}
protected boolean isNearestLeft() {
int middle = mScreenWidth / 2;
isNearestLeft = getX() < middle;
return isNearestLeft;
}
public void onRemove() {
if (mMagnetViewListener != null) {
mMagnetViewListener.onRemove(this);
}
}
//最赞的,用最基础的代码实现了动画。handler+setX()
protected class MoveAnimator implements Runnable {
private Handler handler = new Handler(Looper.getMainLooper());
private float destinationX;
private float destinationY;
private long startingTime;
void start(float x, float y) {
this.destinationX = x;
this.destinationY = y;
startingTime = System.currentTimeMillis();
handler.post(this);
}
@Override
public void run() {
if (getRootView() == null || getRootView().getParent() == null) {
return;
}
//400ms
float progress = Math.min(1, (System.currentTimeMillis() - startingTime) / 400f);
float deltaX = (destinationX - getX()) * progress;
float deltaY = (destinationY - getY()) * progress;
move(deltaX, deltaY);
if (progress < 1) {
handler.post(this);
}
}
private void stop() {
handler.removeCallbacks(this);
}
}
//最基础的代码实现的动画,通过handler 不断的 setX,setY来移动位置
private void move(float deltaX, float deltaY) {
setX(getX() + deltaX);
setY(getY() + deltaY);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (getParent() != null) {
final boolean isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
markPortraitY(isLandscape);
((ViewGroup) getParent()).post(new Runnable() {
@Override
public void run() {
updateSize();
moveToEdge(isNearestLeft, isLandscape);
}
});
}
}
private void markPortraitY(boolean isLandscape) {
if (isLandscape) {
mPortraitY = getY();
}
}
private float touchDownX;
private void initTouchDown(MotionEvent ev) {
changeOriginalTouchParams(ev);
updateSize();
mMoveAnimator.stop();
}
//判断是否拦截父容器
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
intercepted = false;
touchDownX = ev.getX();
initTouchDown(ev);
break;
case MotionEvent.ACTION_MOVE:
intercepted = Math.abs(touchDownX - ev.getX()) >= ViewConfiguration.get(getContext()).getScaledTouchSlop();
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
}
return intercepted;
}
}