先看效果
直接上代码
/**
* @Author : 马占柱
* E-mail : mazhanzhu_3351@163.com
* Time : 2023/6/17 23:18
* Desc : 拖拽、自吸边控件
*/
public class MoveFrameLayout extends FrameLayout {
private boolean isScaled;//是否已经放大
private AnimatorSet mAnimatorSet;//动画集合
public MoveFrameLayout(@NonNull Context context) {
this(context, null);
}
public MoveFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MoveFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
}
private boolean isAttach;//是否自动吸边
private boolean isDrag;//是否可拖动
/**
* 初始化自定义属性
*/
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray mTypedAttay = context.obtainStyledAttributes(attrs, R.styleable.MoveFrameLayout);
isAttach = mTypedAttay.getBoolean(R.styleable.MoveFrameLayout_IsAttach, true);
isDrag = mTypedAttay.getBoolean(R.styleable.MoveFrameLayout_IsDrag, true);
mTypedAttay.recycle();
}
private int mParentWidth = 0;//父控件的宽
private int mParentHeight = 0;//父控件的高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取父控件宽高
ViewGroup mViewGroup = (ViewGroup) getParent();
if (mViewGroup != null) {
int[] location = new int[2];
mViewGroup.getLocationOnScreen(location);
//获取父布局的高度
mParentHeight = mViewGroup.getMeasuredHeight();
mParentWidth = mViewGroup.getMeasuredWidth();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//点击点在控件区域内,接受事件
boolean inRangeOfView = inRangeOfView(this, ev);
return inRangeOfView;
}
private float mLastX;//按下位置x
private float mLastY;//按下位置Y
private boolean isDrug = false;//是否是拖动
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (isDrag) {
//请求父控件将事件交由自己处理
getParent().requestDisallowInterceptTouchEvent(true);
float mX = ev.getX();
float mY = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//重置拖动状态
isDrug = false;
//记录按下的位置
mLastX = mX;
mLastY = mY;
isScaled = false;
break;
case MotionEvent.ACTION_MOVE:
//手指X轴滑动距离
float differenceValueX = mX - mLastX;
//手指Y轴滑动距离
float differenceValueY = mY - mLastY;
//判断是否为拖动操作
if (!isDrug) {
if (Math.sqrt(differenceValueX * differenceValueX + differenceValueY * differenceValueY) < 2) {
isDrug = false;
} else {
isDrug = true;
}
}
//获取手指按下的距离与控件本身X轴的距离
float ownX = getX();
//获取手指按下的距离与控件本身Y轴的距离
float ownY = getY();
//理论中X轴拖动的距离
float endX = ownX + differenceValueX;
//理论中Y轴拖动的距离
float endY = ownY + differenceValueY;
//X轴可以拖动的最大距离
float maxX = mParentWidth - getWidth();
//Y轴可以拖动的最大距离
float maxY = mParentHeight -PxUtils.dp2px(50);//首页下面是50的tab高度
//X轴边界限制---这里对X轴拖动不做限制
//endX = endX < 0 ? 0 : endX > maxX ? maxX : endX;
//Y轴边界限制
endY = endY < 0 ? 0 : endY > maxY ? maxY : endY;
//开始移动
setX(endX);
setY(endY);
if (isDrug && !isScaled) {
//放大动画
startScaleAnimator(1.0f, 1.5f, 300);
//Animator scaleAnimator = AnimatorInflater.loadAnimator(getContext(), R.animator.anim_scale_up);
//scaleAnimator.setTarget(this);
//scaleAnimator.start();
isScaled = true;
}
break;
case MotionEvent.ACTION_UP:
//根据自定义属性判断是否需要贴边
if (isAttach) {
//判断是否为点击事件
float center = mParentWidth / 2;
//自动贴边
if (ev.getRawX() <= center) {
//向左贴边
animate()
//.setInterpolator(new BounceInterpolator())
.setInterpolator(new OvershootInterpolator())
.setDuration(400)
.x(PxUtils.dp2px(0))
.start();
} else {
//向右贴边
animate()
.setInterpolator(new OvershootInterpolator())
.setDuration(400)
.x(mParentWidth - getWidth() - PxUtils.dp2px(0))
.start();
}
if (isScaled) {
//缩小动画
startScaleAnimator(1.5f, 1.0f, 300);
//Animator scaleAnimator = AnimatorInflater.loadAnimator(getContext(), R.animator.anim_scale_down);
//scaleAnimator.setTarget(this);
//scaleAnimator.start();
isScaled = false;
}
}
break;
}
}
return isDrug ? true : super.onTouchEvent(ev);
}
/**
* 点击点是否在控件区域
*
* @param view
* @param ev
* @return
*/
private boolean inRangeOfView(View view, MotionEvent ev) {
Rect rect = new Rect();
view.getGlobalVisibleRect(rect);
int rawX = (int) ev.getRawX();
int rawY = (int) ev.getRawY();
if (rect.contains(rawX, rawY)) {
return true;
}
return false;
}
/**
* 执行动画
*
* @param valueFrom
* @param valueTo
* @param duration
*/
private void startScaleAnimator(float valueFrom, float valueTo, int duration) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(this, "scaleX", valueFrom, valueTo);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(this, "scaleY", valueFrom, valueTo);
if (null == mAnimatorSet) {
mAnimatorSet = new AnimatorSet();
}
mAnimatorSet.play(scaleX).with(scaleY);
mAnimatorSet.setDuration(duration);
mAnimatorSet.start();
}
}
然后在布局里面使用
<com.ouhui.store.view.MoveFrameLayout
android:id="@+id/me_kefu"
android:layout_width="@dimen/dp_50"
android:layout_height="@dimen/dp_50"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/dp_50"
app:IsAttach="true"
app:IsDrag="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_orange_100">
<ImageView
android:layout_centerHorizontal="true"
android:layout_width="@dimen/dp_20"
android:layout_height="@dimen/dp_20"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/dp_5"
android:background="@mipmap/ic_me_kefu" />
<TextView
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/dp_25"
android:text="客服"
android:textColor="@color/white"
android:textSize="@dimen/sp_10" />
</RelativeLayout>
</com.ouhui.store.view.MoveFrameLayout>
别忘了在values的attrs.xml里面添加自定义属性
<declare-styleable name="MoveFrameLayout">
<!--是否需要自动吸边-->
<attr name="IsAttach" format="boolean" />
<!--是否可拖曳-->
<attr name="IsDrag" format="boolean" />
</declare-styleable>
代码里面有一个工具类,可能有的人不清楚,我也发出来吧
/**
* Time : 2021/6/17 10:27
* Desc : 尺寸工具类
*/
public class PxUtils {
public static final String TAG = "PxUtils";
/**
* 得到设备屏幕的宽度
*/
public static int getScreenWidth(Context context) {
return context.getResources().getDisplayMetrics().widthPixels;
}
/**
* 得到设备屏幕的高度
*/
public static int getScreenHeight(Context context) {
return context.getResources().getDisplayMetrics().heightPixels;
}
/**
* 得到设备的密度
*/
public static float getScreenDensity(Context context) {
return context.getResources().getDisplayMetrics().density;
}
/**
* 把密度【dp】转换为像素【px】
*/
public static int dp2px(float dipValue) {
final float scale = getScreenDensity(BaseApplication.getContext());
return (int) (dipValue * scale + 0.5);
}
/**
* 将sp值转换为px值,保证文字大小不变
*/
public static int sp2px(float spValue) {
final float fontScale = BaseApplication.getContext().getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
/**
* 下面都是工具类,dp单位转px单位
*/
public static int dp2px(Context c, float dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, c.getResources().getDisplayMetrics());
}
public static float dp2pxF(Context c, float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, c.getResources().getDisplayMetrics());
}
public static float sp2pxF(Context c, float sp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, c.getResources().getDisplayMetrics());
}
public float caculate(ArrayList<PointXY> list, Context context) {
float temp = 0;
for (int i = 0; i < list.size(); i++) {
if (i < list.size() - 1) {
PointXY p1 = list.get(i);
PointXY p2 = list.get(i + 1);
temp += p1.getX() * p2.getY() - p2.getX() * p1.getY();
} else {
PointXY pn = list.get(i);
PointXY p0 = list.get(0);
temp += pn.getX() * p0.getY() - p0.getX() * pn.getY();
}
}
temp = temp / 2;
ArrayList<Float> area = getScreenSizeOfDevice(context);
temp = (temp / area.get(0)) * area.get(1) * 2.54f;
Log_Ma.e(TAG, "画图的面积 : " + temp);
return temp;
}
private ArrayList<Float> getScreenSizeOfDevice(Context context) {
ArrayList<Float> list02 = new ArrayList<Float>();
Point point = new Point();
DisplayMetrics dm = context.getResources().getDisplayMetrics();
float area = point.x * point.y;//得到像素面积,
Log_Ma.d(TAG, "屏幕的总像素面积: " + area);
list02.clear();
list02.add(area);
double x = Math.pow(point.x / dm.xdpi, 2);
double y = Math.pow(point.y / dm.ydpi, 2);
double screenInches = Math.sqrt(x + y);//得到尺寸大小
list02.add((float) (x * y));//得到手机屏幕的物理面积
// Log.d(TAG, "屏幕尺寸大小: "+screenInches);
return list02;
}
public class PointXY {
public float X;
public float Y;
public PointXY() {
this.X = 0;
this.Y = 0;
}
public PointXY(float x, float y) {
X = x;
Y = y;
}
public float getX() {
return X;
}
public void setX(float x) {
X = x;
}
public float getY() {
return Y;
}
public void setY(float y) {
Y = y;
}
}
}