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"/> - +