diff --git a/app/src/main/java/com/auto/autoclicker/AutoClickService.java b/app/src/main/java/com/auto/autoclicker/AutoClickService.java index 134f5a7..7a9d97c 100644 --- a/app/src/main/java/com/auto/autoclicker/AutoClickService.java +++ b/app/src/main/java/com/auto/autoclicker/AutoClickService.java @@ -12,14 +12,21 @@ import android.view.accessibility.AccessibilityEvent; import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import com.auto.autoclicker.util.ScreenUtils; +import com.auto.autoclicker.util.ViewUtils; + public class AutoClickService extends AccessibilityService { private static final String TAG = "AutoClickService"; - private static final long MIN_CLICK_INTERVAL = 100; + private static AutoClickService instance; + + private static final long MIN_CLICK_INTERVAL = 40; private static final long MAX_CLICK_INTERVAL = 10000; private static final int MIN_CLICK_DURATION = 50; private static final int MAX_CLICK_DURATION = 1000; + private final Handler handler = new Handler(Looper.getMainLooper()); private boolean isClicking = false; + private int clickX = 500; private int clickY = 500; private long clickInterval = 1000; @@ -30,46 +37,69 @@ public class AutoClickService extends AccessibilityService { @Override public void onCreate() { super.onCreate(); + instance = this; Point screenSize = ScreenUtils.getScreenSize(this); screenWidth = screenSize.x; screenHeight = screenSize.y; logDebug("屏幕尺寸: " + screenWidth + "x" + screenHeight); } + @Override + public void onServiceConnected() { + super.onServiceConnected(); + logDebug("无障碍服务已连接"); + } + + @Override + public void onInterrupt() { + stopClicking(); + 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 null; + return instance; } public void setClickPosition(float x, float y) { - Point constrainedPoint = ViewUtils.constrainToScreen(x, y, screenWidth, screenHeight); - this.clickX = constrainedPoint.x; - this.clickY = constrainedPoint.y; - logDebug("设置点击位置: x=" + x + ", y=" + y + ", 修正后: (" + clickX + ", " + clickY + ")"); + Point constrained = ViewUtils.constrainToScreen(x, y, screenWidth, screenHeight); + this.clickX = constrained.x; + this.clickY = constrained.y; + logDebug("设置点击位置: 原始 (" + x + ", " + y + ") -> 修正 (" + clickX + ", " + clickY + ")"); } public void setClickInterval(long interval) { if (interval < MIN_CLICK_INTERVAL || interval > MAX_CLICK_INTERVAL) { - Log.w(TAG, "点击间隔超出合理范围: " + interval + "ms"); + Log.w(TAG, "点击间隔超出范围: " + interval + "ms"); return; } this.clickInterval = interval; - logDebug("点击间隔设置为: " + interval + "ms"); + logDebug("设置点击间隔: " + interval + "ms"); } public void setClickDuration(int duration) { if (duration < MIN_CLICK_DURATION || duration > MAX_CLICK_DURATION) { - Log.w(TAG, "点击时长超出合理范围: " + duration + "ms"); + Log.w(TAG, "点击时长超出范围: " + duration + "ms"); return; } this.clickDuration = duration; - logDebug("点击时长设置为: " + duration + "ms"); + logDebug("设置点击时长: " + duration + "ms"); } public void startClicking() { if (!isClicking) { isClicking = true; + logDebug("开始自动点击"); performClick(); - logDebug("开始点击: (" + clickX + ", " + clickY + ")"); } } @@ -77,17 +107,23 @@ public class AutoClickService extends AccessibilityService { if (isClicking) { isClicking = false; handler.removeCallbacksAndMessages(null); - logDebug("停止点击"); + logDebug("停止自动点击"); } } + public boolean isClicking() { + return isClicking; + } + private void performClick() { if (!isClicking) { - logDebug("点击被跳过(未开始)"); + logDebug("跳过点击:服务未处于运行中"); return; } logDebug("执行点击: (" + clickX + ", " + clickY + ")"); + flashTouchFeedback(); + Path path = new Path(); path.moveTo(clickX, clickY); GestureDescription.StrokeDescription stroke = @@ -98,7 +134,7 @@ public class AutoClickService extends AccessibilityService { dispatchGesture(gesture, new GestureResultCallback() { @Override public void onCompleted(GestureDescription gestureDescription) { - logDebug("点击完成: (" + clickX + ", " + clickY + ")"); + logDebug("点击完成"); if (isClicking) { handler.postDelayed(() -> performClick(), clickInterval); } @@ -106,7 +142,7 @@ public class AutoClickService extends AccessibilityService { @Override public void onCancelled(GestureDescription gestureDescription) { - Log.e(TAG, "点击取消: (" + clickX + ", " + clickY + ")"); + Log.e(TAG, "点击被取消"); if (isClicking) { handler.postDelayed(() -> performClick(), clickInterval + 300); } @@ -114,35 +150,17 @@ public class AutoClickService extends AccessibilityService { }, null); } + private void flashTouchFeedback() { + FloatingViewManager manager = FloatingViewManager.getInstance(); + if (manager != null) { + manager.flashTouchPoint(); + } + } + @Override public void onAccessibilityEvent(AccessibilityEvent event) { } - @Override - public void onInterrupt() { - stopClicking(); - logDebug("无障碍服务中断"); - } - - @Override - protected void onServiceConnected() { - super.onServiceConnected(); - logDebug("无障碍服务已连接"); - } - - @Override - public void onDestroy() { - super.onDestroy(); - stopClicking(); - Intent intent = new Intent("com.auto.autoclicker.SERVICE_DESTROYED"); - LocalBroadcastManager.getInstance(this).sendBroadcast(intent); - logDebug("无障碍服务已销毁"); - } - - public boolean isClicking() { - return isClicking; - } - private void logDebug(String message) { Log.d(TAG, message); } diff --git a/app/src/main/java/com/auto/autoclicker/FloatingViewManager.java b/app/src/main/java/com/auto/autoclicker/FloatingViewManager.java index 6fd8bef..25ab103 100644 --- a/app/src/main/java/com/auto/autoclicker/FloatingViewManager.java +++ b/app/src/main/java/com/auto/autoclicker/FloatingViewManager.java @@ -1,24 +1,38 @@ package com.auto.autoclicker; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; import android.content.Context; import android.content.Intent; +import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Point; import android.os.Build; import android.provider.Settings; +import android.text.InputType; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; +import com.auto.autoclicker.util.ScreenUtils; +import com.auto.autoclicker.util.ViewUtils; + public class FloatingViewManager { private static final String TAG = "FloatingViewManager"; private static final long DEBOUNCE_INTERVAL = 500; + + private static FloatingViewManager instance; + private final Context context; private final WindowManager windowManager; private View touchPointView; @@ -32,9 +46,11 @@ public class FloatingViewManager { private long lastToggleTime = 0; private final int screenWidth; private final int screenHeight; + private final AutoClickService service = AutoClickService.getInstance(); public FloatingViewManager(Context context) { this.context = context; + instance = this; this.windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Point screenSize = ScreenUtils.getScreenSize(context); this.screenWidth = screenSize.x; @@ -42,6 +58,10 @@ public class FloatingViewManager { logDebug("屏幕尺寸: " + screenWidth + "x" + screenHeight); } + public static FloatingViewManager getInstance() { + return instance; + } + public void showFloatingViews() throws SecurityException { if (!Settings.canDrawOverlays(context)) { throw new SecurityException("需要悬浮窗权限"); @@ -61,6 +81,24 @@ public class FloatingViewManager { logDebug("悬浮窗已添加"); } + 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); @@ -106,16 +144,7 @@ public class FloatingViewManager { }); } - 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); - } - + @SuppressLint("ClickableViewAccessibility") private void initializeControlBarView() { controlBarView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.control_bar, null); controlBarParams = new WindowManager.LayoutParams( @@ -150,18 +179,21 @@ public class FloatingViewManager { float dy = event.getRawY() - lastY; updateControlBarPosition(paramX, paramY, dx, dy); return true; + case MotionEvent.ACTION_UP: + v.performClick(); + return true; } return false; } }); - Button toggleButton = controlBarView.findViewById(R.id.toggle_button); - toggleButton.setOnClickListener(v -> { - if (!isDebounced()) { - return; - } + 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(); @@ -173,13 +205,13 @@ public class FloatingViewManager { if (isClicking) { service.stopClicking(); - toggleButton.setText(R.string.start_click); + play.setBackgroundResource(R.drawable.play); touchPointView.setBackgroundResource(R.drawable.un_touch_point); Toast.makeText(context, "停止自动点击", Toast.LENGTH_SHORT).show(); } else { - service.setClickPosition(touchPointX + 50, touchPointY + 50); + service.setClickPosition(touchPointX, touchPointY); service.startClicking(); - toggleButton.setText(R.string.stop_click); + play.setBackgroundResource(R.drawable.play); touchPointView.setBackgroundResource(R.drawable.touch_point); Toast.makeText(context, "开始自动点击", Toast.LENGTH_SHORT).show(); } @@ -187,6 +219,25 @@ public class FloatingViewManager { isClicking = !isClicking; logDebug("服务是否点击中: " + service.isClicking()); }); + + close.setOnClickListener(v -> { + service.stopClicking(); + touchPointView.setBackgroundResource(R.drawable.un_touch_point); + removeFloatingViews(); + }); + + setting.setOnClickListener(v -> showInputDialog()); + + } + + 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); } private void updateControlBarPosition(float paramX, float paramY, float dx, float dy) { @@ -197,6 +248,72 @@ public class FloatingViewManager { windowManager.updateViewLayout(controlBarView, controlBarParams); } + public void showInputDialog() { + EditText input = new EditText(context); + input.setInputType(InputType.TYPE_CLASS_NUMBER); + input.setHint("输入点击间隔(ms)"); + input.setFocusableInTouchMode(true); + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle("设置点击间隔") + .setView(input) + .setPositiveButton("确定", (dialog, which) -> { + String value = input.getText().toString(); + try { + long interval = Long.parseLong(value); + AutoClickService service = AutoClickService.getInstance(); + if (service != null) { + service.setClickInterval(interval); + } else { + Log.w("InputDialog", "AutoClickService 未初始化"); + } + } catch (NumberFormatException e) { + Toast.makeText(context, "无效输入", Toast.LENGTH_SHORT).show(); + } + dialog.dismiss(); + }) + .setNegativeButton("取消", (dialog, which) -> dialog.dismiss()); + + AlertDialog dialog = builder.create(); + + // 设置弹窗的类型,允许在悬浮窗中显示输入框 + if (!(context instanceof Activity)) { + if (dialog.getWindow() != null) { + dialog.getWindow().setType( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : + WindowManager.LayoutParams.TYPE_PHONE + ); + } + } + + dialog.show(); + + // 强制显示软键盘 + input.requestFocus(); + input.post(() -> { + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + } + }); + } + + 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 boolean isDebounced() { long currentTime = System.currentTimeMillis(); if (currentTime - lastToggleTime < DEBOUNCE_INTERVAL) { @@ -206,25 +323,7 @@ public class FloatingViewManager { return true; } - 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 logDebug(String message) { Log.d(TAG, message); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/auto/autoclicker/ForegroundService.java b/app/src/main/java/com/auto/autoclicker/ForegroundService.java index e20a5df..e7be063 100644 --- a/app/src/main/java/com/auto/autoclicker/ForegroundService.java +++ b/app/src/main/java/com/auto/autoclicker/ForegroundService.java @@ -48,7 +48,6 @@ public class ForegroundService extends Service { startForeground(NOTIFICATION_ID, notification); - // 仅在未显示悬浮窗时显示 if (floatingViewManager != null && !isFloatingViewShown) { try { floatingViewManager.showFloatingViews(); @@ -65,10 +64,7 @@ public class ForegroundService extends Service { return START_STICKY; } - - /** - * 创建通知渠道(Android 8.0+) - */ + private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( diff --git a/app/src/main/java/com/auto/autoclicker/MainActivity.java b/app/src/main/java/com/auto/autoclicker/MainActivity.java index 790828c..3d8bf91 100644 --- a/app/src/main/java/com/auto/autoclicker/MainActivity.java +++ b/app/src/main/java/com/auto/autoclicker/MainActivity.java @@ -8,16 +8,17 @@ import android.os.Build; import android.os.Bundle; import android.os.PowerManager; import android.provider.Settings; -import android.widget.Button; import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AppCompatActivity; +import com.auto.autoclicker.databinding.ActivityMainBinding; + public class MainActivity extends AppCompatActivity { - private Button startButton; + private ActivityMainBinding binding; private ActivityResultLauncher permissionLauncher; private boolean isFloatingShown = false; private long lastClickTime = 0; @@ -26,10 +27,11 @@ public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - startButton = findViewById(R.id.start_Button); - startButton.setOnClickListener(v -> toggleFloatingWindow()); + binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + binding.startButton.setOnClickListener(v -> toggleFloatingWindow()); permissionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> checkPermissions()); @@ -75,7 +77,7 @@ public class MainActivity extends AppCompatActivity { } } - startButton.setEnabled(allPermissionsGranted); + binding.startButton.setEnabled(allPermissionsGranted); updateButtonText(); } @@ -135,7 +137,9 @@ public class MainActivity extends AppCompatActivity { isFloatingShown = true; Toast.makeText(this, "悬浮窗已显示", Toast.LENGTH_SHORT).show(); } else { + AutoClickService service = AutoClickService.getInstance(); stopService(new Intent(this, ForegroundService.class)); + service.stopClicking(); isFloatingShown = false; Toast.makeText(this, "悬浮窗已隐藏", Toast.LENGTH_SHORT).show(); } @@ -143,6 +147,6 @@ public class MainActivity extends AppCompatActivity { } private void updateButtonText() { - startButton.setText(isFloatingShown ? R.string.hide_floating_window : R.string.show_floating_window); + binding.startButton.setText(isFloatingShown ? R.string.hide_floating_window : R.string.show_floating_window); } } \ No newline at end of file diff --git a/app/src/main/java/com/auto/autoclicker/ScreenUtils.java b/app/src/main/java/com/auto/autoclicker/util/ScreenUtils.java similarity index 90% rename from app/src/main/java/com/auto/autoclicker/ScreenUtils.java rename to app/src/main/java/com/auto/autoclicker/util/ScreenUtils.java index 5a847b4..083e0ce 100644 --- a/app/src/main/java/com/auto/autoclicker/ScreenUtils.java +++ b/app/src/main/java/com/auto/autoclicker/util/ScreenUtils.java @@ -1,4 +1,4 @@ -package com.auto.autoclicker; +package com.auto.autoclicker.util; import android.content.Context; import android.graphics.Point; diff --git a/app/src/main/java/com/auto/autoclicker/ViewUtils.java b/app/src/main/java/com/auto/autoclicker/util/ViewUtils.java similarity index 90% rename from app/src/main/java/com/auto/autoclicker/ViewUtils.java rename to app/src/main/java/com/auto/autoclicker/util/ViewUtils.java index ac13359..704458e 100644 --- a/app/src/main/java/com/auto/autoclicker/ViewUtils.java +++ b/app/src/main/java/com/auto/autoclicker/util/ViewUtils.java @@ -1,4 +1,4 @@ -package com.auto.autoclicker; +package com.auto.autoclicker.util; import android.graphics.Point; diff --git a/app/src/main/res/drawable/cancel.xml b/app/src/main/res/drawable/cancel.xml new file mode 100644 index 0000000..c048b34 --- /dev/null +++ b/app/src/main/res/drawable/cancel.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/eye.xml b/app/src/main/res/drawable/eye.xml new file mode 100644 index 0000000..891f6de --- /dev/null +++ b/app/src/main/res/drawable/eye.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/play.xml b/app/src/main/res/drawable/play.xml new file mode 100644 index 0000000..3c0d6d3 --- /dev/null +++ b/app/src/main/res/drawable/play.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/rounded_background.xml b/app/src/main/res/drawable/rounded_background.xml new file mode 100644 index 0000000..250fa56 --- /dev/null +++ b/app/src/main/res/drawable/rounded_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/save.xml b/app/src/main/res/drawable/save.xml new file mode 100644 index 0000000..ce2eff7 --- /dev/null +++ b/app/src/main/res/drawable/save.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/setting.xml b/app/src/main/res/drawable/setting.xml new file mode 100644 index 0000000..fd1be01 --- /dev/null +++ b/app/src/main/res/drawable/setting.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/layout/control_bar.xml b/app/src/main/res/layout/control_bar.xml index 7a3d93b..e8ced22 100644 --- a/app/src/main/res/layout/control_bar.xml +++ b/app/src/main/res/layout/control_bar.xml @@ -1,16 +1,45 @@ - -