拖拽控件
2020-06-07 本文已影响0人
我的阿福
-
为了复习View的位置坐标,今天做一个可以在父布局内随意拖拽的小控件,值得一提的是,这个可以根据父布局来调整整体的效果,,如图所示:
p3o9v-v4wdh.gif
-
整个控件很简单,但能帮助回忆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方向滑动距离
- 移动和手指相同的距离
这里移动用setTranslationX和setTranslationY的方法来移动,这两个方法是指左顶点相对于父布局一开始的位置的偏移量。需要注意的是,视图钢加载出来的时候,不论view在父布局的什么位置,这两个偏移量都是0。
float tranY = getTranslationY() + delY;
float nowReY = getY();
//这几句判断是为了不让自己垂直方向超出父视图的边界
if (nowReY <= 0 && delY < 0) {
tranY = -getTop();
} else if (nowReY + mHeight >= mParentHeight && delY > 0) {
tranY = mParentHeight - mHeight - getTop();
}
float tranX = getTranslationX() + delX;
float nowReX = getX();
//同理,不让自己水平方向超出父视图的边界
if (nowReX <= 0 && delX < 0) {
tranX = -getLeft();
} else if (nowReX + mWidth >= mParentWidth && delX > 0) {
tranX = mParentWidth - getRight();
}
setTranslationX(tranX);
setTranslationY(tranY);
- 手指离开屏幕,up事件时,判断边界距离,贴到边界。
if (getX() + (mWidth / 2) > (mParentWidth / 2)) {
int rightMoveDistance = (int) (mParentWidth - getX() - mWidth / 2);
//貼右边
startSideAnimation(RIGTHSIDE, getTranslationX(), rightMoveDistance);
} else {
//貼左边
int leftMoveDistance = (int) (getX() + mWidth / 2);
startSideAnimation(LEFTSIDE, getTranslationX(), leftMoveDistance);
}
private void startSideAnimation(final int side, final float origTX, int moveDistance) {
mSideAnimation = ValueAnimator.ofFloat(moveDistance);
mSideAnimation.setDuration(200);
mSideAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
mSideAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
switch (side) {
case LEFTSIDE:
setTranslationX(origTX - value);
break;
default:
setTranslationX(origTX + value);
}
}
});
mSideAnimation.start();
}
整个过程其实很简单,但值得注意的地方有两个:
1.获取手指位置用的getRawX而不是getX,这两个的区别:getRawX是手指接触view的位置距离整个屏幕边界的距离,getX是手指触摸view的位置距离父布局边界的距离。如果这里用getX的话,当view移动的时候,getX也会相应改变,无法达到想要的效果。
2.移动view的位置,直接用setTranslationX,setTranslationY的方法。
- 整个代码
package com.anthony.widgets;
import android.animation.ValueAnimator;
import android.content.Context;
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.animation.AccelerateDecelerateInterpolator;
/**
* Created by H.Anthony on 2020/06/06.
*/
public class MoveSpirit extends View {
private static final String TAG = "MoveSpirit";
private static final int LEFTSIDE = 0x1;
private static final int RIGTHSIDE = 0x2;
private float mLastTouchX, mLastTouchY;
private int mParentWidth, mParentHeight, mWidth, mHeight;
private ValueAnimator mSideAnimation;
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();
Log.d(TAG, "onSizeChanged: " + mParentWidth + ";" + mParentHeight + ";" + mWidth + ";" + mHeight);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
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 tranY = getTranslationY() + delY;
float nowReY = getY();
if (nowReY <= 0 && delY < 0) {
tranY = -getTop();
} else if (nowReY + mHeight >= mParentHeight && delY > 0) {
tranY = mParentHeight - mHeight - getTop();
}
float tranX = getTranslationX() + delX;
float nowReX = getX();
if (nowReX <= 0 && delX < 0) {
tranX = -getLeft();
} else if (nowReX + mWidth >= mParentWidth && delX > 0) {
tranX = mParentWidth - getRight();
}
setTranslationX(tranX);
setTranslationY(tranY);
mLastTouchX = nowX;
mLastTouchY = nowY;
break;
case MotionEvent.ACTION_UP:
autoTouchSide();
break;
}
return true;
}
/**
* 左右動畫
*/
private void autoTouchSide() {
if (getX() + (mWidth / 2) > (mParentWidth / 2)) {
int rightMoveDistance = (int) (mParentWidth - getX() - mWidth / 2);
//貼右边
startSideAnimation(RIGTHSIDE, getTranslationX(), rightMoveDistance);
} else {
//貼左边
int leftMoveDistance = (int) (getX() + mWidth / 2);
startSideAnimation(LEFTSIDE, getTranslationX(), leftMoveDistance);
}
}
private void startSideAnimation(final int side, final float origTX, int moveDistance) {
mSideAnimation = ValueAnimator.ofFloat(moveDistance);
mSideAnimation.setDuration(200);
mSideAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
mSideAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
switch (side) {
case LEFTSIDE:
setTranslationX(origTX - value);
break;
default:
setTranslationX(origTX + value);
}
}
});
mSideAnimation.start();
}
private void closeAnimation() {
if (mSideAnimation != null && mSideAnimation.isRunning()) {
mSideAnimation.removeAllUpdateListeners();
mSideAnimation.cancel();
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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.anthony.widgets.MoveSpirit
android:layout_width="160dp"
android:layout_height="160dp"
android:background="@mipmap/panda"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>