-
为了复习View的位置坐标,今天做一个可以在父布局内随意拖拽的小控件,值得一提的是,这个可以根据父布局来调整整体的效果,,如图所示:
整个控件很简单,但能帮助回忆View的位置关系。
还是说一下整体思路:
1.监听触摸事件,计算手指移动的距离。
2.让view在x方向和y方向移动和手指滑动相同的距离。
3.加上贴边动画。.
- 获取滑动位置
当手指按下时,记住当前手指位置,滑动事件产生时用新的坐标减去旧的坐标就是滑动的距离。
case MotionEvent.ACTION_DOWN:
mLastTouchX = event.getRawX();//这里要注意,不能用getX,后面有解释
mLastTouchY = event.getRawY();
case MotionEvent.ACTION_MOVE:
float nowX = event.getRawX();
float nowY = event.getRawY();
float delX = nowX - mLastTouchX;//x方向滑动距离
float delY = nowY - mLastTouchY;//y方向滑动距离
- 移动和手指相同的距离
这里的移动是重点,采用了两种方式,一种是采用Translation属性,一种是设置margin属性。
就个例子,采用前者肯定是最方便的,但这里为了演示方法,两者都用上了。 - 手指离开屏幕,up事件时,判断边界距离,贴到边界。
整个过程其实很简单,但有几个值得注意的地方:
1.获取手指位置用的getRawX而不是getX,这两个的区别:getRawX是手指接触view的位置距离整个屏幕边界的距离,getX是手指触摸view的位置距离view边界的距离。如果这里用getX的话,当view移动的时候,getX也会相应改变,无法达到想要的效果。
2.setTranslationX,setTranslationY改变view位置的时候,view.getLeft等属性不会改变,一直是一开始布局完成的时候的值。
3.通过改变Margin属性改变位置的时候(设置值后需要手动重绘),view.getLeft等属性是会改变的,而且和view.getX等属性相等。
- 整个代码
package com.h.anthony;
import android.animation.ValueAnimator;
import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
/**
* Created by H.Anthony on 2020/06/06.
* 1、对于{@link View#getLeft()}这几个参数,如果是通过{@link View#setTranslationX(float)}来改变view的位置,
* 那么其值一直不会改变,是从布局一开始就定下的固定的值,比如一开始view离父布局左边有100像素,那么getLeft就是100,一直不会变。
* <p>
* 2、但如果通过设置{@link android.view.ViewGroup.MarginLayoutParams#leftMargin}方式来改变位置(当然,设置后需要手动重绘),
* getLeft的值是会变动的,而且这个值和{@link View#getX()}貌似是相等的
* <p>
* 3、有一点后面还需要验证看怎么做,实验发现{@link android.view.ViewGroup.MarginLayoutParams#leftMargin}的值一开始是0,不管view
* 一开始在父布局的什么位置,只有当设置了改变后才会变。这个和{@link View#getTranslationX()}有点像,但又不全一样,区别在于第1、2点。
*/
public class MoveSpirit extends View {
private static final String TAG = "MoveSpirit";
private float mLastTouchX, mLastTouchY;
private int mParentWidth, mParentHeight, mWidth, mHeight;
private AbstractMoveTool mMoveTool;
public MoveSpirit(Context context) {
super(context);
}
public MoveSpirit(Context context, @androidx.annotation.Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
ViewParent viewParent = getParent();
if (viewParent != null) {
if (viewParent instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) viewParent;
mParentWidth = viewGroup.getWidth();
mParentHeight = viewGroup.getHeight();
mWidth = getWidth();
mHeight = getHeight();
mMoveTool = new AnimaTionTransMoveTool(this, mParentWidth, mParentHeight, mWidth, mHeight);
// mMoveTool = new MarginLayoutMoveTool(MoveSpirit.this, mParentWidth, mParentHeight, mWidth, mHeight);
Log.d(TAG, "onSizeChanged: " + mParentWidth + ";" + mParentHeight + ";" + mWidth + ";" + mHeight);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mMoveTool != null) {
mMoveTool.closeAnimation();
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastTouchX = event.getRawX();
mLastTouchY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float nowX = event.getRawX();
float nowY = event.getRawY();
float delX = nowX - mLastTouchX;
float delY = nowY - mLastTouchY;
float moveX = mMoveTool.getRealXChange(delX);
float moveY = mMoveTool.getRealYChange(delY);
mMoveTool.setRealXChange(moveX);
mMoveTool.setRealYChange(moveY);
mLastTouchX = nowX;
mLastTouchY = nowY;
break;
case MotionEvent.ACTION_UP:
mMoveTool.startSideAnimation();
break;
}
return true;
}
/**
* 演示view自身位置改变的工具基类
*/
private static abstract class AbstractMoveTool {
protected View view;
protected SideAnimation sideAnimation;
protected int parentWidth, parentHeight, width, height;
protected float actionUpXPostion;
protected Updatecallback updatecallback = new Updatecallback() {
@Override
public void update(float progess) {
setRealXChange(actionUpXPostion + progess);
}
};
public AbstractMoveTool(View view, int parentWidth, int parentHeight, int width, int height) {
this.view = view;
this.parentWidth = parentWidth;
this.parentHeight = parentHeight;
this.width = width;
this.height = height;
this.sideAnimation = new SideAnimation(updatecallback);
}
protected abstract void setRealYChange(float realYChange);
protected abstract void setRealXChange(float realXChange);
protected abstract float getRealXChange(float delX);
protected abstract float getRealYChange(float delY);
public abstract void startSideAnimation();
public void closeAnimation() {
if (sideAnimation != null) {
sideAnimation.cancel();
}
}
}
/**
* 通过动画属性,setTranslationX(Y)来实现位置改变
*/
private static class AnimaTionTransMoveTool extends AbstractMoveTool {
public AnimaTionTransMoveTool(View view, int parentWidth, int parentHeight, int width, int height) {
super(view, parentWidth, parentHeight, width, height);
}
@Override
protected void setRealXChange(float realXChange) {
view.setTranslationX(realXChange);
}
@Override
protected void setRealYChange(float realYChange) {
view.setTranslationY(realYChange);
}
@Override
protected float getRealXChange(float delX) {
float tranX = view.getTranslationX() + delX;
float nowReX = view.getX();
if (nowReX <= 0 && delX < 0) {
tranX = -view.getLeft();
} else if (nowReX + width >= parentWidth && delX > 0) {
tranX = parentWidth - view.getRight();
}
return tranX;
}
@Override
protected float getRealYChange(float delY) {
float tranY = view.getTranslationY() + delY;
float nowReY = view.getY();
if (nowReY <= 0 && delY < 0) {
tranY = -view.getTop();
} else if (nowReY + height >= parentHeight && delY > 0) {
tranY = parentHeight - height - view.getTop();
}
return tranY;
}
@Override
public void startSideAnimation() {
if (view.getX() + (width / 2) > (parentWidth / 2)) {
int rightMoveDistance = (int) (parentWidth - view.getX() - width / 2);
actionUpXPostion = view.getTranslationX();
sideAnimation.startSideAnimation(rightMoveDistance, false);
} else {
//貼左边
int leftMoveDistance = (int) (view.getX() + width / 2);
actionUpXPostion = view.getTranslationX();
sideAnimation.startSideAnimation(leftMoveDistance, true);
}
}
}
/**
* 通过改变布局参数margin来实现位置改变
*/
private static class MarginLayoutMoveTool extends AbstractMoveTool {
private ViewGroup.MarginLayoutParams marginLayoutParams;
//记住一开始距离父布局边界的位置,因为这个和改变Translation不同,设置margin后,getLeft是会变的。
private int orignalLeft, orignalTop;
public MarginLayoutMoveTool(View view, int parentWidth, int parentHeight, final int width, final int height) {
super(view, parentWidth, parentHeight, width, height);
marginLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
orignalLeft = view.getLeft();
orignalTop = view.getTop();
Log.d(TAG, "MarginLayoutMoveTool: " + marginLayoutParams.leftMargin + ";" + marginLayoutParams.topMargin + ";" + marginLayoutParams.rightMargin + ";" + marginLayoutParams.bottomMargin + "----" + orignalLeft + ";" + orignalTop);
}
@Override
protected void setRealXChange(float realXChange) {
marginLayoutParams.leftMargin = (int) realXChange;
view.requestLayout();
}
@Override
protected void setRealYChange(float realYChange) {
marginLayoutParams.topMargin = (int) realYChange;
view.requestLayout();
}
@Override
protected float getRealXChange(float delX) {
float mX = marginLayoutParams.leftMargin + delX;
if (view.getX() <= 0 && delX < 0) {
mX = -orignalLeft;
} else if (view.getX() + width >= parentWidth && delX > 0) {
mX = parentWidth - orignalLeft - width;
}
return mX;
}
@Override
protected float getRealYChange(float delY) {
float mY = marginLayoutParams.topMargin + delY;
if (view.getY() <= 0 && delY < 0) {
mY = -orignalTop;
} else if (view.getY() + width >= parentHeight && delY > 0) {
mY = parentHeight - orignalTop - height;
}
return mY;
}
@Override
public void startSideAnimation() {
if (view.getX() + width / 2 > (parentWidth / 2)) {
int rightMoveDistance = (int) (parentWidth - view.getX() - width / 2);
actionUpXPostion = marginLayoutParams.leftMargin;
sideAnimation.startSideAnimation(rightMoveDistance, false);
} else {
//貼左边
int leftMoveDistance = (int) (view.getX() + width / 2);
actionUpXPostion = marginLayoutParams.leftMargin;
sideAnimation.startSideAnimation(leftMoveDistance, true);
}
}
}
static class SideAnimation {
private Updatecallback updatecallback;
private ValueAnimator valueAnimator;
public SideAnimation(final Updatecallback updatecallback) {
this.updatecallback = updatecallback;
}
void startSideAnimation(float distance, final boolean isReverse) {
valueAnimator = ValueAnimator.ofFloat(distance);
valueAnimator.setDuration(200);
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
updatecallback.update(isReverse ? (-1 * value) : value);
}
});
valueAnimator.start();
}
void cancel() {
if (valueAnimator != null && valueAnimator.isRunning()) {
valueAnimator.removeAllUpdateListeners();
valueAnimator.cancel();
}
}
}
interface Updatecallback {
void update(float progess);
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.h.anthony.MoveSpirit
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:background="@mipmap/kf" />
</FrameLayout>