diff --git a/mymessagemanager/build.properties b/mymessagemanager/build.properties
index 3ec3fb9..4b30f2a 100644
--- a/mymessagemanager/build.properties
+++ b/mymessagemanager/build.properties
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
-#Tue Feb 10 19:09:02 GMT 2026
+#Tue Feb 10 21:19:54 GMT 2026
stageCount=7
libraryProject=
baseVersion=15.12
publishVersion=15.12.6
-buildCount=1
+buildCount=24
baseBetaVersion=15.12.7
diff --git a/mymessagemanager/src/main/AndroidManifest.xml b/mymessagemanager/src/main/AndroidManifest.xml
index 7647205..27f0e1f 100644
--- a/mymessagemanager/src/main/AndroidManifest.xml
+++ b/mymessagemanager/src/main/AndroidManifest.xml
@@ -65,11 +65,9 @@
android:requestLegacyExternalStorage="true"
android:networkSecurityConfig="@xml/network_security_config">
-
+
-
+
@@ -95,11 +93,9 @@
-
+
-
+
+
+
-
+
\ No newline at end of file
diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/AppSettingsActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/AppSettingsActivity.java
index 69d8426..bdfb4ad 100644
--- a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/AppSettingsActivity.java
+++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/AppSettingsActivity.java
@@ -147,4 +147,9 @@ public class AppSettingsActivity extends WinBoLLActivity implements IWinBoLLActi
}, mszProtectModerRefuseChars);
dlg.show();
}
+
+ public void onTTSFloatSettingsActivity(View view) {
+ Intent intent = new Intent(this, TTSFloatSettingsActivity.class);
+ startActivity(intent);
+ }
}
diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/TTSFloatSettingsActivity.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/TTSFloatSettingsActivity.java
new file mode 100644
index 0000000..829b6bc
--- /dev/null
+++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/activitys/TTSFloatSettingsActivity.java
@@ -0,0 +1,23 @@
+package cc.winboll.studio.mymessagemanager.activitys;
+
+import android.app.Activity;
+import android.os.Bundle;
+import cc.winboll.studio.mymessagemanager.R;
+
+/**
+ * @Author 豆包&ZhanGSKen
+ * @Date 2026/02/11 03:45
+ * @Describe TTS悬浮窗设置类(使用可拖动自定义控件)
+ */
+public class TTSFloatSettingsActivity extends Activity {
+
+ public static final String TAG = "TTSFloatSettingsActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // 直接加载包含自定义拖动控件的布局
+ setContentView(R.layout.activity_ttsfloatsettings);
+ }
+}
+
diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/TextToSpeechUtil.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/TextToSpeechUtil.java
index 70f6938..3ef3272 100644
--- a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/TextToSpeechUtil.java
+++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/utils/TextToSpeechUtil.java
@@ -1,10 +1,5 @@
package cc.winboll.studio.mymessagemanager.utils;
-/**
- * @Author ZhanGSKen
- * @Date 2024/07/03 10:27:46
- * @Describe TTS语音播放工具类
- */
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
@@ -17,14 +12,13 @@ import android.widget.LinearLayout;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.mymessagemanager.R;
import cc.winboll.studio.mymessagemanager.beans.TTSSpeakTextBean;
+import cc.winboll.studio.mymessagemanager.views.DraggableView;
import java.util.ArrayList;
public class TextToSpeechUtil {
public static final String TAG = "TextToSpeechUtil";
-
public static final String UNIQUE_ID = "UNIQUE_ID";
-
static TextToSpeechUtil _mTextToSpeechUtil;
View mView;
@@ -35,7 +29,6 @@ public class TextToSpeechUtil {
TextToSpeechUtil(Context context) {
mContext = context;
- // 获取WindowManager
mWindowManager = (WindowManager) mContext.getSystemService(mContext.WINDOW_SERVICE);
}
@@ -46,65 +39,49 @@ public class TextToSpeechUtil {
return _mTextToSpeechUtil;
}
- //
- // 播放 TTS 语音队列
- //
public void speekTTSList(final ArrayList listTTSSpeakTextBean) {
- // 重置播放退出标志位
isExist = false;
- // 开始播放
if (mTextToSpeech == null) {
- //ToastUtils.show("mTextToSpeech == null");
- // 创建TextToSpeech实例
mTextToSpeech = new TextToSpeech(mContext, new TextToSpeech.OnInitListener() {
- @Override
- public void onInit(int i) {
- if (i == TextToSpeech.SUCCESS) {
- speekTTSList(listTTSSpeakTextBean);
- } else {
- LogUtils.d(TAG, "TTS init failed : " + Integer.toString(i) + ". The app [https://play.google.com/store/apps/details?id=com.google.android.tts] maybe fix this TTS probrem. ");
- }
- }
- });
+ @Override
+ public void onInit(int i) {
+ if (i == TextToSpeech.SUCCESS) {
+ speekTTSList(listTTSSpeakTextBean);
+ } else {
+ LogUtils.d(TAG, "TTS init failed : " + Integer.toString(i) + ". The app [https://play.google.com/store/apps/details?id=com.google.android.tts] maybe fix this TTS probrem. ");
+ }
+ }
+ });
mTextToSpeech.setOnUtteranceProgressListener(mUtteranceProgressListener);
} else {
if (mTextToSpeech != null && listTTSSpeakTextBean != null && listTTSSpeakTextBean.size() > 0) {
- // 清理过期的悬浮窗
if (mWindowManager != null && mView != null) {
try {
mWindowManager.removeView(mView);
mView = null;
- } catch(Exception e) {
+ } catch (Exception e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
-
- // 显示悬浮窗
- initWindow();
-
- // 播放 TTS 语音
- //
- //ToastUtils.show("initWindow done.");
- // 设置延迟间隔
- int nDelay = listTTSSpeakTextBean.get(0).mnDelay;
+
+ initWindow(); // 已同步尺寸和位置
+
+ int nDelay = listTTSSpeakTextBean.get(0).mnDelay;
try {
Thread.sleep(nDelay);
} catch (InterruptedException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
- //ToastUtils.show("Delay done.");
+
for (int speakPosition = 0; speakPosition < listTTSSpeakTextBean.size() && !isExist; speakPosition++) {
- // 播放语音
String szSpeakContent = listTTSSpeakTextBean.get(speakPosition).mszSpeakContent;
isExist = (listTTSSpeakTextBean.size() - 2 < speakPosition);
- //ToastUtils.show("for isExist is : " + Boolean.toString(isExist));
if (speakPosition == 0) {
mTextToSpeech.speak(szSpeakContent, TextToSpeech.QUEUE_FLUSH, null, UNIQUE_ID);
} else {
mTextToSpeech.speak(szSpeakContent, TextToSpeech.QUEUE_ADD, null, UNIQUE_ID);
}
- //ToastUtils.show("mTextToSpeech.speak");
}
}
}
@@ -119,8 +96,6 @@ public class TextToSpeechUtil {
@Override
public void onDone(String utteranceId) {
LogUtils.d(TAG, "播放结束");
- //ToastUtils.show("isExist is : " + Boolean.toString(isExist));
- // 关闭悬浮窗
if (isExist && mWindowManager != null && mView != null) {
LogUtils.d(TAG, "关闭悬浮窗");
mWindowManager.removeView(mView);
@@ -133,54 +108,51 @@ public class TextToSpeechUtil {
}
};
-
- //
- // 初始化 TTS 悬浮窗
- //
private void initWindow() {
- //ToastUtils.show("initWindow");
- // 创建布局参数
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- //这里需要进行不同的设置
+
+ // 窗口类型适配
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
params.type = WindowManager.LayoutParams.TYPE_PHONE;
}
- //设置透明度
+
+ // 基础配置
params.alpha = 0.9f;
- //设置内部视图对齐方式
- params.gravity = Gravity.RIGHT | Gravity.BOTTOM;
- //窗口的右上角角坐标
- params.x = 20;
- params.y = 20;
- //是指定窗口的像素格式为 RGBA_8888。
- //使用 RGBA_8888 像素格式的窗口可以在保持高质量图像的同时实现透明度效果。
params.format = PixelFormat.RGBA_8888;
- //设置窗口的宽高,这里为自动
- params.width = WindowManager.LayoutParams.WRAP_CONTENT;
- params.height = WindowManager.LayoutParams.WRAP_CONTENT;
- //这段非常重要,是后续是否穿透点击的关键
- params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE //表示悬浮窗口不需要获取焦点,这样用户点击悬浮窗口以外的区域,就不需要关闭悬浮窗口。
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//表示悬浮窗口不会阻塞事件传递,即用户点击悬浮窗口以外的区域时,事件会传递给后面的窗口处理。
- //这里的引入布局文件的方式,也可以动态添加控件
+ params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ params.gravity = Gravity.LEFT | Gravity.TOP; // 与保存的左上角坐标匹配
+
+ // 核心修改1:同步DraggableView保存的尺寸(宽高完全一致)
+ int[] savedSize = DraggableView.getLastViewSize(mContext);
+ params.width = savedSize[0]; // 同步宽度
+ params.height = savedSize[1]; // 同步高度
+
+ // 核心修改2:同步DraggableView保存的位置
+ int[] savedPosition = DraggableView.getLastPosition(mContext);
+ params.x = savedPosition[0]; // 同步X坐标
+ params.y = savedPosition[1]; // 同步Y坐标
+
+ // 加载布局(view_tts_back.xml与DraggableView一致,确保样式统一)
mView = View.inflate(mContext, R.layout.view_tts_back, null);
LinearLayout llMain = mView.findViewById(R.id.viewttsbackLinearLayout1);
- llMain.setOnClickListener(new View.OnClickListener(){
+ llMain.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ isExist = true;
+ if (mTextToSpeech != null) {
+ mTextToSpeech.stop();
+ }
+ if (mWindowManager != null && mView != null) {
+ mWindowManager.removeView(mView);
+ mView = null;
+ }
+ }
+ });
- @Override
- public void onClick(View view) {
- //ToastUtils.show("onClick");
- isExist = true;
- if (mTextToSpeech != null) {
- mTextToSpeech.stop();
- }
- if (mWindowManager != null && mView != null) {
- mWindowManager.removeView(mView);
- mView = null;
- }
- }
- });
mWindowManager.addView(mView, params);
}
}
+
diff --git a/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/DraggableView.java b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/DraggableView.java
new file mode 100644
index 0000000..61d87bf
--- /dev/null
+++ b/mymessagemanager/src/main/java/cc/winboll/studio/mymessagemanager/views/DraggableView.java
@@ -0,0 +1,190 @@
+package cc.winboll.studio.mymessagemanager.views;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+import cc.winboll.studio.mymessagemanager.R;
+
+public class DraggableView extends FrameLayout {
+ // SP配置常量(新增尺寸保存键)
+ private static final String SP_NAME = "TTS_FLOAT_DRAG_CONFIG";
+ private static final String KEY_LEFT = "drag_view_left";
+ private static final String KEY_TOP = "drag_view_top";
+ private static final String KEY_WIDTH = "drag_view_width"; // 新增:保存布局宽度
+ private static final String KEY_HEIGHT = "drag_view_height"; // 新增:保存布局高度
+
+ // 位置/尺寸变量
+ private int viewLeft;
+ private int viewTop;
+ private int viewWidth;
+ private int viewHeight;
+ private int screenWidth;
+ private int screenHeight;
+ // 拖动相关
+ private float downX;
+ private float downY;
+ private boolean isDragging = false;
+
+ // 构造方法
+ public DraggableView(Context context) {
+ super(context);
+ init();
+ }
+
+ public DraggableView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public DraggableView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ LayoutInflater.from(getContext()).inflate(R.layout.view_tts_back, this, true);
+ DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ screenWidth = metrics.widthPixels;
+ screenHeight = metrics.heightPixels;
+
+ getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ ViewTreeObserver currentVto = getViewTreeObserver();
+ if (currentVto.isAlive()) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ currentVto.removeOnGlobalLayoutListener(this);
+ } else {
+ currentVto.removeGlobalOnLayoutListener(this);
+ }
+ }
+ // 获取布局实际宽高
+ viewWidth = getMeasuredWidth();
+ viewHeight = getMeasuredHeight();
+ // 保存尺寸到SP(新增)
+ saveViewSize();
+ // 初始化位置
+ initPosition();
+ updateViewPosition();
+ }
+ });
+ }
+
+ // 初始化位置(不变)
+ private void initPosition() {
+ SharedPreferences sp = getContext().getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
+ int defaultLeft = screenWidth - viewWidth;
+ int defaultTop = screenHeight - viewHeight;
+ viewLeft = sp.getInt(KEY_LEFT, defaultLeft);
+ viewTop = sp.getInt(KEY_TOP, defaultTop);
+ checkBoundary();
+ }
+
+ // 新增:保存布局尺寸到SP
+ private void saveViewSize() {
+ if (viewWidth > 0 && viewHeight > 0) {
+ SharedPreferences sp = getContext().getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
+ sp.edit()
+ .putInt(KEY_WIDTH, viewWidth)
+ .putInt(KEY_HEIGHT, viewHeight)
+ .apply();
+ }
+ }
+
+ // 新增:公共静态方法 - 查询最后保存的布局尺寸
+ public static int[] getLastViewSize(Context context) {
+ SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
+ // 默认尺寸:120x120像素(与view_tts_back.xml示例尺寸一致,避免无值时异常)
+ int defaultWidth = dp2px(context, 120);
+ int defaultHeight = dp2px(context, 120);
+ // 从SP读取尺寸(无值则用默认)
+ int width = sp.getInt(KEY_WIDTH, defaultWidth);
+ int height = sp.getInt(KEY_HEIGHT, defaultHeight);
+ return new int[]{width, height};
+ }
+
+ // 新增:dp转px工具方法(确保默认尺寸适配不同屏幕)
+ private static int dp2px(Context context, float dpValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dpValue * scale + 0.5f);
+ }
+
+ // 原有方法(checkBoundary、updateViewPosition、savePosition、onTouchEvent、getLastPosition)保持不变
+ private void checkBoundary() {
+ viewLeft = Math.max(0, viewLeft);
+ viewTop = Math.max(0, viewTop);
+ viewLeft = Math.min(screenWidth - viewWidth, viewLeft);
+ viewTop = Math.min(screenHeight - viewHeight, viewTop);
+ }
+
+ private void updateViewPosition() {
+ LayoutParams params = (LayoutParams) getLayoutParams();
+ if (params != null) {
+ params.leftMargin = viewLeft;
+ params.topMargin = viewTop;
+ setLayoutParams(params);
+ }
+ }
+
+ private void savePosition() {
+ SharedPreferences sp = getContext().getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
+ sp.edit()
+ .putInt(KEY_LEFT, viewLeft)
+ .putInt(KEY_TOP, viewTop)
+ .apply();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (viewWidth == 0 || viewHeight == 0) return super.onTouchEvent(event);
+
+ float rawX = event.getRawX();
+ float rawY = event.getRawY();
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ isDragging = true;
+ downX = rawX - viewLeft;
+ downY = rawY - viewTop;
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (isDragging) {
+ viewLeft = (int) (rawX - downX);
+ viewTop = (int) (rawY - downY);
+ checkBoundary();
+ updateViewPosition();
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ if (isDragging) {
+ isDragging = false;
+ savePosition();
+ }
+ break;
+ }
+ return true;
+ }
+
+ public static int[] getLastPosition(Context context) {
+ SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
+ DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ int screenWidth = metrics.widthPixels;
+ int screenHeight = metrics.heightPixels;
+
+ int defaultLeft = 0;
+ int defaultTop = 0;
+
+ int left = sp.getInt(KEY_LEFT, defaultLeft);
+ int top = sp.getInt(KEY_TOP, defaultTop);
+ return new int[]{left, top};
+ }
+}
+
diff --git a/mymessagemanager/src/main/res/layout/activity_appsettings.xml b/mymessagemanager/src/main/res/layout/activity_appsettings.xml
index 98029b7..e4bf151 100644
--- a/mymessagemanager/src/main/res/layout/activity_appsettings.xml
+++ b/mymessagemanager/src/main/res/layout/activity_appsettings.xml
@@ -4,8 +4,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/bg_frame">
+ android:layout_height="match_parent">
+ android:layout_weight="1.0"
+ android:padding="4dp">
+ android:layout_height="wrap_content"
+ android:spacing="12dp">
+ android:background="@drawable/bg_frame"
+ android:padding="12dp"
+ android:layout_marginBottom="8dp">
+ android:paddingLeft="5dp"
+ android:layout_marginBottom="8dp"/>
+
+
+
+
+
+
+ android:gravity="center_vertical"
+ android:layout_marginLeft="5dp">
-
-
-
+ android:text="拒绝显示的字符集:"
+ android:layout_weight="1.0"
+ android:textSize="12sp"/>
-
+
-
+
-
+
-
+
+
+ android:text="替代字符:"
+ android:textSize="12sp"
+ android:layout_marginRight="8dp"/>
-
-
-
-
-
+
@@ -98,39 +123,62 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/bg_frame">
+ android:background="@drawable/bg_frame"
+ android:padding="12dp"
+ android:layout_marginBottom="8dp">
+ android:paddingLeft="5dp"
+ android:layout_marginBottom="8dp"/>
+
+
+ android:textSize="12sp"
+ android:paddingLeft="5dp"
+ android:layout_marginBottom="10dp"/>
+
+
+ android:gravity="center_vertical"
+ android:layout_marginLeft="5dp">
+ android:text="当前国家代码为:+"
+ android:textSize="12sp"
+ android:layout_weight="1.0"/>
+ android:id="@+id/activityappsettingsEditText2"
+ android:paddingHorizontal="8dp"
+ android:background="@android:drawable/edit_text"
+ android:layout_marginHorizontal="8dp"
+ android:textSize="13sp"/>
+ android:background="@drawable/bg_frame"
+ android:padding="12dp"
+ android:layout_marginBottom="8dp">
+ android:paddingLeft="5dp"
+ android:layout_marginBottom="8dp"/>
+
+
+ android:gravity="center_vertical"
+ android:layout_marginLeft="5dp"
+ android:layout_marginRight="5dp">
+ android:paddingHorizontal="8dp"
+ android:background="@android:drawable/edit_text"
+ android:gravity="center"
+ android:textSize="13sp"/>
+
+
+
+
+
+
+
+
@@ -186,39 +269,60 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/bg_frame">
+ android:background="@drawable/bg_frame"
+ android:padding="12dp"
+ android:layout_marginBottom="8dp"
+ android:gravity="right">
+ android:paddingLeft="5dp"
+ android:layout_marginBottom="8dp"/>
-
+ android:layout_height="2dp"
+ android:background="#999999"
+ android:layout_marginBottom="8dp"/>
-
+
-
+
-
+
-
+
+
+
@@ -227,41 +331,32 @@
android:background="@drawable/bg_frame"
android:padding="10dp"
android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="8dp"/>
-
-
-
-
-
-
+ android:padding="10dp"
+ android:layout_marginTop="8dp">
+ android:id="@+id/activityappsettingsAOHPCTCSeekBar1"
+ android:layout_marginBottom="8dp"/>
+
+
+
+
+
+
+
+