<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/joystick"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="50dp"
android:background="@drawable/click_item"/>
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid android:color="#66000000" />
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="@android:color/transparent" />
<stroke
android:width="2dp"
android:color="@android:color/black" />
</shape>
</item>
</layer-list>
首先是布局文件和控件的item(外圈黑色,内圈透明的灰色)
package com.example.moveclick;
public interface OnJoystickMoveListener {
void onJoystickMove(String direction);
}
一个接口,负责监听控件的移动方向
package com.example.moveclick;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity implements GestureDetector.OnGestureListener {
private FrameLayout container;
private ImageView joystick;
private GestureDetector gestureDetector;
private float centerX, centerY;
private float lastX, lastY;
private static final int RADIUS = 30;
private OnJoystickMoveListener joystickMoveListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
container = findViewById(R.id.container);
joystick = findViewById(R.id.joystick);
gestureDetector = new GestureDetector(this, this);
ViewTreeObserver viewTreeObserver = container.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
container.getViewTreeObserver().removeOnGlobalLayoutListener(this);
centerX = container.getWidth() / 2f;
centerY = container.getHeight() * 5/6f;
}
});
joystickMoveListener = new OnJoystickMoveListener() {
@Override
public void onJoystickMove(String direction) {
// 在这里实现接口方法,可以根据方向来做一些操作
// 比如移动控件、播放动画、发送网络请求等
// 这里只是简单地打印一下方向信息
System.out.println("Joystick direction: " + direction);
}
};
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
@Override
public boolean onDown(MotionEvent e) {
lastX = e.getX();
lastY = e.getY();
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
float deltaX = e2.getX() - centerX;
float deltaY = e2.getY() - centerY;
float distance = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
float angle = (float) Math.atan2(deltaY, deltaX);
if (distance > RADIUS) {
distance = RADIUS;
deltaX = (float) (RADIUS * Math.cos(angle));
deltaY = (float) (RADIUS * Math.sin(angle));
}
joystick.setX(centerX + deltaX - joystick.getWidth() / 2);
joystick.setY(centerY + deltaY - joystick.getHeight() / 2);
String direction = "";
if (deltaY < -RADIUS/2) {
direction = "up";
} else if (deltaY > RADIUS/2) {
direction = "down";
} else if (deltaX < -RADIUS/2) {
direction = "left";
} else if (deltaX > RADIUS/2) {
direction = "right";
}
joystickMoveListener.onJoystickMove(direction);
return true;
}
@Override
public void onLongPress(MotionEvent e) {
// Do nothing
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// Do nothing
return true;
}
@Override
public void onShowPress(MotionEvent e) {
// Do nothing
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
}
//addOnGlobalLayoutListener()是ViewTreeObserver的一个方法,用于添加一个OnGlobalLayoutListener监听器来监听View的状态变化。
//
//当View的布局发生变化时,addOnGlobalLayoutListener()方法会在ViewTreeObserver的消息队列中添加一个消息,告知系统需要执行一个任务。这个任务就是在View的布局完成后回调OnGlobalLayoutListener的onGlobalLayout()方法,让开发者可以在其中获取View的尺寸、位置等信息,并进行相应的操作。
//
//添加OnGlobalLayoutListener后,当View的布局发生变化时,onGlobalLayout()方法会被调用。因此,我们可以在onGlobalLayout()方法中获取View的尺寸、位置等信息,以便在正确的时机对View进行操作。
//
//需要注意的是,当View的布局发生变化时,OnGlobalLayoutListener会被调用多次,因此在使用addOnGlobalLayoutListener()方法时,应该注意避免重复计算或重复操作。可以使用removeOnGlobalLayoutListener()方法来移除OnGlobalLayoutListener,避免重复回调。
//GestureDetector.OnGestureListener是Android中的一个接口,用于监听手势事件,如单击、双击、滑动、长按、快速滑动等。
//
//GestureDetector是一个用于监听手势事件的工具类,它可以通过GestureDetector.OnGestureListener接口来监听各种手势事件。当用户触发手势事件时,GestureDetector会回调对应的方法,以便开发者可以对事件做出响应。
//
//GestureDetector.OnGestureListener接口中定义了一些方法,包括:
//
//onDown(MotionEvent e):手指按下时触发的事件;
//onShowPress(MotionEvent e):手指按下后一段时间后触发的事件,例如长按;
//onSingleTapUp(MotionEvent e):手指轻轻点击屏幕后立刻抬起时触发的事件;
//onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY):手指在屏幕上滑动时触发的事件;
//onLongPress(MotionEvent e):手指长按屏幕时触发的事件;
//onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY):手指在屏幕上快速滑动时触发的事件。
//开发者可以实现GestureDetector.OnGestureListener接口,重写对应的方法,以便监听手势事件并进行相应的处理。使用GestureDetector可以使我们的应用程序更加易用和直观,提升用户体验。
简单的说一下,
1,获取中心点与控件xy的初始位置,中心点的位置通过分割帧布局计算出来
2,实现GestureDetector.OnGestureListener的所有方法,并且在onScroll方法中获取到移动方向并且传递到接口,由于明确的划定了移动范围无论手势如何延长,控件始终在规定的布局中