初步多点实现

This commit is contained in:
lihongwei 2025-04-25 16:09:30 +08:00
parent 84e4871838
commit 3f6fb462ee
8 changed files with 525 additions and 200 deletions

View File

@ -27,7 +27,6 @@
</intent-filter>
</activity>
<!-- 无障碍服务 -->
<service
android:name=".AutoClickService"
android:enabled="true"
@ -41,7 +40,6 @@
android:resource="@xml/accessibility_service_config" />
</service>
<!-- 前台服务 -->
<service
android:name=".ForegroundService"
android:enabled="true"

View File

@ -15,10 +15,18 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.auto.autoclicker.util.ScreenUtils;
import com.auto.autoclicker.util.ViewUtils;
import java.util.ArrayList;
import java.util.List;
public class AutoClickService extends AccessibilityService {
private final List<Point> multiClickPoints = new ArrayList<>();
private static final String TAG = "AutoClickService";
private static AutoClickService instance;
private final List<Point> multiClickPositions = new ArrayList<>();
private int currentClickIndex = 0;
private static final long MIN_CLICK_INTERVAL = 40;
private static final long MAX_CLICK_INTERVAL = 10000;
private static final int MIN_CLICK_DURATION = 50;
@ -44,6 +52,10 @@ public class AutoClickService extends AccessibilityService {
logDebug("屏幕尺寸: " + screenWidth + "x" + screenHeight);
}
public static AutoClickService getInstance() {
return instance;
}
@Override
public void onServiceConnected() {
super.onServiceConnected();
@ -56,25 +68,17 @@ public class AutoClickService extends AccessibilityService {
logDebug("无障碍服务中断");
}
@Override
public void onDestroy() {
super.onDestroy();
instance = null;
stopClicking();
Intent intent = new Intent("com.auto.autoclicker.SERVICE_DESTROYED");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
logDebug("无障碍服务已销毁");
}
public static AutoClickService getInstance() {
return instance;
}
public void setClickPosition(float x, float y) {
Point constrained = ViewUtils.constrainToScreen(x, y, screenWidth, screenHeight);
this.clickX = constrained.x;
this.clickY = constrained.y;
logDebug("设置点击位置: 原始 (" + x + ", " + y + ") -> 修正 (" + clickX + ", " + clickY + ")");
logDebug("设置单点点击位置: 原始 (" + x + ", " + y + ") -> 修正 (" + clickX + ", " + clickY + ")");
}
public void setMultiClickPositions(List<Point> positions) {
this.multiClickPositions.clear();
this.multiClickPositions.addAll(positions);
logDebug("设置多点点击位置: " + positions.size() + " 个点");
}
public void setClickInterval(long interval) {
@ -97,7 +101,12 @@ public class AutoClickService extends AccessibilityService {
public void startClicking() {
if (!isClicking) {
if (multiClickPositions.isEmpty() && (clickX == 0 && clickY == 0)) {
Log.w(TAG, "无有效点击位置,忽略开始点击");
return;
}
isClicking = true;
currentClickIndex = 0;
logDebug("开始自动点击");
performClick();
}
@ -121,11 +130,22 @@ public class AutoClickService extends AccessibilityService {
return;
}
logDebug("执行点击: (" + clickX + ", " + clickY + ")");
// 决定使用单点还是多点点击
Point clickPoint;
if (!multiClickPositions.isEmpty()) {
// 多点点击模式
clickPoint = multiClickPositions.get(currentClickIndex);
currentClickIndex = (currentClickIndex + 1) % multiClickPositions.size();
} else {
// 单点点击模式
clickPoint = new Point(clickX, clickY);
}
logDebug("执行点击: (" + clickPoint.x + ", " + clickPoint.y + ")");
flashTouchFeedback();
Path path = new Path();
path.moveTo(clickX, clickY);
path.moveTo(clickPoint.x, clickPoint.y);
GestureDescription.StrokeDescription stroke =
new GestureDescription.StrokeDescription(path, 0, clickDuration);
GestureDescription gesture =
@ -162,4 +182,14 @@ public class AutoClickService extends AccessibilityService {
private void logDebug(String message) {
Log.d(TAG, message);
}
@Override
public void onDestroy() {
super.onDestroy();
instance = null;
stopClicking();
Intent intent = new Intent("com.auto.autoclicker.SERVICE_DESTROYED");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
logDebug("无障碍服务已销毁");
}
}

View File

@ -2,7 +2,6 @@ package com.auto.autoclicker;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@ -24,27 +23,45 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.auto.autoclicker.util.PrefUtils;
import com.auto.autoclicker.util.ScreenUtils;
import com.auto.autoclicker.util.ViewUtils;
import java.util.ArrayList;
import java.util.List;
public class FloatingViewManager {
private static final String TAG = "FloatingViewManager";
private static final long DEBOUNCE_INTERVAL = 500;
private final Context context;
private static final long DEBOUNCE_INTERVAL = 500;
private final WindowManager windowManager;
private View touchPointView;
private LinearLayout controlBarView;
private WindowManager.LayoutParams touchPointParams;
private WindowManager.LayoutParams controlBarParams;
private final List<View> moreTouchPointViews = new ArrayList<>();
private final List<WindowManager.LayoutParams> moreTouchPointParams = new ArrayList<>();
private View singleTouchPointView;
private WindowManager.LayoutParams singleTouchPointParams;
private LinearLayout singleControlBarView;
private LinearLayout moreControlBarView;
private WindowManager.LayoutParams singleControlBarParams;
private WindowManager.LayoutParams moreControlBarParams;
private float touchPointX = 500;
private float touchPointY = 500;
private boolean isClicking = false;
private boolean isFloatingViewsShown = false;
private long lastToggleTime = 0;
private final int screenWidth;
private final int screenHeight;
@ -58,47 +75,61 @@ public class FloatingViewManager {
logDebug("屏幕尺寸: " + screenWidth + "x" + screenHeight);
}
public void showFloatingViews() throws SecurityException {
private void registerBroadcast(Context context) {
LocalBroadcastManager.getInstance(context).registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if ("com.auto.autoclicker.FLASH_TOUCH_POINT".equals(intent.getAction())) {
flashTouchPoint();
}
}
}, new IntentFilter("com.auto.autoclicker.FLASH_TOUCH_POINT"));
}
public void showFloatingViews(int mode) throws SecurityException {
if (!Settings.canDrawOverlays(context)) {
throw new SecurityException("需要悬浮窗权限");
}
if (isFloatingViewsShown) {
logDebug("悬浮窗已存在,清理后重新创建");
removeFloatingViews();
removeFloatingViews();
if (mode == 1) {
initSingleTouchPointView();
initSingleControlBar();
if (singleTouchPointView != null) {
windowManager.addView(singleTouchPointView, singleTouchPointParams);
}
if (singleControlBarView != null) {
windowManager.addView(singleControlBarView, singleControlBarParams);
}
} else if (mode == 2) {
initMoreTouchPointView();
initMoreControlBar();
if (moreTouchPointViews.size() == moreTouchPointParams.size()) {
for (int i = 0; i < moreTouchPointViews.size(); i++) {
windowManager.addView(moreTouchPointViews.get(i), moreTouchPointParams.get(i));
}
} else {
Log.e(TAG, "触摸点与布局参数数量不一致");
}
if (moreControlBarView != null) {
windowManager.addView(moreControlBarView, moreControlBarParams);
}
}
initializeTouchPointView();
initializeControlBarView();
windowManager.addView(touchPointView, touchPointParams);
windowManager.addView(controlBarView, controlBarParams);
isFloatingViewsShown = true;
logDebug("悬浮窗已添加");
logDebug("悬浮窗已添加,模式 = " + mode);
}
public void removeFloatingViews() {
try {
if (touchPointView != null) {
windowManager.removeView(touchPointView);
touchPointView = null;
}
if (controlBarView != null) {
windowManager.removeView(controlBarView);
controlBarView = null;
}
isFloatingViewsShown = false;
logDebug("悬浮窗已移除");
} catch (Exception e) {
Log.e(TAG, "移除悬浮窗失败", e);
isFloatingViewsShown = false;
}
}
private void initializeTouchPointView() {
touchPointView = new View(context);
touchPointView.setBackgroundResource(R.drawable.un_touch_point);
touchPointParams = new WindowManager.LayoutParams(
private void initSingleTouchPointView() {
singleTouchPointView = new View(context);
singleTouchPointView.setBackgroundResource(R.drawable.un_touch_point);
singleTouchPointParams = new WindowManager.LayoutParams(
100, 100,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
@ -107,11 +138,11 @@ public class FloatingViewManager {
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
);
touchPointParams.gravity = Gravity.TOP | Gravity.START;
touchPointParams.x = (int) touchPointX;
touchPointParams.y = (int) touchPointY;
singleTouchPointParams.gravity = Gravity.TOP | Gravity.START;
singleTouchPointParams.x = (int) touchPointX;
singleTouchPointParams.y = (int) touchPointY;
touchPointView.setOnTouchListener(new View.OnTouchListener() {
singleTouchPointView.setOnTouchListener(new View.OnTouchListener() {
private float lastX, lastY;
private float paramX, paramY;
@ -121,8 +152,8 @@ public class FloatingViewManager {
case MotionEvent.ACTION_DOWN:
lastX = event.getRawX();
lastY = event.getRawY();
paramX = touchPointParams.x;
paramY = touchPointParams.y;
paramX = singleTouchPointParams.x;
paramY = singleTouchPointParams.y;
break;
case MotionEvent.ACTION_MOVE:
float dx = event.getRawX() - lastX;
@ -131,7 +162,7 @@ public class FloatingViewManager {
AutoClickService service = AutoClickService.getInstance();
if (service != null) {
service.setClickPosition(touchPointX + 50, touchPointY + 50);
logDebug("触摸点移动到: (" + touchPointX + ", " + touchPointY + ")");
logDebug("单点触摸点移动到: (" + touchPointX + ", " + touchPointY + ")");
}
break;
}
@ -140,10 +171,16 @@ public class FloatingViewManager {
});
}
private void initMoreTouchPointView() {
moreTouchPointViews.clear();
moreTouchPointParams.clear();
addNewTouchPoint(100, 500);
}
@SuppressLint("ClickableViewAccessibility")
private void initializeControlBarView() {
controlBarView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.single_control_bar, null);
controlBarParams = new WindowManager.LayoutParams(
private void initSingleControlBar() {
singleControlBarView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.single_control_bar, null);
singleControlBarParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
@ -153,11 +190,11 @@ public class FloatingViewManager {
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
);
controlBarParams.gravity = Gravity.TOP | Gravity.START;
controlBarParams.x = 0;
controlBarParams.y = 200;
singleControlBarParams.gravity = Gravity.TOP | Gravity.START;
singleControlBarParams.x = 0;
singleControlBarParams.y = 200;
controlBarView.setOnTouchListener(new View.OnTouchListener() {
singleControlBarView.setOnTouchListener(new View.OnTouchListener() {
private float lastX, lastY;
private float paramX, paramY;
@ -167,13 +204,13 @@ public class FloatingViewManager {
case MotionEvent.ACTION_DOWN:
lastX = event.getRawX();
lastY = event.getRawY();
paramX = controlBarParams.x;
paramY = controlBarParams.y;
paramX = singleControlBarParams.x;
paramY = singleControlBarParams.y;
return true;
case MotionEvent.ACTION_MOVE:
float dx = event.getRawX() - lastX;
float dy = event.getRawY() - lastY;
updateControlBarPosition(paramX, paramY, dx, dy);
updateSingleControlBarPosition(paramX, paramY, dx, dy);
return true;
case MotionEvent.ACTION_UP:
v.performClick();
@ -183,84 +220,304 @@ public class FloatingViewManager {
}
});
ImageView play = controlBarView.findViewById(R.id.play_button);
ImageView close = controlBarView.findViewById(R.id.close_button);
ImageView setting = controlBarView.findViewById(R.id.settings_button);
play.setOnClickListener(v -> {
if (!isDebounced()) return;
AutoClickService service = AutoClickService.getInstance();
if (service == null) {
Log.e(TAG, "AutoClickService 未初始化");
Toast.makeText(context, "请在设置中启用无障碍服务", Toast.LENGTH_LONG).show();
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return;
}
if (isClicking) {
service.stopClicking();
play.setBackgroundResource(R.drawable.play);
touchPointView.setBackgroundResource(R.drawable.un_touch_point);
Toast.makeText(context, "停止自动点击", Toast.LENGTH_SHORT).show();
} else {
service.setClickPosition(touchPointX, touchPointY);
service.startClicking();
play.setBackgroundResource(R.drawable.play);
touchPointView.setBackgroundResource(R.drawable.touch_point);
Toast.makeText(context, "开始自动点击", Toast.LENGTH_SHORT).show();
}
isClicking = !isClicking;
logDebug("服务是否点击中: " + service.isClicking());
});
close.setOnClickListener(v -> {
AutoClickService service = AutoClickService.getInstance();
if (service != null) {
service.stopClicking();
} else {
logDebug("AutoClickService 未初始化");
Toast.makeText(context, "请启用无障碍服务", Toast.LENGTH_SHORT).show();
}
touchPointView.setBackgroundResource(R.drawable.un_touch_point);
removeFloatingViews();
context.stopService(new Intent(context, ForegroundService.class));
PrefUtils.setFloatingShown(context, false);
Intent intent = new Intent("com.auto.autoclicker.FLOATING_WINDOW_STATE_CHANGED");
intent.putExtra("isShown", false);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
});
ImageView play = singleControlBarView.findViewById(R.id.play_button);
ImageView close = singleControlBarView.findViewById(R.id.close_button);
ImageView setting = singleControlBarView.findViewById(R.id.settings_button);
play.setOnClickListener(v -> toggleSingleClicking(play));
close.setOnClickListener(v -> closeFloatingViews());
setting.setOnClickListener(v -> showInputDialog());
}
@SuppressLint("ClickableViewAccessibility")
private void initMoreControlBar() {
moreControlBarView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.more_control_bar, null);
moreControlBarParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
);
moreControlBarParams.gravity = Gravity.TOP | Gravity.START;
moreControlBarParams.x = 0;
moreControlBarParams.y = 200;
moreControlBarView.setOnTouchListener(new View.OnTouchListener() {
private float lastX, lastY;
private float paramX, paramY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = event.getRawX();
lastY = event.getRawY();
paramX = moreControlBarParams.x;
paramY = moreControlBarParams.y;
return true;
case MotionEvent.ACTION_MOVE:
float dx = event.getRawX() - lastX;
float dy = event.getRawY() - lastY;
updateMoreControlBarPosition(paramX, paramY, dx, dy);
return true;
case MotionEvent.ACTION_UP:
v.performClick();
return true;
}
return false;
}
});
ImageView addButton = moreControlBarView.findViewById(R.id.add_button);
ImageView removeButton = moreControlBarView.findViewById(R.id.remove_button);
ImageView playButton = moreControlBarView.findViewById(R.id.play_button);
ImageView closeButton = moreControlBarView.findViewById(R.id.close_button);
ImageView settingButton = moreControlBarView.findViewById(R.id.settings_button);
addButton.setOnClickListener(v -> addNewTouchPoint(100 + moreTouchPointViews.size() * 120, 500));
removeButton.setOnClickListener(v -> removeLastTouchPoint());
playButton.setOnClickListener(v -> toggleMoreClicking(playButton));
closeButton.setOnClickListener(v -> closeFloatingViews());
settingButton.setOnClickListener(v -> showInputDialog());
}
private void toggleSingleClicking(ImageView play) {
if (isDebounced()) return;
AutoClickService service = AutoClickService.getInstance();
if (service == null) {
Log.e(TAG, "AutoClickService 未初始化");
Toast.makeText(context, "请在设置中启用无障碍服务", Toast.LENGTH_LONG).show();
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return;
}
if (isClicking) {
service.stopClicking();
play.setBackgroundResource(R.drawable.play);
singleTouchPointView.setBackgroundResource(R.drawable.un_touch_point);
Toast.makeText(context, "停止自动点击", Toast.LENGTH_SHORT).show();
} else {
service.setClickPosition(touchPointX + 50, touchPointY + 50);
service.startClicking();
play.setBackgroundResource(R.drawable.pause);
singleTouchPointView.setBackgroundResource(R.drawable.touch_point);
Toast.makeText(context, "开始自动点击", Toast.LENGTH_SHORT).show();
}
isClicking = !isClicking;
logDebug("单点服务是否点击中: " + service.isClicking());
}
private void toggleMoreClicking(ImageView play) {
if (isDebounced()) return;
AutoClickService service = AutoClickService.getInstance();
if (service == null) {
Log.e(TAG, "AutoClickService 未初始化");
Toast.makeText(context, "请在设置中启用无障碍服务", Toast.LENGTH_LONG).show();
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return;
}
if (isClicking) {
service.stopClicking();
play.setBackgroundResource(R.drawable.play);
for (View point : moreTouchPointViews) {
point.setBackgroundResource(R.drawable.un_touch_point);
}
Toast.makeText(context, "停止多点自动点击", Toast.LENGTH_SHORT).show();
} else {
List<Point> positions = new ArrayList<>();
for (WindowManager.LayoutParams params : moreTouchPointParams) {
positions.add(new Point(params.x + 50, params.y + 50));
}
service.setMultiClickPositions(positions);
service.startClicking();
play.setBackgroundResource(R.drawable.pause);
for (View point : moreTouchPointViews) {
point.setBackgroundResource(R.drawable.touch_point);
}
Toast.makeText(context, "开始多点自动点击", Toast.LENGTH_SHORT).show();
}
isClicking = !isClicking;
logDebug("多点服务是否点击中: " + service.isClicking());
}
private void closeFloatingViews() {
AutoClickService service = AutoClickService.getInstance();
if (service != null) {
service.stopClicking();
} else {
logDebug("AutoClickService 未初始化");
Toast.makeText(context, "请启用无障碍服务", Toast.LENGTH_SHORT).show();
}
if (singleTouchPointView != null) {
singleTouchPointView.setBackgroundResource(R.drawable.un_touch_point);
}
for (View point : moreTouchPointViews) {
point.setBackgroundResource(R.drawable.un_touch_point);
}
removeFloatingViews();
context.stopService(new Intent(context, ForegroundService.class));
PrefUtils.setFloatingShown(context, 0);
Intent intent = new Intent("com.auto.autoclicker.FLOATING_WINDOW_STATE_CHANGED");
intent.putExtra("isShown", false);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
public void removeFloatingViews() {
try {
// 移除单点触摸视图
if (singleTouchPointView != null) {
try {
windowManager.removeView(singleTouchPointView);
} catch (Exception e) {
Log.w(TAG, "移除 singleTouchPointView 失败", e);
}
singleTouchPointView = null;
}
// 移除多点触摸视图
List<View> pointsToRemove = new ArrayList<>(moreTouchPointViews);
for (View point : pointsToRemove) {
try {
windowManager.removeView(point);
moreTouchPointViews.remove(point); // 成功移除后从列表中删除
} catch (Exception e) {
Log.w(TAG, "移除 multiTouchPoint 失败", e);
}
}
moreTouchPointParams.clear(); // 清空参数列表
// 移除单点控制栏
if (singleControlBarView != null) {
try {
windowManager.removeView(singleControlBarView);
} catch (Exception e) {
Log.w(TAG, "移除 singleControlBarView 失败", e);
}
singleControlBarView = null;
}
// 移除多点控制栏
if (moreControlBarView != null) {
try {
windowManager.removeView(moreControlBarView);
} catch (Exception e) {
Log.w(TAG, "移除 moreControlBarView 失败", e);
}
moreControlBarView = null;
}
isFloatingViewsShown = false;
isClicking = false;
logDebug("悬浮窗已移除");
} catch (Exception e) {
Log.e(TAG, "移除悬浮窗失败", e);
isFloatingViewsShown = false;
isClicking = false;
}
}
private void addNewTouchPoint(int x, int y) {
View point = new View(context);
point.setBackgroundResource(R.drawable.un_touch_point);
WindowManager.LayoutParams params = createTouchPointLayoutParams(x, y);
windowManager.addView(point, params);
moreTouchPointViews.add(point);
moreTouchPointParams.add(params);
point.setOnTouchListener(new View.OnTouchListener() {
private float lastX, lastY, startX, startY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = event.getRawX();
lastY = event.getRawY();
startX = params.x;
startY = params.y;
break;
case MotionEvent.ACTION_MOVE:
float dx = event.getRawX() - lastX;
float dy = event.getRawY() - lastY;
params.x = (int) (startX + dx);
params.y = (int) (startY + dy);
windowManager.updateViewLayout(v, params);
logDebug("多点触摸点移动到: (" + params.x + ", " + params.y + ")");
break;
}
return true;
}
});
}
private void removeLastTouchPoint() {
if (moreTouchPointViews.size() <= 1) {
Toast.makeText(context, "至少保留一个触摸点", Toast.LENGTH_SHORT).show();
return;
}
int lastIndex = moreTouchPointViews.size() - 1;
View last = moreTouchPointViews.remove(lastIndex);
moreTouchPointParams.remove(lastIndex);
windowManager.removeView(last);
}
private WindowManager.LayoutParams createTouchPointLayoutParams(int x, int y) {
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
100, 100,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
);
params.gravity = Gravity.TOP | Gravity.START;
params.x = x;
params.y = y;
return params;
}
private void updateTouchPointPosition(float paramX, float paramY, float dx, float dy) {
Point constrainedPoint = ViewUtils.constrainToScreen(
paramX + dx, paramY + dy, screenWidth - touchPointView.getWidth(), screenHeight - touchPointView.getHeight());
touchPointParams.x = constrainedPoint.x;
touchPointParams.y = constrainedPoint.y;
touchPointX = touchPointParams.x;
touchPointY = touchPointParams.y;
windowManager.updateViewLayout(touchPointView, touchPointParams);
paramX + dx, paramY + dy, screenWidth - singleTouchPointView.getWidth(), screenHeight - singleTouchPointView.getHeight());
singleTouchPointParams.x = constrainedPoint.x;
singleTouchPointParams.y = constrainedPoint.y;
touchPointX = singleTouchPointParams.x;
touchPointY = singleTouchPointParams.y;
windowManager.updateViewLayout(singleTouchPointView, singleTouchPointParams);
}
private void updateControlBarPosition(float paramX, float paramY, float dx, float dy) {
private void updateSingleControlBarPosition(float paramX, float paramY, float dx, float dy) {
Point constrainedPoint = ViewUtils.constrainToScreen(
paramX + dx, paramY + dy, screenWidth - controlBarView.getWidth(), screenHeight - controlBarView.getHeight());
controlBarParams.x = constrainedPoint.x;
controlBarParams.y = constrainedPoint.y;
windowManager.updateViewLayout(controlBarView, controlBarParams);
paramX + dx, paramY + dy, screenWidth - singleControlBarView.getWidth(), screenHeight - singleControlBarView.getHeight());
singleControlBarParams.x = constrainedPoint.x;
singleControlBarParams.y = constrainedPoint.y;
windowManager.updateViewLayout(singleControlBarView, singleControlBarParams);
}
private void updateMoreControlBarPosition(float paramX, float paramY, float dx, float dy) {
Point constrainedPoint = ViewUtils.constrainToScreen(
paramX + dx, paramY + dy, screenWidth - moreControlBarView.getWidth(), screenHeight - moreControlBarView.getHeight());
moreControlBarParams.x = constrainedPoint.x;
moreControlBarParams.y = constrainedPoint.y;
windowManager.updateViewLayout(moreControlBarView, moreControlBarParams);
}
public void showInputDialog() {
@ -303,7 +560,6 @@ public class FloatingViewManager {
dialog.show();
// 强制显示软键盘
input.requestFocus();
input.post(() -> {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
@ -314,41 +570,42 @@ public class FloatingViewManager {
}
public void flashTouchPoint() {
if (touchPointView == null) return;
touchPointView.animate()
.alpha(0.3f)
.setDuration(100)
.withEndAction(() ->
touchPointView.animate()
.alpha(1.0f)
.setDuration(100)
.start()
)
.start();
}
private void registerBroadcast(Context context) {
LocalBroadcastManager.getInstance(context).registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if ("com.auto.autoclicker.FLASH_TOUCH_POINT".equals(intent.getAction())) {
flashTouchPoint();
}
}
}, new IntentFilter("com.auto.autoclicker.FLASH_TOUCH_POINT"));
if (singleTouchPointView != null) {
singleTouchPointView.animate()
.alpha(0.3f)
.setDuration(100)
.withEndAction(() ->
singleTouchPointView.animate()
.alpha(1.0f)
.setDuration(100)
.start()
)
.start();
}
for (View point : moreTouchPointViews) {
point.animate()
.alpha(0.3f)
.setDuration(100)
.withEndAction(() ->
point.animate()
.alpha(1.0f)
.setDuration(100)
.start()
)
.start();
}
}
private boolean isDebounced() {
long currentTime = System.currentTimeMillis();
if (currentTime - lastToggleTime < DEBOUNCE_INTERVAL) {
return false;
return true;
}
lastToggleTime = currentTime;
return true;
return false;
}
private void logDebug(String message) {
Log.d(TAG, message);
}
}
}

View File

@ -1,5 +1,7 @@
package com.auto.autoclicker;
import static com.auto.autoclicker.MainActivity.FLOATING_SINGLE;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@ -33,11 +35,10 @@ public class ForegroundService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE
);
int type = intent != null ? intent.getIntExtra("FLOATING_TYPE", FLOATING_SINGLE) : FLOATING_SINGLE;
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("自动点击运行中")
.setContentText("自动点击正在后台运行")
@ -50,21 +51,20 @@ public class ForegroundService extends Service {
if (floatingViewManager != null && !isFloatingViewShown) {
try {
floatingViewManager.showFloatingViews();
floatingViewManager.showFloatingViews(type);
isFloatingViewShown = true;
Log.d(TAG, "悬浮窗已显示");
} catch (SecurityException e) {
Log.e(TAG, "未授予悬浮窗权限", e);
stopSelf();
}
} else if (floatingViewManager == null) {
Log.e(TAG, "FloatingViewManager 未初始化");
stopSelf();
}
return START_STICKY;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(

View File

@ -23,17 +23,27 @@ import com.auto.autoclicker.util.PrefUtils;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static final String ACTION_FLOATING_STATE_CHANGED = "com.auto.autoclicker.FLOATING_WINDOW_STATE_CHANGED";
private ActivityMainBinding binding;
public static final int FLOATING_NONE = 0;
public static final int FLOATING_SINGLE = 1;
public static final int FLOATING_MULTI = 2;
private Toast debounceToast;
private ActivityResultLauncher<Intent> permissionLauncher;
private long lastClickTime = 0;
private static final long DEBOUNCE_INTERVAL = 500;
private boolean isFloatingShown;
private static final String ACTION_FLOATING_STATE_CHANGED = "com.auto.autoclicker.FLOATING_WINDOW_STATE_CHANGED";
private int isFloatingShown;
private final BroadcastReceiver floatingReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
isFloatingShown = intent.getBooleanExtra("isShown", false);
isFloatingShown = intent.getIntExtra("isShown", 0);
updateButtonText();
}
};
@ -45,13 +55,14 @@ public class MainActivity extends AppCompatActivity {
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
isFloatingShown = PrefUtils.isFloatingShown(this);
isFloatingShown = PrefUtils.getFloatingShown(this);
updateButtonText();
LocalBroadcastManager.getInstance(this).registerReceiver(
floatingReceiver, new IntentFilter(ACTION_FLOATING_STATE_CHANGED));
binding.singleButton.setOnClickListener(v -> onToggleFloatingWindowClicked());
binding.singleButton.setOnClickListener(v -> onToggleFloatingWindowClicked(FLOATING_SINGLE));
binding.moreButton.setOnClickListener(v -> onToggleFloatingWindowClicked(FLOATING_MULTI));
permissionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> checkPermissions());
@ -120,24 +131,24 @@ public class MainActivity extends AppCompatActivity {
}
private void syncServiceState() {
isFloatingShown = isServiceRunning();
isFloatingShown = getFloatingMode();
updateButtonText();
}
private boolean isServiceRunning() {
private int getFloatingMode() {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (ForegroundService.class.getName().equals(service.service.getClassName())) {
return true;
return PrefUtils.getFloatingShown(this);
}
}
return false;
return 0;
}
public void onToggleFloatingWindowClicked() {
public void onToggleFloatingWindowClicked(int requestedType) {
long currentTime = System.currentTimeMillis();
if (currentTime - lastClickTime < DEBOUNCE_INTERVAL) {
Toast.makeText(this, "点击太频繁", Toast.LENGTH_LONG).show();
showDebounceToast();
return;
}
lastClickTime = currentTime;
@ -148,20 +159,28 @@ public class MainActivity extends AppCompatActivity {
return;
}
if (isFloatingShown) {
if (isFloatingShown != FLOATING_NONE && isFloatingShown != requestedType) {
Toast.makeText(this, "已显示其他悬浮窗,请先关闭", Toast.LENGTH_SHORT).show();
return;
}
if (isFloatingShown == requestedType) {
stopService(new Intent(this, ForegroundService.class));
AutoClickService.getInstance().stopClicking();
isFloatingShown = false;
isFloatingShown = FLOATING_NONE;
logDebug("悬浮窗已经隐藏");
} else {
Intent serviceIntent = new Intent(this, ForegroundService.class);
serviceIntent.putExtra("FLOATING_TYPE", requestedType);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent);
} else {
startService(serviceIntent);
}
isFloatingShown = true;
logDebug("悬浮窗已经显示");
isFloatingShown = requestedType;
logDebug("悬浮窗已显示: " + requestedType);
}
PrefUtils.setFloatingShown(this, isFloatingShown);
@ -169,14 +188,14 @@ public class MainActivity extends AppCompatActivity {
updateButtonText();
}
private void broadcastFloatingState(boolean isShown) {
private void broadcastFloatingState(int isShown) {
Intent intent = new Intent(ACTION_FLOATING_STATE_CHANGED);
intent.putExtra("isShown", isShown);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
private void updateButtonText() {
binding.singleButton.setText(isFloatingShown ? "隐藏悬浮窗" : "显示悬浮窗");
binding.singleButton.setText(isFloatingShown == 1 ? "隐藏悬浮窗" : "显示悬浮窗");
}
@Override
@ -185,6 +204,15 @@ public class MainActivity extends AppCompatActivity {
LocalBroadcastManager.getInstance(this).unregisterReceiver(floatingReceiver);
}
private void showDebounceToast() {
if (debounceToast == null) {
debounceToast = Toast.makeText(this, "点击太频繁", Toast.LENGTH_SHORT);
} else {
debounceToast.setText("点击太频繁");
}
debounceToast.show();
}
private void logDebug(String message) {
Log.d(TAG, message);
}

View File

@ -6,13 +6,13 @@ public class PrefUtils {
private static final String PREF_NAME = "app_prefs";
private static final String KEY_FLOATING_SHOWN = "floating_window_shown";
public static void setFloatingShown(Context context, boolean shown) {
public static void setFloatingShown(Context context, int shown) {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit().putBoolean(KEY_FLOATING_SHOWN, shown).apply();
.edit().putInt(KEY_FLOATING_SHOWN, shown).apply();
}
public static boolean isFloatingShown(Context context) {
public static int getFloatingShown(Context context) {
return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.getBoolean(KEY_FLOATING_SHOWN, false);
.getInt(KEY_FLOATING_SHOWN, 0);
}
}

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M512,42.7a469.3,469.3 0,1 0,469.3 469.3A469.3,469.3 0,0 0,512 42.7zM512,906.7a394.7,394.7 0,1 1,394.7 -394.7,395.1 395.1,0 0,1 -394.7,394.7z"/>
<path
android:fillColor="#FF000000"
android:pathData="M427.2,332.1a37.3,37.3 0,0 0,-37.3 37.3v285.1a37.3,37.3 0,0 0,74.7 0V369.4a37.3,37.3 0,0 0,-37.3 -37.3zM596.8,332.1a37.3,37.3 0,0 0,-37.3 37.3v285.1a37.3,37.3 0,1 0,74.7 0V369.4a37.3,37.3 0,0 0,-37.3 -37.3z"/>
</vector>

View File

@ -16,14 +16,14 @@
android:src="@drawable/play" />
<ImageView
android:id="@+id/file_button"
android:id="@+id/add_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="8dp"
android:src="@drawable/save" />
<ImageView
android:id="@+id/eye_button"
android:id="@+id/remove_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="8dp"