From 6aa0ee4cb02b31bf934d5a4557bfd1082a9a65e7 Mon Sep 17 00:00:00 2001 From: lihongwei Date: Mon, 23 Jun 2025 11:44:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BF=9D=E5=AD=98=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 9 +- .../floating/TransparentFloatingActivity.java | 109 +++++++++++++ .../ui/adapter/floating/TabPagerAdapter.java | 39 +++++ .../fragment/floating/LoadScriptFragment.java | 66 ++++++++ .../fragment/floating/SaveScriptFragment.java | 66 ++++++++ .../util/FloatingTabDialogManager.java | 150 ++++++++++++++++++ .../autoclicker/view/FloatingViewManager.java | 25 ++- .../layout/activity_transparent_floating.xml | 10 ++ .../main/res/layout/floating_tab_dialog.xml | 50 ++++++ .../main/res/layout/fragment_load_script.xml | 14 ++ .../main/res/layout/fragment_save_script.xml | 14 ++ app/src/main/res/values/themes.xml | 14 ++ 12 files changed, 563 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/auto/clicker/autoclicker/ui/activity/floating/TransparentFloatingActivity.java create mode 100644 app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/floating/TabPagerAdapter.java create mode 100644 app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/LoadScriptFragment.java create mode 100644 app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/SaveScriptFragment.java create mode 100644 app/src/main/java/com/auto/clicker/autoclicker/util/FloatingTabDialogManager.java create mode 100644 app/src/main/res/layout/activity_transparent_floating.xml create mode 100644 app/src/main/res/layout/floating_tab_dialog.xml create mode 100644 app/src/main/res/layout/fragment_load_script.xml create mode 100644 app/src/main/res/layout/fragment_save_script.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e03c3e2..00db0e7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,6 +18,13 @@ android:supportsRtl="true" android:theme="@style/Theme.AutoClicker" tools:targetApi="31"> + @@ -72,11 +79,11 @@ + - tab.setText(pagerAdapter.getPageTitle(position)) + ).attach(); + + // 假设你在 floating_tab_dialog.xml 中有一个关闭按钮,ID 为 close_dialog_button + ImageView closeDialogButton = findViewById(R.id.close_dialog_button); + if (closeDialogButton != null) { + closeDialogButton.setOnClickListener(v -> { + // 点击关闭按钮时,直接结束这个 Activity + finish(); + }); + } + + + // 注册广播接收器 + LocalBroadcastManager.getInstance(this).registerReceiver(closeReceiver, + new IntentFilter(ACTION_CLOSE_FLOATING_TAB_DIALOG)); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + Log.d(TAG, "TransparentFloatingActivity onDestroy"); + // 注销广播接收器 + LocalBroadcastManager.getInstance(this).unregisterReceiver(closeReceiver); + } + + // 移除这个方法,因为现在 Activity 自己会关闭 + // public static void sendCloseBroadcast(Context context) { + // Intent intent = new Intent(ACTION_CLOSE_FLOATING_TAB_DIALOG); + // LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + // } +} \ No newline at end of file diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/floating/TabPagerAdapter.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/floating/TabPagerAdapter.java new file mode 100644 index 0000000..ca7f354 --- /dev/null +++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/adapter/floating/TabPagerAdapter.java @@ -0,0 +1,39 @@ +package com.auto.clicker.autoclicker.ui.adapter.floating; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import java.util.ArrayList; +import java.util.List; + +public class TabPagerAdapter extends FragmentStateAdapter { + + private final List fragmentList = new ArrayList<>(); + private final List fragmentTitleList = new ArrayList<>(); + + public TabPagerAdapter(@NonNull FragmentActivity fragmentActivity) { // 保持接收 FragmentActivity + super(fragmentActivity); + } + + public void addFragment(Fragment fragment, String title) { + fragmentList.add(fragment); + fragmentTitleList.add(title); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + return fragmentList.get(position); + } + + @Override + public int getItemCount() { + return fragmentList.size(); + } + + public CharSequence getPageTitle(int position) { + return fragmentTitleList.get(position); + } +} diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/LoadScriptFragment.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/LoadScriptFragment.java new file mode 100644 index 0000000..49f1444 --- /dev/null +++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/LoadScriptFragment.java @@ -0,0 +1,66 @@ +package com.auto.clicker.autoclicker.ui.fragment.floating; + +import android.os.Bundle; + +import androidx.fragment.app.Fragment; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.auto.clicker.autoclicker.R; + +/** + * A simple {@link Fragment} subclass. + * Use the {@link LoadScriptFragment#newInstance} factory method to + * create an instance of this fragment. + */ +public class LoadScriptFragment extends Fragment { + + // TODO: Rename parameter arguments, choose names that match + // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER + private static final String ARG_PARAM1 = "param1"; + private static final String ARG_PARAM2 = "param2"; + + // TODO: Rename and change types of parameters + private String mParam1; + private String mParam2; + + public LoadScriptFragment() { + // Required empty public constructor + } + + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @param param1 Parameter 1. + * @param param2 Parameter 2. + * @return A new instance of fragment LoadScriptFragment. + */ + // TODO: Rename and change types and number of parameters + public static LoadScriptFragment newInstance(String param1, String param2) { + LoadScriptFragment fragment = new LoadScriptFragment(); + Bundle args = new Bundle(); + args.putString(ARG_PARAM1, param1); + args.putString(ARG_PARAM2, param2); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mParam1 = getArguments().getString(ARG_PARAM1); + mParam2 = getArguments().getString(ARG_PARAM2); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_load_script, container, false); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/SaveScriptFragment.java b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/SaveScriptFragment.java new file mode 100644 index 0000000..a2f1471 --- /dev/null +++ b/app/src/main/java/com/auto/clicker/autoclicker/ui/fragment/floating/SaveScriptFragment.java @@ -0,0 +1,66 @@ +package com.auto.clicker.autoclicker.ui.fragment.floating; + +import android.os.Bundle; + +import androidx.fragment.app.Fragment; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.auto.clicker.autoclicker.R; + +/** + * A simple {@link Fragment} subclass. + * Use the {@link SaveScriptFragment#newInstance} factory method to + * create an instance of this fragment. + */ +public class SaveScriptFragment extends Fragment { + + // TODO: Rename parameter arguments, choose names that match + // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER + private static final String ARG_PARAM1 = "param1"; + private static final String ARG_PARAM2 = "param2"; + + // TODO: Rename and change types of parameters + private String mParam1; + private String mParam2; + + public SaveScriptFragment() { + // Required empty public constructor + } + + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @param param1 Parameter 1. + * @param param2 Parameter 2. + * @return A new instance of fragment SaveScriptFragment. + */ + // TODO: Rename and change types and number of parameters + public static SaveScriptFragment newInstance(String param1, String param2) { + SaveScriptFragment fragment = new SaveScriptFragment(); + Bundle args = new Bundle(); + args.putString(ARG_PARAM1, param1); + args.putString(ARG_PARAM2, param2); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mParam1 = getArguments().getString(ARG_PARAM1); + mParam2 = getArguments().getString(ARG_PARAM2); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_save_script, container, false); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/auto/clicker/autoclicker/util/FloatingTabDialogManager.java b/app/src/main/java/com/auto/clicker/autoclicker/util/FloatingTabDialogManager.java new file mode 100644 index 0000000..e4256ad --- /dev/null +++ b/app/src/main/java/com/auto/clicker/autoclicker/util/FloatingTabDialogManager.java @@ -0,0 +1,150 @@ +package com.auto.clicker.autoclicker.util; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.os.Build; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.auto.clicker.autoclicker.R; + +public class FloatingTabDialogManager { + + private static final String TAG = "FloatingTabDialogMgr"; + private final Context originalContext; // 保存原始的 Service Context + private final WindowManager windowManager; + private LinearLayout floatingDialogView; + private WindowManager.LayoutParams floatingDialogParams; + + private Button tabOneButton; + private Button tabTwoButton; + private FrameLayout contentFrame; + private View pageOneView; + private View pageTwoView; + + public FloatingTabDialogManager(Context context) { + this.originalContext = context; // 保存原始 Context + this.windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + } + + public void showFloatingTabDialog() { + if (floatingDialogView != null) { + removeFloatingTabDialog(); // 如果已经显示,先移除 + } + + // --- 关键修改:使用 ContextThemeWrapper 包裹 Service Context --- + Context themedContext = new ContextThemeWrapper(originalContext, R.style.Theme_AutoClicker); + // 或者如果你想用 Material3 的默认主题,但你的 Theme_AutoClicker 已经继承了 Material3,那用它就行 + // Context themedContext = new ContextThemeWrapper(originalContext, R.style.Theme_Material3_DayNight_NoActionBar); // 也可以直接用这个 + + floatingDialogView = (LinearLayout) LayoutInflater.from(themedContext).inflate(R.layout.floating_tab_dialog, null); + // --- 关键修改结束 --- + + floatingDialogParams = createFloatingDialogParams(); + + // 获取视图组件 + tabOneButton = floatingDialogView.findViewById(R.id.tab_one_button); + tabTwoButton = floatingDialogView.findViewById(R.id.tab_two_button); + contentFrame = floatingDialogView.findViewById(R.id.content_frame); + pageOneView = floatingDialogView.findViewById(R.id.page_one_view); + pageTwoView = floatingDialogView.findViewById(R.id.page_two_view); + ImageView closeDialogButton = floatingDialogView.findViewById(R.id.close_dialog_button); + + // 设置标签按钮的点击事件 + tabOneButton.setOnClickListener(v -> showPage(0)); + tabTwoButton.setOnClickListener(v -> showPage(1)); + + // 设置关闭按钮的点击事件 + if (closeDialogButton != null) { + closeDialogButton.setOnClickListener(v -> { + removeFloatingTabDialog(); // 点击关闭按钮时,移除弹窗 + }); + } + + // 默认显示第一个页面 + showPage(0); + + try { + windowManager.addView(floatingDialogView, floatingDialogParams); + Log.d(TAG, "浮动标签页弹窗已显示"); + } catch (WindowManager.BadTokenException e) { + Log.e(TAG, "无法添加浮动标签页弹窗,可能是权限问题或上下文无效", e); + } catch (SecurityException e) { + Log.e(TAG, "需要悬浮窗权限才能显示浮动弹窗", e); + // 引导用户去开启权限,如果你在 FloatingViewManager 中已经处理了,这里可以省略 + // Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + // Uri.parse("package:" + context.getPackageName())); + // intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // context.startActivity(intent); + } + } + + private void showPage(int pageIndex) { + // 确保所有视图都已正确加载(例如,通过 inflated 后的 findViewById) + if (pageOneView == null || pageTwoView == null) { + Log.e(TAG, "页面视图未初始化!"); + return; + } + + // 隐藏所有页面 + pageOneView.setVisibility(View.GONE); + pageTwoView.setVisibility(View.GONE); + + // 重置所有标签按钮的颜色/背景(如果需要视觉反馈) + // 注意:这里使用 getContext().getResources().getColor() 是在 themedContext 的上下文中获取颜色 + // 如果你的颜色定义在 themes.xml 或 colors.xml 中,并且依赖于主题属性,那么需要确保主题是正确的 + tabOneButton.setTextColor(originalContext.getResources().getColor(android.R.color.darker_gray)); // 用 originalContext 或 themedContext + tabTwoButton.setTextColor(originalContext.getResources().getColor(android.R.color.darker_gray)); // 最好用 themedContext + + // 显示选定页面并更新标签样式 + switch (pageIndex) { + case 0: + pageOneView.setVisibility(View.VISIBLE); + tabOneButton.setTextColor(originalContext.getResources().getColor(android.R.color.black)); // 选中颜色 + break; + case 1: + pageTwoView.setVisibility(View.VISIBLE); + tabTwoButton.setTextColor(originalContext.getResources().getColor(android.R.color.black)); // 选中颜色 + break; + // 添加更多页面 + } + } + + public void removeFloatingTabDialog() { + if (floatingDialogView != null) { + try { + windowManager.removeView(floatingDialogView); + floatingDialogView = null; + Log.d(TAG, "浮动标签页弹窗已移除"); + } catch (IllegalArgumentException e) { + Log.w(TAG, "尝试移除不存在的视图", e); + } + } + } + + private WindowManager.LayoutParams createFloatingDialogParams() { + // 创建浮动弹窗的参数 + int overlayType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : + WindowManager.LayoutParams.TYPE_PHONE; + + WindowManager.LayoutParams params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, // 宽度自适应内容 + WindowManager.LayoutParams.WRAP_CONTENT, // 高度自适应内容 + overlayType, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | // 如果希望点击弹窗外部能穿透,则保持 + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, // 确保它能接收触摸事件 + PixelFormat.TRANSLUCENT + ); + params.gravity = Gravity.CENTER; // 居中显示 + return params; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingViewManager.java b/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingViewManager.java index afeab2a..08b0e18 100644 --- a/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingViewManager.java +++ b/app/src/main/java/com/auto/clicker/autoclicker/view/FloatingViewManager.java @@ -9,6 +9,7 @@ import android.content.IntentFilter; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Point; +import android.net.Uri; import android.os.Build; import android.provider.Settings; import android.text.InputType; @@ -38,6 +39,8 @@ import com.auto.clicker.autoclicker.data.SlideEvent; import com.auto.clicker.autoclicker.dialog.ScriptManagerDialog; import com.auto.clicker.autoclicker.service.AutoClickService; import com.auto.clicker.autoclicker.service.ForegroundService; +import com.auto.clicker.autoclicker.ui.activity.floating.TransparentFloatingActivity; +import com.auto.clicker.autoclicker.util.FloatingTabDialogManager; import com.auto.clicker.autoclicker.util.PrefUtils; import com.auto.clicker.autoclicker.util.ScreenUtils; import com.auto.clicker.autoclicker.util.ViewUtils; @@ -66,12 +69,15 @@ public class FloatingViewManager { private boolean areEventsVisible = true; + private FloatingTabDialogManager floatingTabDialogManager; + public FloatingViewManager(Context context) { this.context = context; this.windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Point screenSize = ScreenUtils.getScreenSize(context); this.screenWidth = screenSize.x; this.screenHeight = screenSize.y; + this.floatingTabDialogManager = new FloatingTabDialogManager(context); registerBroadcastReceiver(); } @@ -199,9 +205,20 @@ public class FloatingViewManager { addButton.setOnClickListener(v -> addPointEvent()); removeButton.setOnClickListener(v -> removeLastEvent()); slideButton.setOnClickListener(v -> addSlideEvent()); + saveButton.setOnClickListener(v -> { - ScriptManagerDialog dialog = new ScriptManagerDialog(context); - dialog.show(); // 显示弹窗 + // 直接调用 FloatingTabDialogManager 来显示悬浮弹窗 + // 记得要检查悬浮窗权限! + if (!Settings.canDrawOverlays(context)) { + Toast.makeText(context, "需要悬浮窗权限才能显示弹窗", Toast.LENGTH_LONG).show(); + // 引导用户去开启权限 + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + context.getPackageName())); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } else { + floatingTabDialogManager.showFloatingTabDialog(); + } }); eyeButton.setOnClickListener(v -> { @@ -493,6 +510,10 @@ public class FloatingViewManager { multipleControlBarView = null; } + if (floatingTabDialogManager != null) { + floatingTabDialogManager.removeFloatingTabDialog(); + } + isMultipleRunning = false; Log.d(TAG, "悬浮窗已移除"); } catch (Exception e) { diff --git a/app/src/main/res/layout/activity_transparent_floating.xml b/app/src/main/res/layout/activity_transparent_floating.xml new file mode 100644 index 0000000..1ba554e --- /dev/null +++ b/app/src/main/res/layout/activity_transparent_floating.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/floating_tab_dialog.xml b/app/src/main/res/layout/floating_tab_dialog.xml new file mode 100644 index 0000000..89045e9 --- /dev/null +++ b/app/src/main/res/layout/floating_tab_dialog.xml @@ -0,0 +1,50 @@ + + + +