初步多点实现

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> </intent-filter>
</activity> </activity>
<!-- 无障碍服务 -->
<service <service
android:name=".AutoClickService" android:name=".AutoClickService"
android:enabled="true" android:enabled="true"
@ -41,7 +40,6 @@
android:resource="@xml/accessibility_service_config" /> android:resource="@xml/accessibility_service_config" />
</service> </service>
<!-- 前台服务 -->
<service <service
android:name=".ForegroundService" android:name=".ForegroundService"
android:enabled="true" 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.ScreenUtils;
import com.auto.autoclicker.util.ViewUtils; import com.auto.autoclicker.util.ViewUtils;
import java.util.ArrayList;
import java.util.List;
public class AutoClickService extends AccessibilityService { public class AutoClickService extends AccessibilityService {
private final List<Point> multiClickPoints = new ArrayList<>();
private static final String TAG = "AutoClickService"; private static final String TAG = "AutoClickService";
private static AutoClickService instance; 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 MIN_CLICK_INTERVAL = 40;
private static final long MAX_CLICK_INTERVAL = 10000; private static final long MAX_CLICK_INTERVAL = 10000;
private static final int MIN_CLICK_DURATION = 50; private static final int MIN_CLICK_DURATION = 50;
@ -44,6 +52,10 @@ public class AutoClickService extends AccessibilityService {
logDebug("屏幕尺寸: " + screenWidth + "x" + screenHeight); logDebug("屏幕尺寸: " + screenWidth + "x" + screenHeight);
} }
public static AutoClickService getInstance() {
return instance;
}
@Override @Override
public void onServiceConnected() { public void onServiceConnected() {
super.onServiceConnected(); super.onServiceConnected();
@ -56,25 +68,17 @@ public class AutoClickService extends AccessibilityService {
logDebug("无障碍服务中断"); 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) { public void setClickPosition(float x, float y) {
Point constrained = ViewUtils.constrainToScreen(x, y, screenWidth, screenHeight); Point constrained = ViewUtils.constrainToScreen(x, y, screenWidth, screenHeight);
this.clickX = constrained.x; this.clickX = constrained.x;
this.clickY = constrained.y; 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) { public void setClickInterval(long interval) {
@ -97,7 +101,12 @@ public class AutoClickService extends AccessibilityService {
public void startClicking() { public void startClicking() {
if (!isClicking) { if (!isClicking) {
if (multiClickPositions.isEmpty() && (clickX == 0 && clickY == 0)) {
Log.w(TAG, "无有效点击位置,忽略开始点击");
return;
}
isClicking = true; isClicking = true;
currentClickIndex = 0;
logDebug("开始自动点击"); logDebug("开始自动点击");
performClick(); performClick();
} }
@ -121,11 +130,22 @@ public class AutoClickService extends AccessibilityService {
return; 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(); flashTouchFeedback();
Path path = new Path(); Path path = new Path();
path.moveTo(clickX, clickY); path.moveTo(clickPoint.x, clickPoint.y);
GestureDescription.StrokeDescription stroke = GestureDescription.StrokeDescription stroke =
new GestureDescription.StrokeDescription(path, 0, clickDuration); new GestureDescription.StrokeDescription(path, 0, clickDuration);
GestureDescription gesture = GestureDescription gesture =
@ -162,4 +182,14 @@ public class AutoClickService extends AccessibilityService {
private void logDebug(String message) { private void logDebug(String message) {
Log.d(TAG, 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.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -24,27 +23,45 @@ import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.auto.autoclicker.util.PrefUtils; import com.auto.autoclicker.util.PrefUtils;
import com.auto.autoclicker.util.ScreenUtils; import com.auto.autoclicker.util.ScreenUtils;
import com.auto.autoclicker.util.ViewUtils; import com.auto.autoclicker.util.ViewUtils;
import java.util.ArrayList;
import java.util.List;
public class FloatingViewManager { public class FloatingViewManager {
private static final String TAG = "FloatingViewManager"; private static final String TAG = "FloatingViewManager";
private static final long DEBOUNCE_INTERVAL = 500;
private final Context context; private final Context context;
private static final long DEBOUNCE_INTERVAL = 500;
private final WindowManager windowManager; private final WindowManager windowManager;
private View touchPointView;
private LinearLayout controlBarView; private final List<View> moreTouchPointViews = new ArrayList<>();
private WindowManager.LayoutParams touchPointParams; private final List<WindowManager.LayoutParams> moreTouchPointParams = new ArrayList<>();
private WindowManager.LayoutParams controlBarParams;
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 touchPointX = 500;
private float touchPointY = 500; private float touchPointY = 500;
private boolean isClicking = false; private boolean isClicking = false;
private boolean isFloatingViewsShown = false; private boolean isFloatingViewsShown = false;
private long lastToggleTime = 0; private long lastToggleTime = 0;
private final int screenWidth; private final int screenWidth;
private final int screenHeight; private final int screenHeight;
@ -58,47 +75,61 @@ public class FloatingViewManager {
logDebug("屏幕尺寸: " + screenWidth + "x" + screenHeight); 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)) { if (!Settings.canDrawOverlays(context)) {
throw new SecurityException("需要悬浮窗权限"); throw new SecurityException("需要悬浮窗权限");
} }
if (isFloatingViewsShown) { removeFloatingViews();
logDebug("悬浮窗已存在,清理后重新创建");
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; isFloatingViewsShown = true;
logDebug("悬浮窗已添加"); logDebug("悬浮窗已添加,模式 = " + mode);
} }
public void removeFloatingViews() { private void initSingleTouchPointView() {
try { singleTouchPointView = new View(context);
if (touchPointView != null) { singleTouchPointView.setBackgroundResource(R.drawable.un_touch_point);
windowManager.removeView(touchPointView); singleTouchPointParams = new WindowManager.LayoutParams(
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(
100, 100, 100, 100,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
@ -107,11 +138,11 @@ public class FloatingViewManager {
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT PixelFormat.TRANSLUCENT
); );
touchPointParams.gravity = Gravity.TOP | Gravity.START; singleTouchPointParams.gravity = Gravity.TOP | Gravity.START;
touchPointParams.x = (int) touchPointX; singleTouchPointParams.x = (int) touchPointX;
touchPointParams.y = (int) touchPointY; singleTouchPointParams.y = (int) touchPointY;
touchPointView.setOnTouchListener(new View.OnTouchListener() { singleTouchPointView.setOnTouchListener(new View.OnTouchListener() {
private float lastX, lastY; private float lastX, lastY;
private float paramX, paramY; private float paramX, paramY;
@ -121,8 +152,8 @@ public class FloatingViewManager {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
lastX = event.getRawX(); lastX = event.getRawX();
lastY = event.getRawY(); lastY = event.getRawY();
paramX = touchPointParams.x; paramX = singleTouchPointParams.x;
paramY = touchPointParams.y; paramY = singleTouchPointParams.y;
break; break;
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
float dx = event.getRawX() - lastX; float dx = event.getRawX() - lastX;
@ -131,7 +162,7 @@ public class FloatingViewManager {
AutoClickService service = AutoClickService.getInstance(); AutoClickService service = AutoClickService.getInstance();
if (service != null) { if (service != null) {
service.setClickPosition(touchPointX + 50, touchPointY + 50); service.setClickPosition(touchPointX + 50, touchPointY + 50);
logDebug("触摸点移动到: (" + touchPointX + ", " + touchPointY + ")"); logDebug("单点触摸点移动到: (" + touchPointX + ", " + touchPointY + ")");
} }
break; break;
} }
@ -140,10 +171,16 @@ public class FloatingViewManager {
}); });
} }
private void initMoreTouchPointView() {
moreTouchPointViews.clear();
moreTouchPointParams.clear();
addNewTouchPoint(100, 500);
}
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private void initializeControlBarView() { private void initSingleControlBar() {
controlBarView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.single_control_bar, null); singleControlBarView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.single_control_bar, null);
controlBarParams = new WindowManager.LayoutParams( singleControlBarParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
@ -153,11 +190,11 @@ public class FloatingViewManager {
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT PixelFormat.TRANSLUCENT
); );
controlBarParams.gravity = Gravity.TOP | Gravity.START; singleControlBarParams.gravity = Gravity.TOP | Gravity.START;
controlBarParams.x = 0; singleControlBarParams.x = 0;
controlBarParams.y = 200; singleControlBarParams.y = 200;
controlBarView.setOnTouchListener(new View.OnTouchListener() { singleControlBarView.setOnTouchListener(new View.OnTouchListener() {
private float lastX, lastY; private float lastX, lastY;
private float paramX, paramY; private float paramX, paramY;
@ -167,13 +204,13 @@ public class FloatingViewManager {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
lastX = event.getRawX(); lastX = event.getRawX();
lastY = event.getRawY(); lastY = event.getRawY();
paramX = controlBarParams.x; paramX = singleControlBarParams.x;
paramY = controlBarParams.y; paramY = singleControlBarParams.y;
return true; return true;
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
float dx = event.getRawX() - lastX; float dx = event.getRawX() - lastX;
float dy = event.getRawY() - lastY; float dy = event.getRawY() - lastY;
updateControlBarPosition(paramX, paramY, dx, dy); updateSingleControlBarPosition(paramX, paramY, dx, dy);
return true; return true;
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
v.performClick(); v.performClick();
@ -183,84 +220,304 @@ public class FloatingViewManager {
} }
}); });
ImageView play = controlBarView.findViewById(R.id.play_button); ImageView play = singleControlBarView.findViewById(R.id.play_button);
ImageView close = controlBarView.findViewById(R.id.close_button); ImageView close = singleControlBarView.findViewById(R.id.close_button);
ImageView setting = controlBarView.findViewById(R.id.settings_button); ImageView setting = singleControlBarView.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);
});
play.setOnClickListener(v -> toggleSingleClicking(play));
close.setOnClickListener(v -> closeFloatingViews());
setting.setOnClickListener(v -> showInputDialog()); 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) { private void updateTouchPointPosition(float paramX, float paramY, float dx, float dy) {
Point constrainedPoint = ViewUtils.constrainToScreen( Point constrainedPoint = ViewUtils.constrainToScreen(
paramX + dx, paramY + dy, screenWidth - touchPointView.getWidth(), screenHeight - touchPointView.getHeight()); paramX + dx, paramY + dy, screenWidth - singleTouchPointView.getWidth(), screenHeight - singleTouchPointView.getHeight());
touchPointParams.x = constrainedPoint.x; singleTouchPointParams.x = constrainedPoint.x;
touchPointParams.y = constrainedPoint.y; singleTouchPointParams.y = constrainedPoint.y;
touchPointX = touchPointParams.x; touchPointX = singleTouchPointParams.x;
touchPointY = touchPointParams.y; touchPointY = singleTouchPointParams.y;
windowManager.updateViewLayout(touchPointView, touchPointParams); 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( Point constrainedPoint = ViewUtils.constrainToScreen(
paramX + dx, paramY + dy, screenWidth - controlBarView.getWidth(), screenHeight - controlBarView.getHeight()); paramX + dx, paramY + dy, screenWidth - singleControlBarView.getWidth(), screenHeight - singleControlBarView.getHeight());
controlBarParams.x = constrainedPoint.x; singleControlBarParams.x = constrainedPoint.x;
controlBarParams.y = constrainedPoint.y; singleControlBarParams.y = constrainedPoint.y;
windowManager.updateViewLayout(controlBarView, controlBarParams); 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() { public void showInputDialog() {
@ -303,7 +560,6 @@ public class FloatingViewManager {
dialog.show(); dialog.show();
// 强制显示软键盘
input.requestFocus(); input.requestFocus();
input.post(() -> { input.post(() -> {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
@ -314,41 +570,42 @@ public class FloatingViewManager {
} }
public void flashTouchPoint() { public void flashTouchPoint() {
if (touchPointView == null) return; if (singleTouchPointView != null) {
singleTouchPointView.animate()
touchPointView.animate() .alpha(0.3f)
.alpha(0.3f) .setDuration(100)
.setDuration(100) .withEndAction(() ->
.withEndAction(() -> singleTouchPointView.animate()
touchPointView.animate() .alpha(1.0f)
.alpha(1.0f) .setDuration(100)
.setDuration(100) .start()
.start() )
) .start();
.start(); }
} for (View point : moreTouchPointViews) {
point.animate()
private void registerBroadcast(Context context) { .alpha(0.3f)
LocalBroadcastManager.getInstance(context).registerReceiver(new BroadcastReceiver() { .setDuration(100)
@Override .withEndAction(() ->
public void onReceive(Context context, Intent intent) { point.animate()
if ("com.auto.autoclicker.FLASH_TOUCH_POINT".equals(intent.getAction())) { .alpha(1.0f)
flashTouchPoint(); .setDuration(100)
} .start()
} )
}, new IntentFilter("com.auto.autoclicker.FLASH_TOUCH_POINT")); .start();
}
} }
private boolean isDebounced() { private boolean isDebounced() {
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
if (currentTime - lastToggleTime < DEBOUNCE_INTERVAL) { if (currentTime - lastToggleTime < DEBOUNCE_INTERVAL) {
return false; return true;
} }
lastToggleTime = currentTime; lastToggleTime = currentTime;
return true; return false;
} }
private void logDebug(String message) { private void logDebug(String message) {
Log.d(TAG, message); Log.d(TAG, message);
} }
} }

View File

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

View File

@ -23,17 +23,27 @@ import com.auto.autoclicker.util.PrefUtils;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity"; private static final String TAG = "MainActivity";
private static final String ACTION_FLOATING_STATE_CHANGED = "com.auto.autoclicker.FLOATING_WINDOW_STATE_CHANGED";
private ActivityMainBinding binding; 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 ActivityResultLauncher<Intent> permissionLauncher;
private long lastClickTime = 0; private long lastClickTime = 0;
private static final long DEBOUNCE_INTERVAL = 500; 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() { private final BroadcastReceiver floatingReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
isFloatingShown = intent.getBooleanExtra("isShown", false); isFloatingShown = intent.getIntExtra("isShown", 0);
updateButtonText(); updateButtonText();
} }
}; };
@ -45,13 +55,14 @@ public class MainActivity extends AppCompatActivity {
binding = ActivityMainBinding.inflate(getLayoutInflater()); binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); setContentView(binding.getRoot());
isFloatingShown = PrefUtils.isFloatingShown(this); isFloatingShown = PrefUtils.getFloatingShown(this);
updateButtonText(); updateButtonText();
LocalBroadcastManager.getInstance(this).registerReceiver( LocalBroadcastManager.getInstance(this).registerReceiver(
floatingReceiver, new IntentFilter(ACTION_FLOATING_STATE_CHANGED)); 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()); permissionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> checkPermissions());
@ -120,24 +131,24 @@ public class MainActivity extends AppCompatActivity {
} }
private void syncServiceState() { private void syncServiceState() {
isFloatingShown = isServiceRunning(); isFloatingShown = getFloatingMode();
updateButtonText(); updateButtonText();
} }
private boolean isServiceRunning() { private int getFloatingMode() {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (ForegroundService.class.getName().equals(service.service.getClassName())) { 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(); long currentTime = System.currentTimeMillis();
if (currentTime - lastClickTime < DEBOUNCE_INTERVAL) { if (currentTime - lastClickTime < DEBOUNCE_INTERVAL) {
Toast.makeText(this, "点击太频繁", Toast.LENGTH_LONG).show(); showDebounceToast();
return; return;
} }
lastClickTime = currentTime; lastClickTime = currentTime;
@ -148,20 +159,28 @@ public class MainActivity extends AppCompatActivity {
return; 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)); stopService(new Intent(this, ForegroundService.class));
AutoClickService.getInstance().stopClicking(); AutoClickService.getInstance().stopClicking();
isFloatingShown = false; isFloatingShown = FLOATING_NONE;
logDebug("悬浮窗已经隐藏"); logDebug("悬浮窗已经隐藏");
} else { } else {
Intent serviceIntent = new Intent(this, ForegroundService.class); Intent serviceIntent = new Intent(this, ForegroundService.class);
serviceIntent.putExtra("FLOATING_TYPE", requestedType);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent); startForegroundService(serviceIntent);
} else { } else {
startService(serviceIntent); startService(serviceIntent);
} }
isFloatingShown = true;
logDebug("悬浮窗已经显示"); isFloatingShown = requestedType;
logDebug("悬浮窗已显示: " + requestedType);
} }
PrefUtils.setFloatingShown(this, isFloatingShown); PrefUtils.setFloatingShown(this, isFloatingShown);
@ -169,14 +188,14 @@ public class MainActivity extends AppCompatActivity {
updateButtonText(); updateButtonText();
} }
private void broadcastFloatingState(boolean isShown) { private void broadcastFloatingState(int isShown) {
Intent intent = new Intent(ACTION_FLOATING_STATE_CHANGED); Intent intent = new Intent(ACTION_FLOATING_STATE_CHANGED);
intent.putExtra("isShown", isShown); intent.putExtra("isShown", isShown);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent); LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
} }
private void updateButtonText() { private void updateButtonText() {
binding.singleButton.setText(isFloatingShown ? "隐藏悬浮窗" : "显示悬浮窗"); binding.singleButton.setText(isFloatingShown == 1 ? "隐藏悬浮窗" : "显示悬浮窗");
} }
@Override @Override
@ -185,6 +204,15 @@ public class MainActivity extends AppCompatActivity {
LocalBroadcastManager.getInstance(this).unregisterReceiver(floatingReceiver); 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) { private void logDebug(String message) {
Log.d(TAG, 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 PREF_NAME = "app_prefs";
private static final String KEY_FLOATING_SHOWN = "floating_window_shown"; 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) 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) 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" /> android:src="@drawable/play" />
<ImageView <ImageView
android:id="@+id/file_button" android:id="@+id/add_button"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:src="@drawable/save" /> android:src="@drawable/save" />
<ImageView <ImageView
android:id="@+id/eye_button" android:id="@+id/remove_button"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"