添加保存页面

This commit is contained in:
lihongwei 2025-06-23 11:44:30 +08:00
parent 6cf551aa6c
commit 6aa0ee4cb0
12 changed files with 563 additions and 3 deletions

View File

@ -18,6 +18,13 @@
android:supportsRtl="true"
android:theme="@style/Theme.AutoClicker"
tools:targetApi="31">
<activity
android:name=".ui.activity.floating.TransparentFloatingActivity"
android:theme="@style/Theme.TransparentOverlay"
android:launchMode="singleTop"
android:excludeFromRecents="true"
android:taskAffinity=""
android:exported="false" />
<activity
android:name=".ui.activity.main.QuestionActivity"
android:exported="false" />
@ -72,11 +79,11 @@
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
<service
android:name=".service.ForegroundService"
android:enabled="true"

View File

@ -0,0 +1,109 @@
package com.auto.clicker.autoclicker.ui.activity.floating; // 替换为你的实际包名
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; // 新增
import android.view.View;
import android.view.WindowManager; // 新增用于设置 Window 属性
import android.widget.ImageView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; // 继续使用 AppCompatActivity
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.viewpager2.widget.ViewPager2; // 新增
import com.auto.clicker.autoclicker.R;
import com.auto.clicker.autoclicker.ui.adapter.floating.TabPagerAdapter;
import com.auto.clicker.autoclicker.ui.fragment.floating.LoadScriptFragment;
import com.auto.clicker.autoclicker.ui.fragment.floating.SaveScriptFragment;
import com.google.android.material.tabs.TabLayout; // 新增
import com.google.android.material.tabs.TabLayoutMediator; // 新增
public class TransparentFloatingActivity extends AppCompatActivity {
private static final String TAG = "TransparentFloatingAct";
public static final String ACTION_CLOSE_FLOATING_TAB_DIALOG = "com.auto.autoclicker.CLOSE_FLOATING_TAB_DIALOG";
// 不再需要 FloatingTabDialogManager 实例直接在这里处理 UI
private View dialogContentView; // 承载整个弹窗内容的 View
// 接收关闭指令的广播接收器
private BroadcastReceiver closeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_CLOSE_FLOATING_TAB_DIALOG.equals(intent.getAction())) {
Log.d(TAG, "收到关闭浮动标签页弹窗的广播");
finish(); // 关闭自身 Activity
}
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置 Activity 的窗口属性使其看起来像一个对话框
// 注意这里的设置可能与主题中的部分重复但可以作为额外的控制
WindowManager.LayoutParams params = getWindow().getAttributes();
params.gravity = android.view.Gravity.CENTER; // 确保居中
params.flags &= ~WindowManager.LayoutParams.FLAG_DIM_BEHIND; // 移除背景变暗如果主题没有完全实现
params.width = WindowManager.LayoutParams.WRAP_CONTENT; // 根据内容调整宽度
params.height = WindowManager.LayoutParams.WRAP_CONTENT; // 根据内容调整高度
getWindow().setAttributes(params);
getWindow().setBackgroundDrawableResource(android.R.color.transparent); // 确保背景透明
// 加载你的弹窗布局
setContentView(R.layout.floating_tab_dialog);
dialogContentView = findViewById(android.R.id.content); // 获取根视图如果你的布局没有特定ID
Log.d(TAG, "TransparentFloatingActivity onCreate");
// 初始化 TabLayout ViewPager2
TabLayout tabLayout = findViewById(R.id.tab_layout);
ViewPager2 viewPager = findViewById(R.id.view_pager);
TabPagerAdapter pagerAdapter = new TabPagerAdapter(this); // 传入当前的 Activity 作为 FragmentActivity
pagerAdapter.addFragment(new SaveScriptFragment(), "选项卡一");
pagerAdapter.addFragment(new LoadScriptFragment(), "选项卡二");
// 添加更多 Fragment...
viewPager.setAdapter(pagerAdapter);
new TabLayoutMediator(tabLayout, viewPager,
(tab, position) -> 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);
// }
}

View File

@ -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<Fragment> fragmentList = new ArrayList<>();
private final List<String> 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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.activity.floating.TransparentFloatingActivity">
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="321dp"
android:layout_height="386dp"
android:background="@drawable/rounded_edittext_dotted_background"
android:padding="8dp">
<LinearLayout
android:id="@+id/tab_buttons_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:weightSum="2"> <Button
android:id="@+id/tab_one_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="选项卡一"
android:background="?attr/selectableItemBackground"
android:textColor="@color/black" /> <Button
android:id="@+id/tab_two_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="选项卡二"
android:background="?attr/selectableItemBackground"
android:textColor="@color/gray" /> <ImageView
android:id="@+id/close_dialog_button"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@android:drawable/ic_menu_close_clear_cancel"
android:clickable="true"
android:focusable="true"
android:contentDescription="关闭弹窗"
android:layout_marginStart="8dp" /> </LinearLayout>
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp">
<include layout="@layout/save_script_page" android:id="@+id/page_one_view"/>
<include layout="@layout/load_script_page" android:id="@+id/page_two_view" android:visibility="gone"/>
</FrameLayout>
</LinearLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.floating.LoadScriptFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</FrameLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.floating.SaveScriptFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</FrameLayout>

View File

@ -6,5 +6,19 @@
<item name="android:windowBackground">@drawable/activity_background</item>
</style>
<style name="Theme.TransparentOverlay" parent="Theme.Material3.DayNight.Dialog">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:gravity">center</item>
<item name="android:windowAnimationStyle">@android:style/Animation</item>
</style>
<style name="Theme.AutoClicker" parent="Base.Theme.AutoClicker" />
</resources>