前言
可以任意拖动的自定义图片控件(可点击,有动画)
一、话不多说,先看效果 ![请添加图片描述](https://img-blog.csdnimg.cn/0ee4c970d47348afa8fc2e0760fee18d.gif)
二、使用步骤
1.引入自定义代码
代码如下(示例):
public class MoveScaleRotateView extends RelativeLayout{
private Context mContext;
private ImageView clickOnIV;
public void setImage(int imgId) {
clickOnIV.setImageDrawable(mContext.getResources().getDrawable(imgId));
}
//默认的触摸点ID
private static final int INVALID_POINTER_ID = -1;
//子View上的两个手指的触摸点ID
private int mChildPtrID1 = INVALID_POINTER_ID, mChildPtrID2
= INVALID_POINTER_ID;
//父View上的两个手指的触摸点ID
private int mPtrID1 = INVALID_POINTER_ID, mPtrID2 = INVALID_POINTER_ID;
//父布局的Event事件
private MotionEvent mEvent;
//记录点击在子View上的x和y坐标
private float mChildActionDownX = 0;
private float mChildActionDownY = 0;
//记录点击在父View上的第一个点和第二个点的x和y坐标
private float mActionDownX1 = 0;
private float mActionDownX2 = 0;
private float mActionDownY1 = 0;
private float mActionDownY2 = 0;
//初始的旋转角度
private float mDefaultAngle;
//当前旋转角度
private float mAngle;
//记录原始落点的时候两个手指之间的距离
private float oldDist = 0;
//测试View
private View view;
private DisplayMetrics dm;
int x,y;
//初始化操作
public void init(final Context context ,int x,int y) {
mContext = context;
view = View.inflate(context, R.layout.float_view, null);
addView(view);
clickOnIV = view.findViewById(R.id.clickOnIV);
//获取屏幕宽高
dm = new DisplayMetrics();
dm = getResources().getDisplayMetrics();
view.setX(dm.widthPixels - new Dp_Px_Util().Dp2Px(context,x));
view.setY(dm.heightPixels - new Dp_Px_Util().Dp2Px(context,y));
this.setFocusableInTouchMode(true);
view.setOnTouchListener(new OnTouchListener() {
private float lastY;
private float lastX;
private float rawY;
private float rawX;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mChildPtrID1 = event.getPointerId(event.getActionIndex());
if (mEvent != null) {
mChildActionDownX = mEvent.getX()
- view.getX();
mChildActionDownY = mEvent.getY()
- view.getY();
rawX = (event.getRawX());
rawY = (event.getRawY());
lastX = rawX;
lastY = rawY;
} else {
return false;
}
break;
case MotionEvent.ACTION_MOVE:
if (mEvent != null) {
//获取控件位置xy
float x1 = mEvent.getX();
float y1 = mEvent.getY();
if (x1 - mChildActionDownX <= 0) {
view.setX(0);
} else if (x1 - mChildActionDownX >= dm.widthPixels - new Dp_Px_Util().Dp2Px(context,50)) {
view.setX(dm.widthPixels - new Dp_Px_Util().Dp2Px(context,50));
} else {
view.setX(x1 - mChildActionDownX);
}
if (y1 - mChildActionDownY <= 0) {
view.setY(0);
} else if (y1 - mChildActionDownY >= dm.heightPixels - new Dp_Px_Util().Dp2Px(context,50)) {
view.setY(dm.heightPixels - new Dp_Px_Util().Dp2Px(context,50));
} else {
view.setY(y1 - mChildActionDownY);
}
lastX = (event.getRawX());
lastY = (event.getRawY());
} else {
return false;
}
break;
case MotionEvent.ACTION_UP:
//抬起的
float x1 = mEvent.getX();
mChildPtrID1 = INVALID_POINTER_ID;
if (rawX - lastX <= 10 && rawX - lastX >= -10
&& rawY - lastY >= -10 && rawY - lastY <= 10) {
if (l != null) {
l.onClick(view);
}
}else {
if(x1 - mChildActionDownX<dm.widthPixels/2){
int dw1 = new Dp_Px_Util().Dp2Px(context, 20);
view.setX(new Dp_Px_Util().Dp2Px(context,20));
ObjectAnimator translatey1 = new ObjectAnimator().ofFloat(view, "translationX", dw1+90,dw1,dw1+50,dw1,dw1+20 ,dw1);
translatey1.setDuration(300).start();
}else {
int dw3 = dm.widthPixels - new Dp_Px_Util().Dp2Px(context, 70);
view.setX(dm.widthPixels - new Dp_Px_Util().Dp2Px(context,70));
ObjectAnimator translatey1 = new ObjectAnimator().ofFloat(view, "translationX", dw3-90,dw3,dw3-50,dw3,dw3-20,dw3);
translatey1.setDuration(300).start();
}
}
break;
case MotionEvent.ACTION_CANCEL:
mChildPtrID1 = INVALID_POINTER_ID;
mChildPtrID2 = INVALID_POINTER_ID;
break;
}
return true;
}
});
}
public class Dp_Px_Util {
public int Dp2Px(Context context, float dp) {
final float scale = context.getResources().getDisplayMetrics().density; //当前屏幕密度因子
return (int) (dp * scale + 0.5f);
}
public int Px2Dp(Context context, float px) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (px / scale + 0.5f);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mEvent = event;
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mPtrID1 = event.getPointerId(event.getActionIndex());
mActionDownX1 = event.getX(event.findPointerIndex(mPtrID1));
mActionDownY1 = event.getY(event.findPointerIndex(mPtrID1));
break;
case MotionEvent.ACTION_POINTER_DOWN:
//非第一个触摸点按下
mPtrID2 = event.getPointerId(event.getActionIndex());
mActionDownX2 = event.getX(event.findPointerIndex(mPtrID2));
mActionDownY2 = event.getY(event.findPointerIndex(mPtrID2));
oldDist = spacing(event, mPtrID1, mPtrID2);
break;
case MotionEvent.ACTION_MOVE:
if (mPtrID1 != INVALID_POINTER_ID && mPtrID2 != INVALID_POINTER_ID) {
float x1 = 0, x2 = 0, y1 = 0, y2 = 0;
x1 = event.getX(event.findPointerIndex(mPtrID1));
y1 = event.getY(event.findPointerIndex(mPtrID1));
x2 = event.getX(event.findPointerIndex(mPtrID2));
y2 = event.getY(event.findPointerIndex(mPtrID2));
//在这里处理旋转逻辑
mAngle = angleBetweenLines(mActionDownX1, mActionDownY1, mActionDownX2,
mActionDownY2, x1, y1, x2, y2) + mDefaultAngle;
view.setRotation(mAngle);
//在这里处理缩放的逻辑
//处理缩放模块
float newDist = spacing(event, mPtrID1, mPtrID2);
float scale = newDist / oldDist;
if (newDist > oldDist + 1) {
// zoom(scale, view);
oldDist = newDist;
}
if (newDist < oldDist - 1) {
// zoom(scale, view);
oldDist = newDist;
}
}
break;
case MotionEvent.ACTION_UP:
mPtrID1 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
//非第一个触摸点抬起
mPtrID2 = INVALID_POINTER_ID;
mDefaultAngle = mAngle;
break;
case MotionEvent.ACTION_CANCEL:
mPtrID1 = INVALID_POINTER_ID;
mPtrID2 = INVALID_POINTER_ID;
break;
}
return false;
}
//对控件进行缩放操作
private void zoom(float scale, View view) {
int w = view.getWidth();
int h = view.getHeight();
view.setLayoutParams(new LayoutParams((int) (w * scale), (int) (h * scale)));
}
/**
* 计算两点之间的距离
*
* @param event
* @return 两点之间的距离
*/
private float spacing(MotionEvent event, int ID1, int ID2) {
float x = event.getX(ID1) - event.getX(ID2);
float y = event.getY(ID1) - event.getY(ID2);
return 1;
}
/**
* 计算刚开始触摸的两个点构成的直线和滑动过程中两个点构成直线的角度
*
* @param fX 初始点一号x坐标
* @param fY 初始点一号y坐标
* @param sX 初始点二号x坐标
* @param sY 初始点二号y坐标
* @param nfX 终点一号x坐标
* @param nfY 终点一号y坐标
* @param nsX 终点二号x坐标
* @param nsY 终点二号y坐标
* @return 构成的角度值
*/
private float angleBetweenLines(float fX, float fY, float sX, float sY, float nfX, float nfY, float nsX, float nsY) {
float angle1 = (float) Math.atan2((fY - sY), (fX - sX));
float angle2 = (float) Math.atan2((nfY - nsY), (nfX - nsX));
float angle = ((float) Math.toDegrees(angle1 - angle2)) % 360;
if (angle < -180.f) angle += 360.0f;
if (angle > 180.f) angle -= 360.0f;
return -angle;
}
public void init_xy(Context context,int x,int y){
Log.e("aaa","x"+x);
Log.e("aaa","y"+y);
this.x=x;
this.y=y;
init(context,x,y);
}
public MoveScaleRotateView(Context context) {
super(context);
}
public MoveScaleRotateView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 点击浮动按钮触发事件,需要override该方法
*/
private OnClickListener l;
public void onFloatViewClick(OnClickListener l) {
this.l = l;
}
/**
* 测试用 显示Toast
*
* @param msg
*/
private void showToast(String msg) {
Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
}
/**
* 测试用 打印log
*
* @param log
*/
private void log(String log) {
Log.e("HHHHHHHHHH", log);
}
/**
* 测试用 打印log 指定TAG
*
* @param log
* @param tag
*/
private void log(String log, String tag) {
Log.e(tag, log);
}
}
2.自定义布局代码
代码如下 :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="50dp"
android:layout_height="50dp"
android:focusableInTouchMode="true">
<ImageView
android:id="@+id/clickOnIV"
android:layout_width="50dp"
android:layout_height="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</RelativeLayout>
3.调用页面布局代码
<RelativeLayout 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=".func.Freedom_Act">
<com.ami.myzonghe_demo.utils.MoveScaleRotateView
android:id="@+id/floatingBtn"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusableInTouchMode="true" />
</RelativeLayout>
3.主页面代码
public class Freedom_Act extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_freedom);
MoveScaleRotateView floatingBtn;
floatingBtn=findViewById(R.id.floatingBtn);
floatingBtn.init_xy(this,70,100);
floatingBtn.setImage(R.drawable.ic_launcher_background);
floatingBtn.onFloatViewClick(new View.OnClickListener() {
@Override
public void onClick(View v) {
floatingBtn.setImage(R.drawable.zm);
Toast.makeText(Freedom_Act.this, "点击", Toast.LENGTH_SHORT).show();
}
});
}
}
总结
有相关需求的大家可以复制尝试一下,直接可以用,后面单独添加了靠边和左右移动的动画,感觉比较符合场景,大家可以根据需求添加删减