功能添加

This commit is contained in:
lihongwei 2025-06-30 15:24:58 +08:00
parent 5a78b467fc
commit 427f0b12c4
7 changed files with 436 additions and 307 deletions

View File

@ -30,8 +30,6 @@ public class AutoClickService extends AccessibilityService {
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 static final int MIN_SLIDE_DURATION = 100;
private static final int MAX_SLIDE_DURATION = 2000;
@ -109,15 +107,6 @@ public class AutoClickService extends AccessibilityService {
logDebug("设置点击间隔: " + interval + "ms");
}
public void setClickDuration(int duration) {
if (duration < MIN_CLICK_DURATION || duration > MAX_CLICK_DURATION) {
Log.w(TAG, "点击时长超出范围: " + duration + "ms");
return;
}
this.clickDuration = duration;
logDebug("设置点击时长: " + duration + "ms");
}
public void setSlideDuration(int duration) {
if (duration < MIN_SLIDE_DURATION || duration > MAX_SLIDE_DURATION) {
Log.w(TAG, "滑动时长超出范围: " + duration + "ms");
@ -127,11 +116,6 @@ public class AutoClickService extends AccessibilityService {
logDebug("设置滑动时长: " + duration + "ms");
}
/**
* 设置事件队列的循环次数
*
* @param count 循环次数设置为0表示无限循环设置为1表示不循环执行一次
*/
public void setLoopCount(int count) {
if (count < 0) {
Log.w(TAG, "循环次数不能为负数,已设置为默认值 1");
@ -140,18 +124,12 @@ public class AutoClickService extends AccessibilityService {
this.loopCount = count;
}
logDebug("设置循环次数: " + loopCount + "");
// 如果设置了循环次数则重置循环时间为0确保两种模式不冲突
if (this.loopCount > 0) {
this.loopTimeMillis = 0;
logDebug("已重置循环时间为 0ms");
}
}
/**
* 设置事件队列的循环总时间
*
* @param timeMillis 循环总时间单位毫秒设置为0表示不按时间循环
*/
public void setLoopTime(long timeMillis) {
if (timeMillis < 0) {
Log.w(TAG, "循环时间不能为负数,已设置为默认值 0ms");
@ -160,7 +138,6 @@ public class AutoClickService extends AccessibilityService {
this.loopTimeMillis = timeMillis;
}
logDebug("设置循环时间: " + loopTimeMillis + "ms");
// 如果设置了循环时间则重置循环次数为0确保两种模式不冲突
if (this.loopTimeMillis > 0) {
this.loopCount = 0;
logDebug("已重置循环次数为 0");
@ -203,10 +180,9 @@ public class AutoClickService extends AccessibilityService {
EventViewBinder.setAllTouchPointsDraggable(true);
// 重置循环状态
currentEventIndex = 0; // 停止时重置事件索引
currentLoop = 0; // 停止时重置循环计数
startTimeMillis = 0; // 停止时重置开始时间
currentEventIndex = 0;
currentLoop = 0;
startTimeMillis = 0;
logDebug("停止执行事件");
}
@ -224,29 +200,26 @@ public class AutoClickService extends AccessibilityService {
if (runtimeEvents.isEmpty()) {
logDebug("跳过执行:事件队列为空");
stopClicking(); // 如果事件队列为空则停止服务
stopClicking();
return;
}
// 在每一轮循环开始前排序确保顺序正确
runtimeEvents.sort(Comparator.comparingInt(EventWrapper::getOrder));
// 检查是否完成了一轮事件列表
if (currentEventIndex >= runtimeEvents.size()) {
currentEventIndex = 0; // 重置事件索引准备开始新一轮循环
currentLoop++; // 完成一轮增加循环计数
currentEventIndex = 0;
currentLoop++;
logDebug("完成第 " + currentLoop + " 轮事件执行。");
// 判断是否继续循环
boolean continueLoop = false;
if (loopCount > 0) { // 按次数循环模式
if (loopCount > 0) {
if (currentLoop < loopCount) {
continueLoop = true;
logDebug("继续第 " + (currentLoop + 1) + " 轮循环 (共 " + loopCount + " 轮)");
} else {
logDebug("已达到设定的循环次数 (" + loopCount + " 次),停止循环。");
}
} else if (loopTimeMillis > 0) { // 按时间循环模式
} else if (loopTimeMillis > 0) {
long elapsedTime = System.currentTimeMillis() - startTimeMillis;
if (elapsedTime < loopTimeMillis) {
continueLoop = true;
@ -254,13 +227,13 @@ public class AutoClickService extends AccessibilityService {
} else {
logDebug("已达到设定的循环时间 (" + (loopTimeMillis / 1000) + "s),停止循环。");
}
} else { // 无限循环模式 (loopCount == 0 && loopTimeMillis == 0)
} else {
continueLoop = true;
logDebug("无限循环模式,继续下一轮循环。");
}
if (!continueLoop) {
stopClicking(); // 不再继续循环则停止服务
stopClicking();
return;
}
}
@ -274,8 +247,8 @@ public class AutoClickService extends AccessibilityService {
performSlide((SlideEvent) event);
} else {
logDebug("未知或不匹配的事件类型,跳过执行: " + wrapper.getType());
currentEventIndex++; // 跳过当前事件
handler.postDelayed(this::executeEventsByType, clickInterval); // 立即尝试下一个事件
currentEventIndex++;
handler.postDelayed(this::executeEventsByType, clickInterval);
}
}

View File

@ -4,6 +4,7 @@ import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.text.InputType;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -12,18 +13,35 @@ import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatButton;
import androidx.fragment.app.Fragment;
import com.auto.clicker.autoclicker.R;
import com.auto.clicker.autoclicker.databinding.FragmentActionBinding;
import com.auto.clicker.autoclicker.service.AutoClickService;
public class ActionFragment extends Fragment {
private static final String TAG = "ActionFragment";
private FragmentActionBinding binding;
private static final int TYPE_INTERVAL = 0;
private static final int TYPE_SWIPE_DURATION = 1;
private static final int TYPE_REPEAT = 2;
private enum UnitType {
MS, SEC, MIN, INFINITELY, DURATION, CYCLE, UNKNOWN
}
private UnitType currentIntervalUnit = UnitType.MS;
private UnitType currentSwipeDurationUnit = UnitType.MS;
private UnitType currentRepeatUnit = UnitType.INFINITELY;
private String currentIntervalValue = "";
private String currentDurationValue = "";
private String currentRepeatValue = "";
private boolean isRepeatSwitchChecked = false;
private AppCompatButton saveSettingsButton;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@ -33,38 +51,61 @@ public class ActionFragment extends Fragment {
binding.unitSelectorContainer.setOnClickListener(v -> showUnitSelection(v, TYPE_SWIPE_DURATION));
setupRepeatMode();
setupAdvancedMode();
restoreUIState();
saveSettingsButton = binding.save;
saveSettingsButton.setOnClickListener(v -> {
applySettingsToService();
Toast.makeText(getContext(), "设置已保存并应用!", Toast.LENGTH_SHORT).show();
});
return binding.getRoot();
}
@Override
public void onResume() {
super.onResume();
restoreUIState();
}
private void setupRepeatMode() {
binding.repeatSwitch.setChecked(isRepeatSwitchChecked);
updateRepeatUI(isRepeatSwitchChecked);
binding.repeatSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
binding.repeatInput.setVisibility(View.VISIBLE);
binding.repeatSelectorContainer.setVisibility(View.VISIBLE);
isRepeatSwitchChecked = isChecked;
updateRepeatUI(isChecked);
});
binding.repeatSelectorContainer.setOnClickListener(v -> showUnitSelection(v, TYPE_REPEAT));
}
private void updateRepeatUI(boolean isChecked) {
if (isChecked) {
binding.repeatInput.setVisibility(View.VISIBLE);
binding.repeatSelectorContainer.setVisibility(View.VISIBLE);
if (currentRepeatUnit == UnitType.INFINITELY) {
binding.repeatSelectedUnit.setText(getString(R.string.infinitely_unit));
binding.etRepeatValue.setText("");
binding.etRepeatValue.setEnabled(false);
binding.etRepeatValue.setInputType(InputType.TYPE_NULL);
} else {
binding.repeatInput.setVisibility(View.GONE);
binding.repeatSelectorContainer.setVisibility(View.GONE);
} else if (currentRepeatUnit == UnitType.DURATION) {
binding.repeatSelectedUnit.setText(getString(R.string.duration_unit));
binding.etRepeatValue.setText(currentRepeatValue);
binding.etRepeatValue.setEnabled(true);
binding.etRepeatValue.setInputType(InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_TIME);
} else if (currentRepeatUnit == UnitType.CYCLE) {
binding.repeatSelectedUnit.setText(getString(R.string.cycle_unit));
binding.etRepeatValue.setText(currentRepeatValue);
binding.etRepeatValue.setEnabled(true);
binding.etRepeatValue.setInputType(InputType.TYPE_CLASS_NUMBER);
}
});
if (!binding.repeatSwitch.isChecked()) {
} else {
binding.repeatInput.setVisibility(View.GONE);
binding.repeatSelectorContainer.setVisibility(View.GONE);
} else {
binding.repeatSelectedUnit.setText(getString(R.string.infinitely_unit));
binding.etRepeatValue.setText("");
binding.etRepeatValue.setEnabled(false);
binding.etRepeatValue.setInputType(InputType.TYPE_NULL);
}
binding.repeatSelectorContainer.setOnClickListener(v -> showUnitSelection(v, TYPE_REPEAT));
}
private void setupAdvancedMode() {
@ -72,9 +113,9 @@ public class ActionFragment extends Fragment {
binding.advancedSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
Toast.makeText(getContext(), "Anti-Detection mode is ON", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "防检测模式已开启", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(), "Anti-Detection mode is OFF", Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), "防检测模式已关闭", Toast.LENGTH_SHORT).show();
}
});
}
@ -98,132 +139,251 @@ public class ActionFragment extends Fragment {
TextView tvDuration = popupView.findViewById(R.id.tv_unit_duration);
TextView tvCycle = popupView.findViewById(R.id.tv_unit_cycle);
tvMs.setVisibility(View.GONE);
tvSec.setVisibility(View.GONE);
tvMin.setVisibility(View.GONE);
tvInfinitely.setVisibility(View.GONE);
tvDuration.setVisibility(View.GONE);
tvCycle.setVisibility(View.GONE);
switch (type) {
case TYPE_INTERVAL:
tvMs.setVisibility(View.VISIBLE);
tvSec.setVisibility(View.VISIBLE);
tvMin.setVisibility(View.VISIBLE);
tvInfinitely.setVisibility(View.GONE);
tvDuration.setVisibility(View.GONE);
tvCycle.setVisibility(View.GONE);
break;
case TYPE_SWIPE_DURATION:
tvMs.setVisibility(View.VISIBLE);
tvSec.setVisibility(View.VISIBLE);
tvMin.setVisibility(View.GONE);
tvInfinitely.setVisibility(View.GONE);
tvDuration.setVisibility(View.GONE);
tvCycle.setVisibility(View.GONE);
break;
case TYPE_REPEAT:
tvMs.setVisibility(View.GONE);
tvSec.setVisibility(View.GONE);
tvMin.setVisibility(View.GONE);
tvInfinitely.setVisibility(View.VISIBLE);
tvDuration.setVisibility(View.VISIBLE);
tvCycle.setVisibility(View.VISIBLE);
break;
}
if (type == TYPE_INTERVAL || type == TYPE_SWIPE_DURATION) {
tvMs.setOnClickListener(v -> {
String unitText = getString(R.string.milliseconds_unit);
if (type == TYPE_INTERVAL) {
binding.intervalSelectedUnit.setText(unitText);
showInputValue("Interval", binding.etIntervalValue.getText().toString(), unitText);
} else {
binding.tvSelectedUnit.setText(unitText);
showInputValue("Swipe Duration", binding.etDurationValue.getText().toString(), unitText);
}
tvMs.setOnClickListener(v -> {
UnitType selectedUnitType = UnitType.MS;
String unitText = getString(R.string.milliseconds_unit);
updateUIAndSaveSettings(type, selectedUnitType, unitText);
popupWindow.dismiss();
});
tvSec.setOnClickListener(v -> {
UnitType selectedUnitType = UnitType.SEC;
String unitText = getString(R.string.seconds_unit);
updateUIAndSaveSettings(type, selectedUnitType, unitText);
popupWindow.dismiss();
});
if (type == TYPE_INTERVAL) {
tvMin.setOnClickListener(v -> {
UnitType selectedUnitType = UnitType.MIN;
String unitText = getString(R.string.minutes_unit);
updateUIAndSaveSettings(type, selectedUnitType, unitText);
popupWindow.dismiss();
});
tvSec.setOnClickListener(v -> {
String unitText = getString(R.string.seconds_unit);
if (type == TYPE_INTERVAL) {
binding.intervalSelectedUnit.setText(unitText);
showInputValue("Interval", binding.etIntervalValue.getText().toString(), unitText);
} else {
binding.tvSelectedUnit.setText(unitText);
showInputValue("Swipe Duration", binding.etDurationValue.getText().toString(), unitText);
}
popupWindow.dismiss();
});
if (type == TYPE_INTERVAL) {
tvMin.setOnClickListener(v -> {
String unitText = getString(R.string.minutes_unit);
binding.intervalSelectedUnit.setText(unitText);
popupWindow.dismiss();
showInputValue("Interval", binding.etIntervalValue.getText().toString(), unitText);
});
}
}
if (type == TYPE_REPEAT) {
tvInfinitely.setOnClickListener(v -> {
binding.repeatSelectedUnit.setText(getString(R.string.infinitely_unit));
binding.etRepeatValue.setText("");
binding.etRepeatValue.setEnabled(false);
binding.etRepeatValue.setInputType(InputType.TYPE_NULL);
UnitType selectedUnitType = UnitType.INFINITELY;
String unitText = getString(R.string.infinitely_unit);
updateUIAndSaveSettings(type, selectedUnitType, unitText);
updateRepeatUI(true);
popupWindow.dismiss();
showInputValue("Repeat", binding.etRepeatValue.getText().toString(), getString(R.string.infinitely_unit));
});
tvDuration.setOnClickListener(v -> {
binding.repeatSelectedUnit.setText(getString(R.string.duration_unit));
binding.etRepeatValue.setText("");
binding.etRepeatValue.setEnabled(true);
binding.etRepeatValue.setInputType(InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_TIME); // 设置为时间格式
UnitType selectedUnitType = UnitType.DURATION;
String unitText = getString(R.string.duration_unit);
updateUIAndSaveSettings(type, selectedUnitType, unitText);
updateRepeatUI(true);
popupWindow.dismiss();
showInputValue("Repeat", binding.etRepeatValue.getText().toString(), getString(R.string.duration_unit));
});
tvCycle.setOnClickListener(v -> {
binding.repeatSelectedUnit.setText(getString(R.string.cycle_unit));
binding.etRepeatValue.setText("");
binding.etRepeatValue.setEnabled(true);
binding.etRepeatValue.setInputType(InputType.TYPE_CLASS_NUMBER);
UnitType selectedUnitType = UnitType.CYCLE;
String unitText = getString(R.string.cycle_unit);
updateUIAndSaveSettings(type, selectedUnitType, unitText);
updateRepeatUI(true);
popupWindow.dismiss();
showInputValue("Repeat", binding.etRepeatValue.getText().toString(), getString(R.string.cycle_unit));
});
}
popupWindow.showAsDropDown(anchorView);
}
private void showInputValue(String type, String value, String unit) {
String message = type + " 值: " + (value.isEmpty() ? "未输入" : value) + ", 单位: " + unit;
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
private void updateUIAndSaveSettings(int type, UnitType selectedUnitType, String unitText) {
switch (type) {
case TYPE_INTERVAL:
binding.intervalSelectedUnit.setText(unitText);
currentIntervalUnit = selectedUnitType;
break;
case TYPE_SWIPE_DURATION:
binding.tvSelectedUnit.setText(unitText);
currentSwipeDurationUnit = selectedUnitType;
break;
case TYPE_REPEAT:
binding.repeatSelectedUnit.setText(unitText);
currentRepeatUnit = selectedUnitType;
break;
}
saveCurrentInputValues();
showInputValue(type, getEditTextValue(type), unitText);
}
if (type.equals("Interval")) {
long intervalValue = value.isEmpty() ? 0 : Long.parseLong(value);
// 根据 unit 进行单位转换或保存 Interval
// 例如if (unit.equals(getString(R.string.seconds_unit))) { ... }
} else if (type.equals("Swipe Duration")) {
long durationValue = value.isEmpty() ? 0 : Long.parseLong(value);
// 根据 unit 进行单位转换或保存 Swipe Duration
} else if (type.equals("Repeat")) {
if (unit.equals(getString(R.string.infinitely_unit))) {
// Repeat 为无限次不需要处理输入值
} else if (unit.equals(getString(R.string.duration_unit))) {
// Repeat 为时长处理时间格式的输入
// 您需要在这里添加解析时间格式的逻辑
String durationValue = value.isEmpty() ? "0" : value;
// long parsedDuration = parseDurationString(durationValue); // 自定义方法解析时间
Toast.makeText(getContext(), "Repeat Duration: " + durationValue, Toast.LENGTH_SHORT).show();
} else if (unit.equals(getString(R.string.cycle_unit))) {
// Repeat 为次数处理整数输入
int repeatCount = value.isEmpty() ? 0 : Integer.parseInt(value);
Toast.makeText(getContext(), "Repeat Cycles: " + repeatCount, Toast.LENGTH_SHORT).show();
private String getEditTextValue(int type) {
switch (type) {
case TYPE_INTERVAL: return binding.etIntervalValue.getText().toString();
case TYPE_SWIPE_DURATION: return binding.etDurationValue.getText().toString();
case TYPE_REPEAT: return binding.etRepeatValue.getText().toString();
default: return "";
}
}
private void showInputValue(int type, String value, String unitText) {
String typeName = "";
switch (type) {
case TYPE_INTERVAL: typeName = "点击间隔"; break;
case TYPE_SWIPE_DURATION: typeName = "滑动时长"; break;
case TYPE_REPEAT: typeName = "循环模式"; break;
}
String message = typeName + " 值: " + (value.isEmpty() ? "未输入" : value) + ", 单位: " + unitText;
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
}
private void saveCurrentInputValues() {
currentIntervalValue = binding.etIntervalValue.getText().toString();
currentDurationValue = binding.etDurationValue.getText().toString();
currentRepeatValue = binding.etRepeatValue.getText().toString();
isRepeatSwitchChecked = binding.repeatSwitch.isChecked();
}
private void restoreUIState() {
binding.etIntervalValue.setText(currentIntervalValue);
binding.intervalSelectedUnit.setText(getUnitString(currentIntervalUnit));
binding.etDurationValue.setText(currentDurationValue);
binding.tvSelectedUnit.setText(getUnitString(currentSwipeDurationUnit));
binding.repeatSwitch.setChecked(isRepeatSwitchChecked);
updateRepeatUI(isRepeatSwitchChecked);
if (isRepeatSwitchChecked) {
binding.repeatSelectedUnit.setText(getUnitString(currentRepeatUnit));
}
}
private String getUnitString(UnitType unitType) {
switch (unitType) {
case MS: return getString(R.string.milliseconds_unit);
case SEC: return getString(R.string.seconds_unit);
case MIN: return getString(R.string.minutes_unit);
case INFINITELY: return getString(R.string.infinitely_unit);
case DURATION: return getString(R.string.duration_unit);
case CYCLE: return getString(R.string.cycle_unit);
default: return "";
}
}
public void applySettingsToService() {
saveCurrentInputValues();
AutoClickService service = AutoClickService.getInstance();
if (service == null) {
Toast.makeText(getContext(), "自动点击服务未运行,无法应用设置。", Toast.LENGTH_SHORT).show();
Log.e(TAG, "AutoClickService 实例为空,无法应用设置。");
return;
}
long intervalValue = 0;
if (!currentIntervalValue.isEmpty()) {
try {
intervalValue = Long.parseLong(currentIntervalValue);
if (currentIntervalUnit == UnitType.SEC) {
intervalValue *= 1000;
} else if (currentIntervalUnit == UnitType.MIN) {
intervalValue *= 60 * 1000;
}
service.setClickInterval(intervalValue);
Log.d(TAG, "应用点击间隔到服务: " + intervalValue + "ms");
} catch (NumberFormatException e) {
Log.e(TAG, "点击间隔值解析错误: " + currentIntervalValue, e);
Toast.makeText(getContext(), "点击间隔值无效,请输入有效数字。", Toast.LENGTH_SHORT).show();
}
} else {
Log.d(TAG, "点击间隔值为空,未应用到服务。");
}
long slideDurationValue = 0;
if (!currentDurationValue.isEmpty()) {
try {
slideDurationValue = Long.parseLong(currentDurationValue);
if (currentSwipeDurationUnit == UnitType.SEC) {
slideDurationValue *= 1000;
}
service.setSlideDuration((int) slideDurationValue);
Log.d(TAG, "应用滑动时长到服务: " + slideDurationValue + "ms");
} catch (NumberFormatException e) {
Log.e(TAG, "滑动时长值解析错误: " + currentDurationValue, e);
Toast.makeText(getContext(), "滑动时长值无效,请输入有效数字。", Toast.LENGTH_SHORT).show();
}
} else {
Log.d(TAG, "滑动时长值为空,未应用到服务。");
}
if (isRepeatSwitchChecked) {
if (currentRepeatUnit == UnitType.INFINITELY) {
service.setLoopCount(0);
Log.d(TAG, "应用重复模式: 无限次。");
} else if (currentRepeatUnit == UnitType.DURATION) {
long repeatDurationMillis = 0;
if (!currentRepeatValue.isEmpty()) {
try {
repeatDurationMillis = Long.parseLong(currentRepeatValue);
service.setLoopTime(repeatDurationMillis);
Log.d(TAG, "应用重复时长: " + repeatDurationMillis + "ms。");
} catch (NumberFormatException e) {
Log.e(TAG, "重复时长值解析错误: " + currentRepeatValue, e);
Toast.makeText(getContext(), "重复时长值无效,请输入有效数字或正确的时间格式。", Toast.LENGTH_SHORT).show();
service.setLoopTime(0);
}
} else {
service.setLoopTime(0);
Log.d(TAG, "重复时长值为空,取消时间循环。");
}
service.setLoopCount(0);
} else if (currentRepeatUnit == UnitType.CYCLE) {
int repeatCount = 0;
if (!currentRepeatValue.isEmpty()) {
try {
repeatCount = Integer.parseInt(currentRepeatValue);
service.setLoopCount(repeatCount);
Log.d(TAG, "应用重复次数: " + repeatCount + "次。");
} catch (NumberFormatException e) {
Log.e(TAG, "重复次数值解析错误: " + currentRepeatValue, e);
Toast.makeText(getContext(), "重复次数值无效,请输入有效数字。", Toast.LENGTH_SHORT).show();
service.setLoopCount(1);
}
} else {
service.setLoopCount(1);
Log.d(TAG, "重复次数值为空,设置为执行一次。");
}
service.setLoopTime(0);
}
} else {
service.setLoopCount(1);
service.setLoopTime(0);
Log.d(TAG, "重复开关关闭,服务设置为执行一次。");
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
saveCurrentInputValues();
binding = null;
}
}

View File

@ -22,14 +22,12 @@ import java.util.Map;
public class EventViewBinder {
// 存储每个视图及其对应的 OnTouchListener 实例用于启用/禁用拖动
private static final Map<View, View.OnTouchListener> activeDragListeners = new HashMap<>();
public interface OnPositionChangedCallback {
void onPositionChanged(int x, int y);
}
// --- bindPoint 方法 ---
public static void bindPoint(Context context, WindowManager windowManager,
PointEvent pointEvent, int touchPointSize,
int screenWidth, int screenHeight,
@ -47,7 +45,6 @@ public class EventViewBinder {
pointView.setTextSize(TypedValue.COMPLEX_UNIT_SP, initialTextSize);
// 创建并保存 OnTouchListener
View.OnTouchListener touchListener = createTouchListener(
pointView, params, windowManager, screenWidth, screenHeight,
(x, y) -> {
@ -57,12 +54,11 @@ public class EventViewBinder {
});
pointView.setOnTouchListener(touchListener);
activeDragListeners.put(pointView, touchListener); // 保存监听器到 Map
activeDragListeners.put(pointView, touchListener);
windowManager.addView(pointView, params);
}
// --- bindSlide 方法 ---
public static void bindSlide(Context context, WindowManager windowManager,
SlideEvent slideEvent, int touchPointSize,
int screenWidth, int screenHeight,
@ -126,15 +122,13 @@ public class EventViewBinder {
updateLinePosition(slideEvent, touchPointSize);
}
// --- DraggableHelper 移动过来的 OnTouchListener 创建逻辑 ---
// 这个方法创建并返回一个 View.OnTouchListener 实例
private static View.OnTouchListener createTouchListener(
View view, WindowManager.LayoutParams params, WindowManager windowManager,
int screenWidth, int screenHeight, DraggableHelper.DragCallback callback) { // 仍然使用 DraggableHelper.DragCallback 接口
int screenWidth, int screenHeight, DraggableHelper.DragCallback callback) {
return new View.OnTouchListener() {
private float lastX, lastY;
private float paramX, paramY;
private boolean isDragging = false; // 标记是否正在拖动
private boolean isDragging = false;
@Override
public boolean onTouch(View v, MotionEvent event) {
@ -144,7 +138,7 @@ public class EventViewBinder {
lastY = event.getRawY();
paramX = params.x;
paramY = params.y;
isDragging = false; // 重置拖动状态
isDragging = false;
return true;
case MotionEvent.ACTION_MOVE:
@ -169,7 +163,7 @@ public class EventViewBinder {
return true;
case MotionEvent.ACTION_UP:
if (!isDragging) { // 如果没有拖动才视为点击
if (!isDragging) {
v.performClick();
}
return true;
@ -179,42 +173,25 @@ public class EventViewBinder {
};
}
// --- 新增方法统一控制所有触摸点的拖动状态 ---
public static void setAllTouchPointsDraggable(boolean draggable) {
for (Map.Entry<View, View.OnTouchListener> entry : activeDragListeners.entrySet()) {
View view = entry.getKey();
View.OnTouchListener listener = entry.getValue();
if (draggable) {
// 启用拖动重新设置保存的监听器
view.setOnTouchListener(listener);
} else {
// 禁用拖动将监听器设置为 null阻止触摸事件
view.setOnTouchListener(null);
}
}
}
// --- 新增方法移除不再需要的视图及其监听器 ---
public static void removeBoundView(WindowManager windowManager, View view) {
if (view != null) {
try {
windowManager.removeView(view);
activeDragListeners.remove(view); // Map 中移除
Log.d("EventViewBinder", "移除视图并清理监听器: " + view.getTag());
} catch (IllegalArgumentException e) {
Log.e("EventViewBinder", "尝试移除不存在的视图: " + e.getMessage());
}
}
}
// --- 辅助方法 (保持不变) ---
public static TextView createTouchPointView(Context context, String label) {
TextView touchPointView = new TextView(context);
touchPointView.setBackgroundResource(R.drawable.ring_has_bg);
touchPointView.setGravity(Gravity.CENTER);
touchPointView.setText(label);
touchPointView.setTextColor(Color.WHITE);
touchPointView.setTag(label); // 添加一个Tag方便日志识别
touchPointView.setTag(label);
return touchPointView;
}
@ -245,7 +222,7 @@ public class EventViewBinder {
overlayType,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, // 线条通常不可触摸
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT
);
params.gravity = Gravity.TOP | Gravity.START;

View File

@ -96,7 +96,6 @@ public class FloatingSettingDialogManager {
unitSelectorContainer.setOnClickListener(v -> showUnitSelection(v, TYPE_SWIPE_DURATION));
setupRepeatMode();
// --- 恢复UI状态 ---
restoreUIState();
// floatingDialogView.setOnTouchListener((v, event) -> {
@ -179,8 +178,8 @@ public class FloatingSettingDialogManager {
overlayType,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL // 允许将触摸事件传递到其下方窗口
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, // 监听外部触摸事件
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT
);
params.gravity = Gravity.CENTER;
@ -195,7 +194,7 @@ public class FloatingSettingDialogManager {
}
repeatSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
isRepeatSwitchChecked = isChecked; // 保存开关状态
isRepeatSwitchChecked = isChecked;
updateRepeatUI(isChecked);
});
@ -206,16 +205,14 @@ public class FloatingSettingDialogManager {
if (isChecked) {
repeatInput.setVisibility(View.VISIBLE);
repeatSelectorContainer.setVisibility(View.VISIBLE);
// 只有当单位是无限次时才禁用输入框
if (currentRepeatUnit.equals(context.getString(R.string.infinitely_unit)) || repeatSelectedUnit.getText().toString().equals(context.getString(R.string.infinitely_unit))) {
etRepeatValue.setText("");
etRepeatValue.setEnabled(false);
etRepeatValue.setInputType(InputType.TYPE_NULL);
repeatSelectedUnit.setText(context.getString(R.string.infinitely_unit)); // 确保显示无限次
repeatSelectedUnit.setText(context.getString(R.string.infinitely_unit));
} else {
etRepeatValue.setEnabled(true);
etRepeatValue.setInputType(InputType.TYPE_CLASS_NUMBER); // 默认设置为数字输入
// 恢复之前的输入值和单位
etRepeatValue.setInputType(InputType.TYPE_CLASS_NUMBER);
etRepeatValue.setText(currentRepeatValue);
repeatSelectedUnit.setText(currentRepeatUnit.isEmpty() ? context.getString(R.string.cycle_unit) : currentRepeatUnit);
}
@ -383,8 +380,6 @@ public class FloatingSettingDialogManager {
showToastOnUi(context, "点击间隔值无效,请输入有效数字。");
}
} else {
// 如果为空设置一个默认值或不设置这取决于你的业务逻辑
// service.setClickInterval(默认值);
Log.d(TAG, "点击间隔值为空,未应用到服务。");
}
@ -402,12 +397,9 @@ public class FloatingSettingDialogManager {
showToastOnUi(context, "滑动时长值无效,请输入有效数字。");
}
} else {
// 如果为空设置一个默认值或不设置
// service.setSlideDuration(默认值);
Log.d(TAG, "滑动时长值为空,未应用到服务。");
}
// 应用重复设置
if (isRepeatSwitchChecked) {
if (currentRepeatUnit.equals(context.getString(R.string.infinitely_unit))) {
service.setLoopCount(0); // 无限循环
@ -417,8 +409,6 @@ public class FloatingSettingDialogManager {
if (!currentRepeatValue.isEmpty()) {
try {
repeatDurationMillis = Long.parseLong(currentRepeatValue);
// 根据你的UI单位这里可能需要进一步转换例如用户输入的是秒
// repeatDurationMillis *= 1000L;
service.setLoopTime(repeatDurationMillis);
Log.d(TAG, "应用重复时长: " + repeatDurationMillis + "ms。");
} catch (NumberFormatException e) {

View File

@ -24,7 +24,6 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.auto.clicker.autoclicker.R;
@ -87,6 +86,13 @@ public class FloatingViewManager {
private static final int DEFAULT_CONTROL_BAR_WIDTH_DP = 50;
private ImageView playButton;
private ImageView addButton;
private ImageView removeButton;
private ImageView slideButton;
private ImageView saveButton;
private ImageView eyeButton;
private ImageView settingButton;
private ImageView closeButton;
public FloatingViewManager(Context context) {
this.context = context;
@ -145,7 +151,7 @@ public class FloatingViewManager {
multipleControlBarParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
setupDraggableView(multipleControlBarView, multipleControlBarParams, this::updateMoreControlBarPosition);
setupControlButtons(multipleControlBarView,mode);
setupControlButtons(multipleControlBarView, mode);
updateControlBarImageViews(multipleControlBarView, this.controlBarWidth);
}
@ -223,17 +229,17 @@ public class FloatingViewManager {
windowManager.updateViewLayout(multipleControlBarView, multipleControlBarParams);
}
private void setupControlButtons(LinearLayout controlBar,int mode) {
private void setupControlButtons(LinearLayout controlBar, int mode) {
playButton = controlBar.findViewById(R.id.play_button);
ImageView addButton = controlBar.findViewById(R.id.add_button);
ImageView removeButton = controlBar.findViewById(R.id.remove_button);
ImageView slideButton = controlBar.findViewById(R.id.slide_button);
ImageView saveButton = controlBar.findViewById(R.id.save_button);
ImageView eyeButton = controlBar.findViewById(R.id.eye_button);
ImageView settingButton = controlBar.findViewById(R.id.settings_button);
ImageView closeButton = controlBar.findViewById(R.id.close_button);
addButton = controlBar.findViewById(R.id.add_button);
removeButton = controlBar.findViewById(R.id.remove_button);
slideButton = controlBar.findViewById(R.id.slide_button);
saveButton = controlBar.findViewById(R.id.save_button);
eyeButton = controlBar.findViewById(R.id.eye_button);
settingButton = controlBar.findViewById(R.id.settings_button);
closeButton = controlBar.findViewById(R.id.close_button);
if (mode == 1){
if (mode == 1) {
addButton.setVisibility(View.GONE);
removeButton.setVisibility(View.GONE);
slideButton.setVisibility(View.GONE);
@ -336,59 +342,90 @@ public class FloatingViewManager {
public void toggleEventsVisibility(boolean show) {
for (EventWrapper wrapper : new ArrayList<>(runtimeEvents)) {
// 获取事件的视图
View viewToToggle = null;
WindowManager.LayoutParams params = null;
if (wrapper.getType() == EventWrapper.EventType.POINT) {
viewToToggle = EventViewBinder.createTouchPointView(context, String.valueOf(wrapper.getEvent().getId()));
PointEvent pointEvent = (PointEvent) wrapper.getEvent();
View pointView = pointEvent.getView();
if (pointView != null) {
params = (WindowManager.LayoutParams) pointView.getLayoutParams();
if (params != null) {
if (show) {
pointView.setAlpha(1.0f);
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
} else {
pointView.setAlpha(0.0f);
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
windowManager.updateViewLayout(pointView, params);
}
}
} else if (wrapper.getType() == EventWrapper.EventType.SLIDE) {
View startPointView = getView(show, wrapper, context);
SlideEvent slideEvent = (SlideEvent) wrapper.getEvent();
WindowManager.LayoutParams startParams = (WindowManager.LayoutParams) startPointView.getLayoutParams();
if (show) {
startParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
} else {
startParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
View startPointView = slideEvent.getStartPoint().getView();
if (startPointView != null) {
params = (WindowManager.LayoutParams) startPointView.getLayoutParams();
if (params != null) {
if (show) {
startPointView.setAlpha(1.0f);
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
} else {
startPointView.setAlpha(0.0f);
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
windowManager.updateViewLayout(startPointView, params);
}
}
windowManager.updateViewLayout(startPointView, startParams);
continue;
}
if (viewToToggle != null) {
if (show) {
viewToToggle.setAlpha(1.0f);
} else {
viewToToggle.setAlpha(0.0f);
View endPointView = slideEvent.getEndPoint().getView();
if (endPointView != null) {
params = (WindowManager.LayoutParams) endPointView.getLayoutParams();
if (params != null) {
if (show) {
endPointView.setAlpha(1.0f);
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
} else {
endPointView.setAlpha(0.0f);
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
windowManager.updateViewLayout(endPointView, params);
}
}
WindowManager.LayoutParams params = (WindowManager.LayoutParams) viewToToggle.getLayoutParams();
if (show) {
params.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
} else {
params.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
View lineView = slideEvent.getLineView();
if (lineView != null) {
params = (WindowManager.LayoutParams) lineView.getLayoutParams();
if (params != null) {
if (show) {
lineView.setAlpha(1.0f);
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
} else {
lineView.setAlpha(0.0f);
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
windowManager.updateViewLayout(lineView, params);
}
}
windowManager.updateViewLayout(viewToToggle, params);
}
}
}
@NonNull
private View getView(boolean show, EventWrapper wrapper, Context context) {
SlideEvent slideEvent = (SlideEvent) wrapper.getEvent();
View startPointView = EventViewBinder.createTouchPointView(context, String.valueOf(slideEvent.getStartPoint().getId()));
View endPointView = EventViewBinder.createTouchPointView(context, String.valueOf(slideEvent.getEndPoint().getId()));
View lineView = new ConnectingLineView(context);
if (show) {
startPointView.setAlpha(1.0f);
endPointView.setAlpha(1.0f);
lineView.setAlpha(1.0f);
} else {
startPointView.setAlpha(0.0f);
endPointView.setAlpha(0.0f);
lineView.setAlpha(0.0f);
}
return startPointView;
}
private void removeLastEvent() {
if (isMultipleRunning) {
Toast.makeText(context, "请先停止点击", Toast.LENGTH_SHORT).show();
@ -433,6 +470,8 @@ public class FloatingViewManager {
Toast.makeText(context, "多点点击开始", Toast.LENGTH_SHORT).show();
updateTouchPointsBackground(R.drawable.touch_point);
setControlButtonsEnabled(false);
}
private void stopMultiClicking(AutoClickService service) {
@ -440,6 +479,25 @@ public class FloatingViewManager {
Toast.makeText(context, "多点点击停止", Toast.LENGTH_SHORT).show();
updateTouchPointsBackground(R.drawable.ring_has_bg);
setControlButtonsEnabled(true);
}
private void setControlButtonsEnabled(boolean enabled) {
if (addButton != null) addButton.setEnabled(enabled);
if (removeButton != null) removeButton.setEnabled(enabled);
if (slideButton != null) slideButton.setEnabled(enabled);
if (saveButton != null) saveButton.setEnabled(enabled);
if (eyeButton != null) eyeButton.setEnabled(enabled);
if (settingButton != null) settingButton.setEnabled(enabled);
float alpha = enabled ? 1.0f : 0.5f;
if (addButton != null) addButton.setAlpha(alpha);
if (removeButton != null) removeButton.setAlpha(alpha);
if (slideButton != null) slideButton.setAlpha(alpha);
if (saveButton != null) saveButton.setAlpha(alpha);
if (eyeButton != null) eyeButton.setAlpha(alpha);
if (settingButton != null) settingButton.setAlpha(alpha);
}
private void removeEvent(Event event) {
@ -606,7 +664,7 @@ public class FloatingViewManager {
return;
}
Solution newSolution = new Solution(solutionName,mode);
Solution newSolution = new Solution(solutionName, mode);
eventRepository.insertSolutionWithEvents(newSolution, runtimeEvents, new EventRepository.RepositoryCallback<Long>() {
@Override
public void onComplete(Long solutionId) {
@ -782,7 +840,7 @@ public class FloatingViewManager {
}
if (slideEvent.getEndPoint().getView() != null && slideEvent.getEndPoint().getLayoutParams() != null) {
TextView endView = slideEvent.getEndPoint().getView();
TextView endView = slideEvent.getEndPoint().getView();
WindowManager.LayoutParams endParams = slideEvent.getEndPoint().getLayoutParams();
endParams.width = touchPointSize;
endParams.height = touchPointSize;

View File

@ -14,11 +14,12 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/default_actions_container"
android:layout_width="0dp"
android:layout_height="223dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="14dp"
android:layout_marginEnd="20dp"
android:background="@drawable/bg_rounded_rect"
android:paddingBottom="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@ -112,6 +113,18 @@
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/tv_interval_range_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="36dp"
android:layout_marginTop="4dp"
android:text="范围: 40ms - 10000ms"
android:textColor="@color/gray"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/input_interval" />
<ImageView
android:id="@+id/duration_icon"
android:layout_width="20dp"
@ -130,7 +143,7 @@
android:textColor="@color/white"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="@+id/tv_interval_label"
app:layout_constraintTop_toBottomOf="@id/input_interval" />
app:layout_constraintTop_toBottomOf="@id/tv_interval_range_hint" />
<TextView
android:id="@+id/tv_preview_label"
@ -140,6 +153,7 @@
android:text="@string/preview"
android:textColor="@color/blue"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/tv_swipe_duration_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_swipe_duration_label" />
@ -199,6 +213,17 @@
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/tv_duration_range_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="36dp"
android:layout_marginTop="4dp"
android:text="范围: 100ms - 2000ms"
android:textColor="@color/gray"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/input_with_unit_container" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
@ -375,6 +400,20 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/save"
android:layout_width="150dp"
android:layout_height="54dp"
android:layout_marginTop="18dp"
android:background="@drawable/btn_border_background"
android:backgroundTint="#0BC4FC"
android:text="Save"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="21sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/default_advanced_container" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,68 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="38dp"
android:layout_height="wrap_content"
android:background="@drawable/control_bar_bg"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingHorizontal="10dp">
<ImageView
android:id="@+id/play_button"
android:layout_width="13dp"
android:layout_height="13dp"
android:layout_marginTop="15dp"
android:layout_marginBottom="16dp"
android:src="@drawable/play" />
<ImageView
android:id="@+id/save_button"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginBottom="16dp"
android:src="@drawable/save" />
<ImageView
android:id="@+id/eye_button"
android:layout_width="16dp"
android:layout_height="9dp"
android:layout_marginBottom="16dp"
android:src="@drawable/eye" />
<ImageView
android:id="@+id/settings_button"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginBottom="9dp"
android:src="@drawable/control_setting" />
</LinearLayout>
<ImageView
android:id="@+id/cut_off"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="10dp"
android:scaleType="fitXY"
android:src="@drawable/cut_off_line" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingHorizontal="10dp">
<ImageView
android:id="@+id/close_button"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginBottom="11dp"
android:src="@drawable/cancel" />
</LinearLayout>
</LinearLayout>