源码整理

This commit is contained in:
2025-12-23 13:43:30 +08:00
parent 4e84ff493b
commit eff1822ee5
17 changed files with 1366 additions and 906 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Dec 23 04:54:11 GMT 2025
#Tue Dec 23 05:35:18 GMT 2025
stageCount=24
libraryProject=
baseVersion=15.14
publishVersion=15.14.23
buildCount=4
buildCount=7
baseBetaVersion=15.14.24

View File

@@ -21,13 +21,13 @@ import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.dialogs.BackgroundPicturePreviewDialog;
import cc.winboll.studio.powerbell.dialogs.ColorPaletteDialog;
import cc.winboll.studio.powerbell.dialogs.NetworkBackgroundDialog;
import cc.winboll.studio.powerbell.dialogs.YesNoAlertDialog;
import cc.winboll.studio.powerbell.models.BackgroundBean;
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
import cc.winboll.studio.powerbell.utils.BitmapCacheUtils;

View File

@@ -1,9 +1,5 @@
package cc.winboll.studio.powerbell.activities;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/06/22 14:15
*/
import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
@@ -22,20 +18,46 @@ import android.widget.TextView;
import android.widget.Toast;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.views.AToolbar;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.activities.BackgroundSettingsActivity;
import cc.winboll.studio.powerbell.activities.PixelPickerActivity;
import cc.winboll.studio.powerbell.models.BackgroundBean;
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/**
* 像素拾取页面,支持加载图片并拾取指定位置像素颜色,同步至背景配置
* 适配 API30基于 Java7 开发
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/06/22 14:15
*/
public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActivity {
// ======================== 静态常量 =========================
public static final String TAG = "PixelPickerActivity";
public static final String EXTRA_IMAGE_PATH = "imagePath"; // 图片路径传递键
// 提示文本常量
private static final String MSG_IMAGE_LOADED = "图片已加载,点击获取像素值";
private static final String MSG_NO_IMAGE_PATH = "未找到图片路径";
private static final String MSG_IMAGE_LOAD_FAILED = "图片加载失败";
private static final String MSG_FILE_NOT_EXIST = "图片文件不存在";
private static final String MSG_FILE_NOT_FOUND = "图片文件未找到";
private static final String MSG_PIXEL_OUT_OF_RANGE = "像素坐标超出范围";
private static final String MSG_TOUCH_OUT_OF_IMAGE = "点击位置超出图片显示范围";
private static final String MSG_PIXEL_CALC_FAILED = "计算像素位置失败";
private static final String MSG_PIXEL_RECORDED = "已记录像素值";
// ======================== 成员变量 =========================
// UI组件
private AToolbar mAToolbar;
private ImageView imageView;
private TextView infoText;
private ViewGroup imageContainer;
private RelativeLayout mainLayout;
// 图片与像素数据
private Bitmap originalBitmap; // 原始图片Bitmap用于像素拾取
// ======================== 接口实现方法 =========================
@Override
public Activity getActivity() {
return this;
@@ -46,135 +68,140 @@ public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActi
return TAG;
}
private AToolbar mAToolbar;
private ImageView imageView;
private Bitmap originalBitmap;
private TextView infoText;
private ViewGroup imageContainer;
// ======================== 生命周期方法 =========================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pixelpicker);
LogUtils.d(TAG, "【onCreate】PixelPickerActivity 初始化开始");
// 初始化UI组件
initView();
// 初始化工具栏
initToolbar();
// 加载传递的图片
loadImageFromIntent();
// 绑定图片触摸事件
bindImageTouchListener();
LogUtils.d(TAG, "【onCreate】PixelPickerActivity 初始化完成");
}
@Override
protected void onResume() {
super.onResume();
LogUtils.d(TAG, "【onResume】PixelPickerActivity 恢复显示");
// 同步背景颜色
setBackgroundColor();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 回收Bitmap资源避免内存泄漏
if (originalBitmap != null && !originalBitmap.isRecycled()) {
originalBitmap.recycle();
originalBitmap = null;
LogUtils.d(TAG, "【onDestroy】原始图片Bitmap资源已回收");
}
LogUtils.d(TAG, "【onDestroy】PixelPickerActivity 销毁完成");
}
// ======================== UI初始化方法 =========================
/**
* 初始化所有UI组件
*/
private void initView() {
mAToolbar = (AToolbar) findViewById(R.id.toolbar);
imageView = findViewById(R.id.imageView);
infoText = findViewById(R.id.infoText);
imageContainer = findViewById(R.id.imageContainer);
mainLayout = findViewById(R.id.activitypixelpickerRelativeLayout1);
LogUtils.d(TAG, "【initView】UI组件初始化完成");
}
/**
* 初始化工具栏,设置导航与标题
*/
private void initToolbar() {
setActionBar(mAToolbar);
mAToolbar.setSubtitle(R.string.subtitle_activity_pixelpicker);
getActionBar().setDisplayHomeAsUpEnabled(true);
mAToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "【导航栏】点击返回");
finish();
}
});
LogUtils.d(TAG, "【initToolbar】工具栏初始化完成");
}
imageView = findViewById(R.id.imageView);
infoText = findViewById(R.id.infoText);
imageContainer = findViewById(R.id.imageContainer);
// ======================== 业务逻辑方法 =========================
/**
* 从Intent中获取图片路径并加载图片
*/
private void loadImageFromIntent() {
String imagePath = getIntent().getStringExtra(EXTRA_IMAGE_PATH);
LogUtils.d(TAG, "【loadImageFromIntent】获取到图片路径" + imagePath);
// 从Intent获取图片路径并加载
String imagePath = getIntent().getStringExtra("imagePath");
if (imagePath != null) {
loadImage(imagePath);
} else {
infoText.setText("未找到图片路径");
infoText.setText(MSG_NO_IMAGE_PATH);
LogUtils.w(TAG, "【loadImageFromIntent】未获取到图片路径");
}
// 设置图片点击事件
imageContainer.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN && originalBitmap != null) {
// 计算点击位置在图片上的实际坐标
float touchX = event.getX();
float touchY = event.getY();
int pixelX = -1, pixelY = -1;
try {
// 获取图片在容器中的实际位置和尺寸
int[] imageLocation = new int[2];
imageView.getLocationInWindow(imageLocation);
int imageWidth = imageView.getWidth();
int imageHeight = imageView.getHeight();
// 计算缩放比例
float scaleX = (float) originalBitmap.getWidth() / imageWidth;
float scaleY = (float) originalBitmap.getHeight() / imageHeight;
// 调整触摸坐标到图片坐标系
float adjustedX = touchX - imageLocation[0];
float adjustedY = touchY - imageLocation[1];
// 检查是否在图片范围内
if (adjustedX >= 0 && adjustedX <= imageWidth && adjustedY >= 0 && adjustedY <= imageHeight) {
// 计算实际像素坐标
pixelX = (int) (adjustedX * scaleX);
pixelY = (int) (adjustedY * scaleY);
// 再次检查像素坐标是否在有效范围内
if (pixelX >= 0 && pixelX < originalBitmap.getWidth() &&
pixelY >= 0 && pixelY < originalBitmap.getHeight()) {
int pixelColor = originalBitmap.getPixel(pixelX, pixelY);
showPixelDialog(pixelColor, pixelX, pixelY);
} else {
Toast.makeText(PixelPickerActivity.this, "像素坐标超出范围", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(PixelPickerActivity.this, "点击位置超出图片显示范围", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(PixelPickerActivity.this, "计算像素位置失败", Toast.LENGTH_SHORT).show();
}
}
return true;
}
});
}
/**
* 加载图片
* 加载指定路径的图片
* @param imagePath 图片文件路径
*/
private void loadImage(String imagePath) {
try {
File file = new File(imagePath);
if (file.exists()) {
// 解码图片
// 解码图片(加载原图)
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1; // 加载原图
options.inSampleSize = 1;
originalBitmap = BitmapFactory.decodeStream(new FileInputStream(file), null, options);
if (originalBitmap != null) {
imageView.setImageBitmap(originalBitmap);
infoText.setText("图片已加载,点击获取像素值");
infoText.setText(MSG_IMAGE_LOADED);
LogUtils.d(TAG, "【loadImage】图片加载成功尺寸" + originalBitmap.getWidth() + "x" + originalBitmap.getHeight());
} else {
infoText.setText("图片加载失败");
infoText.setText(MSG_IMAGE_LOAD_FAILED);
LogUtils.e(TAG, "【loadImage】图片解码失败");
}
} else {
infoText.setText("图片文件不存在");
infoText.setText(MSG_FILE_NOT_EXIST);
LogUtils.w(TAG, "【loadImage】图片文件不存在" + imagePath);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
infoText.setText("图片文件未找到");
infoText.setText(MSG_FILE_NOT_FOUND);
LogUtils.e(TAG, "【loadImage】图片文件未找到" + e.getMessage());
}
}
/**
* 显示像素对话框
* 显示像素颜色信息对话框
* @param pixelColor 拾取的像素颜色ARGB
* @param x 像素X坐标
* @param y 像素Y坐标
*/
private void showPixelDialog(final int pixelColor, int x, int y) {
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.dialog_pixel);
dialog.setCancelable(true);
// 设置像素颜色视图背景
// 设置颜色预览与信息展示
TextView colorView = dialog.findViewById(R.id.pixelColorView);
TextView infoTextView = dialog.findViewById(R.id.colorInfoText);
colorView.setBackgroundColor(pixelColor);
// 显示颜色信息
TextView infoText = dialog.findViewById(R.id.colorInfoText);
String colorInfo = String.format(
"RGB: (%d, %d, %d)\n" +
"ARGB: #%08X\n" +
@@ -184,63 +211,120 @@ public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActi
Color.blue(pixelColor),
pixelColor,
x, y);
infoText.setText(colorInfo);
infoTextView.setText(colorInfo);
LogUtils.d(TAG, "【showPixelDialog】显示像素信息" + colorInfo);
// 设置确定按钮点击事件
// 确定按钮点击事件
Button confirmButton = dialog.findViewById(R.id.confirmButton);
confirmButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
// 可以在这里添加确定后的回调逻辑
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(PixelPickerActivity.this);
BackgroundBean bean = utils.getPreviewBackgroundBean();
bean.setPixelColor(pixelColor);
utils.saveSettings();
Toast.makeText(PixelPickerActivity.this, "已记录像素值", Toast.LENGTH_SHORT).show();
// 保存像素颜色到背景配置
savePixelColor(pixelColor);
Toast.makeText(PixelPickerActivity.this, MSG_PIXEL_RECORDED, Toast.LENGTH_SHORT).show();
// 同步背景颜色
setBackgroundColor();
}
});
dialog.show();
LogUtils.d(TAG, "【showPixelDialog】像素对话框已显示");
}
@Override
protected void onDestroy() {
super.onDestroy();
// 回收Bitmap资源
if (originalBitmap != null && !originalBitmap.isRecycled()) {
originalBitmap.recycle();
originalBitmap = null;
}
}
void setBackgroundColor() {
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(PixelPickerActivity.this);
/**
* 保存拾取的像素颜色到背景配置
* @param pixelColor 拾取的像素颜色ARGB
*/
private void savePixelColor(int pixelColor) {
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
BackgroundBean bean = utils.getPreviewBackgroundBean();
int nPixelColor = bean.getPixelColor();
RelativeLayout mainLayout = findViewById(R.id.activitypixelpickerRelativeLayout1);
mainLayout.setBackgroundColor(nPixelColor);
bean.setPixelColor(pixelColor);
utils.saveSettings();
LogUtils.d(TAG, "【savePixelColor】像素颜色已保存#" + Integer.toHexString(pixelColor));
}
/**
* 同步背景颜色为拾取的像素颜色
*/
void setBackgroundColor() {
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
BackgroundBean bean = utils.getPreviewBackgroundBean();
int pixelColor = bean.getPixelColor();
mainLayout.setBackgroundColor(pixelColor);
LogUtils.d(TAG, "【setBackgroundColor】背景颜色已同步#" + Integer.toHexString(pixelColor));
}
// ======================== 事件回调方法 =========================
/**
* 绑定图片容器的触摸事件,处理像素拾取逻辑
*/
private void bindImageTouchListener() {
imageContainer.setOnTouchListener(new View.OnTouchListener() {
@Override
protected void onResume() {
super.onResume();
setBackgroundColor();
}
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN && originalBitmap != null) {
float touchX = event.getX();
float touchY = event.getY();
LogUtils.v(TAG, "【onTouch】触摸坐标(" + touchX + ", " + touchY + ")");
try {
// 获取图片在窗口中的位置与尺寸
int[] imageLocation = new int[2];
imageView.getLocationInWindow(imageLocation);
int imageWidth = imageView.getWidth();
int imageHeight = imageView.getHeight();
LogUtils.v(TAG, "【onTouch】图片显示尺寸" + imageWidth + "x" + imageHeight + ",位置:(" + imageLocation[0] + ", " + imageLocation[1] + ")");
// 计算缩放比例
float scaleX = (float) originalBitmap.getWidth() / imageWidth;
float scaleY = (float) originalBitmap.getHeight() / imageHeight;
LogUtils.v(TAG, "【onTouch】图片缩放比例X=" + scaleX + "Y=" + scaleY);
// 调整触摸坐标到图片显示区域坐标系
float adjustedX = touchX - imageLocation[0];
float adjustedY = touchY - imageLocation[1];
LogUtils.v(TAG, "【onTouch】调整后触摸坐标(" + adjustedX + ", " + adjustedY + ")");
// 检查是否在图片显示范围内
if (adjustedX >= 0 && adjustedX <= imageWidth && adjustedY >= 0 && adjustedY <= imageHeight) {
// 计算原始图片的像素坐标
int pixelX = (int) (adjustedX * scaleX);
int pixelY = (int) (adjustedY * scaleY);
LogUtils.v(TAG, "【onTouch】计算后像素坐标(" + pixelX + ", " + pixelY + ")");
// 检查像素坐标是否在原始图片范围内
if (pixelX >= 0 && pixelX < originalBitmap.getWidth() && pixelY >= 0 && pixelY < originalBitmap.getHeight()) {
int pixelColor = originalBitmap.getPixel(pixelX, pixelY);
showPixelDialog(pixelColor, pixelX, pixelY);
} else {
Toast.makeText(PixelPickerActivity.this, MSG_PIXEL_OUT_OF_RANGE, Toast.LENGTH_SHORT).show();
LogUtils.w(TAG, "【onTouch】像素坐标超出原始图片范围");
}
} else {
Toast.makeText(PixelPickerActivity.this, MSG_TOUCH_OUT_OF_IMAGE, Toast.LENGTH_SHORT).show();
LogUtils.w(TAG, "【onTouch】触摸位置超出图片显示范围");
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(PixelPickerActivity.this, MSG_PIXEL_CALC_FAILED, Toast.LENGTH_SHORT).show();
LogUtils.e(TAG, "【onTouch】计算像素位置失败" + e.getMessage());
}
}
return true;
}
});
LogUtils.d(TAG, "【bindImageTouchListener】图片触摸事件已绑定");
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
Intent intent = new Intent();
intent.setClass(this, BackgroundSettingsActivity.class);
LogUtils.d(TAG, "【onOptionsItemSelected】点击返回菜单");
Intent intent = new Intent(this, BackgroundSettingsActivity.class);
startActivity(intent);
//GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), );
return true;
}
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
@@ -249,12 +333,7 @@ public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActi
super.onBackPressed();
setResult(RESULT_OK);
finish();
// Intent intent = new Intent();
// intent.setClass(this, BackgroundSettingsActivity.class);
// startActivity(intent);
//GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), BackgroundPictureActivity.class);
LogUtils.d(TAG, "【onBackPressed】返回键触发页面关闭");
}
}

View File

@@ -9,17 +9,22 @@ import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.R;
/**
* 应用设置窗口,提供应用配置项的统一入口
* 适配 API30基于 Java7 开发
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/27 14:26
* @Describe 应用设置窗口
*/
public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivity {
// ======================== 静态常量 =========================
public static final String TAG = "SettingsActivity";
// 权限请求常量(为后续读取媒体图片权限预留)
private static final int REQUEST_READ_MEDIA_IMAGES = 1001;
private Toolbar mToolbar;
// ======================== 成员变量 =========================
private Toolbar mToolbar; // 顶部工具栏
// ======================== 接口实现方法 =========================
@Override
public Activity getActivity() {
return this;
@@ -30,16 +35,32 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
return TAG;
}
// ======================== 生命周期方法 =========================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
LogUtils.d(TAG, "【onCreate】SettingsActivity 初始化开始");
// 初始化工具栏
initToolbar();
LogUtils.d(TAG, "【onCreate】SettingsActivity 初始化完成");
}
// ======================== UI初始化方法 =========================
/**
* 初始化顶部工具栏,设置导航返回与样式
*/
private void initToolbar() {
mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
// 设置工具栏副标题与标题样式
mToolbar.setSubtitle(getTag());
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
// 显示返回按钮
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// 绑定导航点击事件
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -47,5 +68,7 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
finish();
}
});
LogUtils.d(TAG, "【initToolbar】工具栏初始化完成");
}
}

View File

@@ -3,47 +3,73 @@ package cc.winboll.studio.powerbell.activities;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.utils.APPPlusUtils;
import cc.winboll.studio.powerbell.App;
/**
* 应用快捷方式活动类,处理应用图标快捷菜单的切换请求
* 适配 API30基于 Java7 开发
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/15 13:45
* @Describe 应用快捷方式活动类
*/
public class ShortcutActionActivity extends Activity {
// ======================== 静态常量 =========================
public static final String TAG = "ShortcutActionActivity";
// 快捷指令常量
private static final String ACTION_SWITCH_TO_EN1 = "switchto_en1";
private static final String ACTION_SWITCH_TO_CN1 = "switchto_cn1";
private static final String ACTION_SWITCH_TO_CN2 = "switchto_cn2";
// ======================== 生命周期方法 =========================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 处理应用级别的切换请求
LogUtils.d(TAG, "【onCreate】ShortcutActionActivity 启动,开始处理快捷方式请求");
// 处理应用图标快捷菜单的切换请求
handleSwitchRequest();
LogUtils.d(TAG, "【onCreate】快捷方式请求处理完成关闭活动");
finish();
}
// ======================== 业务逻辑方法 =========================
/**
* 处理应用图标快捷菜单的请求
* 处理应用图标快捷菜单的请求,根据意图数据切换应用启动组件
*/
private void handleSwitchRequest() {
Intent intent = getIntent();
if (intent != null && "switchto_en1".equals(intent.getDataString())) {
if (intent == null) {
LogUtils.w(TAG, "【handleSwitchRequest】意图为空无法处理快捷方式请求");
return;
}
String dataString = intent.getDataString();
LogUtils.d(TAG, "【handleSwitchRequest】获取到快捷指令" + dataString);
// 匹配快捷指令并切换组件
if (ACTION_SWITCH_TO_EN1.equals(dataString)) {
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_EN1);
ToastUtils.show("切换至" + getString(R.string.app_name) + "图标");
//moveTaskToBack(true);
}
if (intent != null && "switchto_cn1".equals(intent.getDataString())) {
String toastMsg = "切换至" + getString(R.string.app_name) + "图标";
ToastUtils.show(toastMsg);
LogUtils.d(TAG, "【handleSwitchRequest】已切换至EN1组件" + App.COMPONENT_EN1);
} else if (ACTION_SWITCH_TO_CN1.equals(dataString)) {
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_CN1);
ToastUtils.show("切换至" + getString(R.string.app_name_cn1) + "图标");
//moveTaskToBack(true);
}
if (intent != null && "switchto_cn2".equals(intent.getDataString())) {
String toastMsg = "切换至" + getString(R.string.app_name_cn1) + "图标";
ToastUtils.show(toastMsg);
LogUtils.d(TAG, "【handleSwitchRequest】已切换至CN1组件" + App.COMPONENT_CN1);
} else if (ACTION_SWITCH_TO_CN2.equals(dataString)) {
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_CN2);
ToastUtils.show("切换至" + getString(R.string.app_name_cn2) + "图标");
//moveTaskToBack(true);
String toastMsg = "切换至" + getString(R.string.app_name_cn2) + "图标";
ToastUtils.show(toastMsg);
LogUtils.d(TAG, "【handleSwitchRequest】已切换至CN2组件" + App.COMPONENT_CN2);
} else {
LogUtils.w(TAG, "【handleSwitchRequest】未匹配到有效快捷指令" + dataString);
}
}
}

View File

@@ -1,10 +1,5 @@
package cc.winboll.studio.powerbell.activities;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/06/19 20:35
* @Describe 应用窗口基类
*/
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Color;
@@ -24,107 +19,187 @@ import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.models.AESThemeBean;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.BuildConfig;
import cc.winboll.studio.powerbell.R;
@SuppressLint("SetTextI18n")
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/06/19 20:35
* @Describe 应用窗口基类提供主题设置、Activity 管理、工具栏配置、全屏切换、版本标签显示等通用功能
* 适配 API30基于 Java7 开发,所有子类需继承此类实现统一窗口行为
*/
public abstract class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity {
// ======================== 静态常量 =========================
public static final String TAG = "WinBoLLActivity";
private static final String VERSION_TAG_TEXT = "MIMO SDK V%s"; // 版本标签文本格式
private static final float VERSION_TAG_TEXT_SIZE = 10f; // 版本标签字体大小sp
protected volatile AESThemeBean.ThemeType mThemeType;
protected TextView mTagView;
// ======================== 成员变量 =========================
protected volatile AESThemeBean.ThemeType mThemeType; // 当前主题类型
protected TextView mTagView; // 版本标签显示控件
// ======================== 接口实现 & 抽象方法 =========================
@Override
public abstract Activity getActivity();
@Override
public abstract String getTag();
// ======================== 生命周期方法 =========================
@Override
protected void onCreate(Bundle savedInstanceState) {
LogUtils.d(TAG, String.format("【%s-onCreate】窗口基类初始化开始", getTag()));
// 初始化主题
mThemeType = getThemeType();
setThemeStyle();
super.onCreate(savedInstanceState);
}
AESThemeBean.ThemeType getThemeType() {
return AESThemeBean.getThemeStyleType(AESThemeUtil.getThemeTypeID(getApplicationContext()));
}
void setThemeStyle() {
setTheme(AESThemeUtil.getThemeTypeID(getApplicationContext()));
LogUtils.d(TAG, String.format("【%s-onCreate】窗口基类初始化完成当前主题%s", getTag(), mThemeType));
}
@Override
protected void onStart() {
super.onStart();
LogUtils.d(TAG, String.format("【%s-onStart】添加版本标签到页面", getTag()));
// 添加版本标签
addVersionNameToContentView();
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// 注册到Activity管理器
WinBoLLActivityManager.getInstance().add(this);
LogUtils.d(TAG, String.format("【%s-onPostCreate】已注册到Activity管理器", getTag()));
}
@Override
protected void onDestroy() {
super.onDestroy();
// 从Activity管理器移除
WinBoLLActivityManager.getInstance().registeRemove(this);
LogUtils.d(TAG, String.format("【%s-onDestroy】已从Activity管理器移除", getTag()));
}
// ======================== 主题相关方法 =========================
/**
* 获取当前主题类型
* @return 主题类型枚举
*/
AESThemeBean.ThemeType getThemeType() {
int themeId = AESThemeUtil.getThemeTypeID(getApplicationContext());
AESThemeBean.ThemeType themeType = AESThemeBean.getThemeStyleType(themeId);
LogUtils.d(TAG, String.format("【%s-getThemeType】获取主题类型ID%d类型%s", getTag(), themeId, themeType));
return themeType;
}
/**
* 设置主题样式
*/
void setThemeStyle() {
int themeId = AESThemeUtil.getThemeTypeID(getApplicationContext());
setTheme(themeId);
LogUtils.d(TAG, String.format("【%s-setThemeStyle】应用主题样式ID%d", getTag(), themeId));
}
// ======================== UI 配置方法 =========================
/**
* 添加版本标签到页面底部
*/
protected void addVersionNameToContentView() {
if (!isTagViewVisible()) {
LogUtils.d(TAG, String.format("【%s-addVersionNameToContentView】版本标签不可见跳过添加", getTag()));
return;
}
if (mTagView == null) {
mTagView = new TextView(this);
// 配置版本标签样式
mTagView.setTextColor(Color.GRAY);
mTagView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
mTagView.setText("MIMO SDK V" + BuildConfig.VERSION_NAME);
mTagView.setTextSize(TypedValue.COMPLEX_UNIT_SP, VERSION_TAG_TEXT_SIZE);
mTagView.setText(String.format(VERSION_TAG_TEXT, BuildConfig.VERSION_NAME));
// 配置布局参数
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
// 添加到根布局
FrameLayout frameLayout = findViewById(android.R.id.content);
if (frameLayout != null) {
frameLayout.addView(mTagView, params);
LogUtils.d(TAG, String.format("【%s-addVersionNameToContentView】版本标签添加完成版本%s", getTag(), BuildConfig.VERSION_NAME));
} else {
LogUtils.w(TAG, String.format("【%s-addVersionNameToContentView】根布局为空无法添加版本标签", getTag()));
}
}
}
protected boolean isTagViewVisible() {
return true;
}
/**
* 配置工具栏,显示返回按钮
*/
public void setupToolbar() {
Toolbar mToolbar = findViewById(R.id.toolbar);
if (mToolbar != null) {
setSupportActionBar(mToolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
LogUtils.d(TAG, String.format("【%s-setupToolbar】工具栏配置完成已显示返回按钮", getTag()));
} else {
LogUtils.w(TAG, String.format("【%s-setupToolbar】ActionBar为空无法显示返回按钮", getTag()));
}
} else {
LogUtils.w(TAG, String.format("【%s-setupToolbar】未找到工具栏控件IDtoolbar", getTag()));
}
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
WinBoLLActivityManager.getInstance().add(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
WinBoLLActivityManager.getInstance().registeRemove(this);
/**
* 版本标签是否可见
* @return 默认为true子类可重写修改
*/
protected boolean isTagViewVisible() {
return true;
}
// ======================== 菜单 & 返回键处理 =========================
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
//GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
LogUtils.d(TAG, String.format("【%s-onOptionsItemSelected】点击返回菜单", getTag()));
return true;
}
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
super.onBackPressed();
//GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
LogUtils.d(TAG, String.format("【%s-onBackPressed】触发返回键", getTag()));
}
// ======================== 工具方法 =========================
/**
* 切换至全屏模式,隐藏状态栏与导航栏
* @param activity 目标Activity
*/
public void changeFullScreen(Activity activity) {
if (activity == null) {
LogUtils.w(TAG, String.format("【%s-changeFullScreen】目标Activity为空无法切换全屏", getTag()));
return;
}
Window window = activity.getWindow();
if (window == null) {
LogUtils.w(TAG, String.format("【%s-changeFullScreen】窗口为空无法切换全屏", getTag()));
return;
}
View decorView = window.getDecorView();
if (decorView == null) {
LogUtils.w(TAG, String.format("【%s-changeFullScreen】DecorView为空无法切换全屏", getTag()));
return;
}
// 配置全屏标志位
int flag = decorView.getSystemUiVisibility();
flag |= View.SYSTEM_UI_FLAG_FULLSCREEN;
flag |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
@@ -132,6 +207,9 @@ public abstract class WinBoLLActivity extends AppCompatActivity implements IWinB
flag |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
flag |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
decorView.setSystemUiVisibility(flag);
// 配置窗口标志位
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
LogUtils.d(TAG, String.format("【%s-changeFullScreen】已切换至全屏模式", getTag()));
}
}

View File

@@ -1,60 +1,106 @@
package cc.winboll.studio.powerbell.adapters;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/22 14:38:55
* @Describe 电池报告数据适配器
*/
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.adapters.BatteryAdapter;
import cc.winboll.studio.powerbell.models.BatteryData;
import java.util.ArrayList;
import java.util.List;
/**
* 电池报告数据适配器用于RecyclerView展示电池电量、充放电时间数据
* 适配 API30基于 Java7 开发
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/22 14:38:55
* @Describe 电池报告数据适配器
*/
public class BatteryAdapter extends RecyclerView.Adapter<BatteryAdapter.ViewHolder> {
// ======================== 静态常量 =========================
public static final String TAG = "BatteryAdapter";
private List<BatteryData> dataList = new ArrayList<>();
private static final String FORMAT_BATTERY_LEVEL = "%d%%"; // 电量显示格式
private static final String PREFIX_DISCHARGE_TIME = "使用时间: "; // 放电时间前缀
private static final String PREFIX_CHARGE_TIME = "充电时间: "; // 充电时间前缀
public void updateData(List<BatteryData> newData) {
dataList = newData;
notifyDataSetChanged();
// ======================== 成员变量 =========================
private List<BatteryData> dataList = new ArrayList<>(); // 电池数据列表
// ======================== 构造方法 =========================
public BatteryAdapter() {
LogUtils.d(TAG, "【BatteryAdapter】适配器初始化初始数据列表为空");
}
// ======================== 数据操作方法 =========================
/**
* 更新适配器数据并刷新列表
* @param newData 新的电池数据列表
*/
public void updateData(List<BatteryData> newData) {
LogUtils.d(TAG, "【updateData】开始更新数据新数据列表是否为空" + (newData == null));
// 判空处理,避免空指针
if (newData != null) {
dataList = newData;
LogUtils.d(TAG, "【updateData】数据更新完成当前数据量" + dataList.size());
} else {
dataList.clear();
LogUtils.w(TAG, "【updateData】新数据列表为空已清空本地数据");
}
notifyDataSetChanged();
LogUtils.d(TAG, "【updateData】已通知列表刷新");
}
// ======================== RecyclerView 重写方法 =========================
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LogUtils.d(TAG, "【onCreateViewHolder】创建ViewHolder父容器" + parent.getContext().getClass().getSimpleName());
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_battery_report, parent, false);
return new ViewHolder(view);
ViewHolder viewHolder = new ViewHolder(view);
LogUtils.d(TAG, "【onCreateViewHolder】ViewHolder创建完成");
return viewHolder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
LogUtils.d(TAG, "【onBindViewHolder】绑定ViewHolder位置" + position);
// 判空与越界校验
if (dataList == null || dataList.isEmpty() || position >= dataList.size()) {
LogUtils.w(TAG, "【onBindViewHolder】数据异常无法绑定视图位置" + position);
return;
}
BatteryData item = dataList.get(position);
holder.tvLevel.setText(String.format("%d%%", item.getCurrentLevel()));
holder.tvDischargeTime.setText("使用时间: " + item.getDischargeTime());
holder.tvChargeTime.setText("充电时间: " + item.getChargeTime());
// 绑定数据到视图
holder.tvLevel.setText(String.format(FORMAT_BATTERY_LEVEL, item.getCurrentLevel()));
holder.tvDischargeTime.setText(PREFIX_DISCHARGE_TIME + item.getDischargeTime());
holder.tvChargeTime.setText(PREFIX_CHARGE_TIME + item.getChargeTime());
LogUtils.d(TAG, "【onBindViewHolder】视图绑定完成位置" + position + ",电量:" + item.getCurrentLevel() + "%");
}
@Override
public int getItemCount() {
return dataList.size();
int count = dataList.size();
LogUtils.d(TAG, "【getItemCount】获取条目数量" + count);
return count;
}
// ======================== ViewHolder 内部类 =========================
static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvLevel;
TextView tvDischargeTime;
TextView tvChargeTime;
TextView tvLevel; // 电量显示
TextView tvDischargeTime; // 放电时间显示
TextView tvChargeTime; // 充电时间显示
ViewHolder(View itemView) {
super(itemView);
// 初始化视图控件
tvLevel = itemView.findViewById(R.id.tvLevel);
tvDischargeTime = itemView.findViewById(R.id.tvDischargeTime);
tvChargeTime = itemView.findViewById(R.id.tvChargeTime);
LogUtils.d(TAG, "【ViewHolder】控件初始化完成");
}
}
}

View File

@@ -1,4 +1,5 @@
package cc.winboll.studio.powerbell.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
@@ -11,130 +12,138 @@ import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.MainActivity;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.activities.BackgroundSettingsActivity;
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
import cc.winboll.studio.powerbell.utils.UriUtils;
import cc.winboll.studio.powerbell.views.BackgroundView;
import java.io.File;
/**
* 背景图片的接收分享文件后的预览对话框
* 适配 API30基于 Java7 开发支持分享图片的Uri解析、预览与确认选择
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/04/25 16:27:53
* @Describe 背景图片的接收分享文件后的预览对话框
*/
public class BackgroundPicturePreviewDialog extends Dialog {
// ======================== 静态常量 =========================
public static final String TAG = "BackgroundPicturePreviewDialog";
private static final String TOAST_MSG_EMPTY_FILE = "接收到的文件为空。"; // 空文件提示文本
Context mContext;
//BackgroundSourceUtils mBackgroundPictureUtils;
Button dialogbackgroundpicturepreviewButton1;
Button dialogbackgroundpicturepreviewButton2;
//String mszPreReceivedFileName;
IOnRecivedPictureListener mIOnRecivedPictureListener;
Uri mUriRecivedPicture;
BackgroundView mBackgroundView;
public BackgroundPicturePreviewDialog(Context context, IOnRecivedPictureListener iOnRecivedPictureListener) {
super(context);
setContentView(R.layout.dialog_backgroundpicturepreview);
mIOnRecivedPictureListener = iOnRecivedPictureListener;
//initEnv();
mContext = context;
//mBackgroundPictureUtils = BackgroundSourceUtils.getInstance(mContext);
mBackgroundView = findViewById(R.id.backgroundview);
previewRecivedPicture();
dialogbackgroundpicturepreviewButton1 = findViewById(R.id.dialogbackgroundpicturepreviewButton1);
dialogbackgroundpicturepreviewButton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 不使用分享到的图片
// 跳转到主窗口
Intent i = new Intent(mContext, MainActivity.class);
mContext.startActivity(i);
dismiss();
}
});
dialogbackgroundpicturepreviewButton2 = findViewById(R.id.dialogbackgroundpicturepreviewButton2);
dialogbackgroundpicturepreviewButton2.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
// 使用分享到的图片
mIOnRecivedPictureListener.onAcceptRecivedPicture(mUriRecivedPicture);
// 关闭对话框
dismiss();
}
});
}
// void initEnv() {
// LogUtils.d(TAG, "initEnv()");
// mszPreReceivedFileName = "PreReceived.data";
// }
void previewRecivedPicture() {
BackgroundSettingsActivity activity = ((BackgroundSettingsActivity)mContext);
//取出文件uri
mUriRecivedPicture = activity.getIntent().getData();
if (mUriRecivedPicture == null) {
mUriRecivedPicture = activity.getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
}
//获取文件真实地址
String szSrcImage = UriUtils.getFilePathFromUri(mContext, mUriRecivedPicture);
if (TextUtils.isEmpty(szSrcImage)) {
Toast.makeText(mContext, "接收到的文件为空。", Toast.LENGTH_SHORT).show();
dismiss();
return;
}
mBackgroundView.loadImage(szSrcImage);
//
// File fSrcImage = new File(szSrcImage);
// //mszPreReceivedFileName = DateUtils.getDateNowString() + "-" + fSrcImage.getName();
// File mfPreReceivedPhoto = new File(BackgroundSourceUtils.getInstance(mContext).getBackgroundSourceDirPath(), mszPreReceivedFileName);
// // 复制源图片到剪裁文件
// try {
// FileUtils.copyFileUsingFileChannels(fSrcImage, mfPreReceivedPhoto);
// LogUtils.d(TAG, "copyFileUsingFileChannels");
// Drawable drawable = Drawable.createFromPath(mfPreReceivedPhoto.getPath());
// imageView.setBackground(drawable);
// //LogUtils.d(TAG, "mszPreReceivedFileName : " + mszPreReceivedFileName);
// } catch (IOException e) {
// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
// }
}
//
// 创建图片背景图片目录
//
// boolean createBackgroundFolder2(String szBackgroundFolder) {
// // 文件路径参数为空值或无效值时返回false.
// if (szBackgroundFolder == null | szBackgroundFolder.equals("")) {
// return false;
// }
//
// LogUtils.d(TAG, "Background Folder Is : " + szBackgroundFolder);
// File f = new File(szBackgroundFolder);
// if (f.exists()) {
// if (f.isDirectory()) {
// return true;
// } else {
// // 工作路径不是一个目录
// LogUtils.d(TAG, "createImageWorkFolder() error : szImageCacheFolder isDirectory return false. -->" + szBackgroundFolder);
// return false;
// }
// } else {
// return f.mkdirs();
// }
// }
// ======================== 成员变量 =========================
private Context mContext; // 上下文对象
private IOnRecivedPictureListener mIOnRecivedPictureListener; // 图片接收监听
private Uri mUriRecivedPicture; // 接收的图片Uri
// 控件对象
private BackgroundView mBackgroundView; // 背景预览视图
private Button dialogbackgroundpicturepreviewButton1; // 取消按钮
private Button dialogbackgroundpicturepreviewButton2; // 确认按钮
// ======================== 接口定义 =========================
/**
* 图片接收监听接口用于通知确认选择的图片Uri
*/
public interface IOnRecivedPictureListener {
void onAcceptRecivedPicture(Uri uriRecivedPicture);
}
// ======================== 构造方法 =========================
public BackgroundPicturePreviewDialog(Context context, IOnRecivedPictureListener iOnRecivedPictureListener) {
super(context);
LogUtils.d(TAG, "【BackgroundPicturePreviewDialog】对话框初始化开始");
// 初始化成员变量
mContext = context;
mIOnRecivedPictureListener = iOnRecivedPictureListener;
// 设置布局与控件
setContentView(R.layout.dialog_backgroundpicturepreview);
initViews();
bindButtonClickEvents();
// 预览接收的图片
previewRecivedPicture();
LogUtils.d(TAG, "【BackgroundPicturePreviewDialog】对话框初始化完成");
}
// ======================== 视图初始化方法 =========================
/**
* 初始化对话框内所有控件
*/
private void initViews() {
mBackgroundView = findViewById(R.id.backgroundview);
dialogbackgroundpicturepreviewButton1 = findViewById(R.id.dialogbackgroundpicturepreviewButton1);
dialogbackgroundpicturepreviewButton2 = findViewById(R.id.dialogbackgroundpicturepreviewButton2);
LogUtils.d(TAG, "【initViews】对话框控件初始化完成");
}
// ======================== 事件绑定方法 =========================
/**
* 绑定按钮点击事件
*/
private void bindButtonClickEvents() {
// 取消按钮:跳转到主页面并关闭对话框
dialogbackgroundpicturepreviewButton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
LogUtils.d(TAG, "【onClick】点击取消按钮跳转到主页面");
Intent intent = new Intent(mContext, MainActivity.class);
mContext.startActivity(intent);
dismiss();
LogUtils.d(TAG, "【onClick】对话框已关闭");
}
});
// 确认按钮:通知监听并关闭对话框
dialogbackgroundpicturepreviewButton2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "【onClick】点击确认按钮通知接收图片");
if (mIOnRecivedPictureListener != null && mUriRecivedPicture != null) {
mIOnRecivedPictureListener.onAcceptRecivedPicture(mUriRecivedPicture);
LogUtils.d(TAG, "【onClick】已通知监听图片Uri" + mUriRecivedPicture);
} else {
LogUtils.w(TAG, "【onClick】监听为空或图片Uri无效无法通知");
}
dismiss();
LogUtils.d(TAG, "【onClick】对话框已关闭");
}
});
LogUtils.d(TAG, "【bindButtonClickEvents】按钮点击事件绑定完成");
}
// ======================== 业务逻辑方法 =========================
/**
* 预览接收的分享图片
*/
private void previewRecivedPicture() {
LogUtils.d(TAG, "【previewRecivedPicture】开始预览接收的图片");
// 校验上下文类型
if (!(mContext instanceof BackgroundSettingsActivity)) {
LogUtils.e(TAG, "【previewRecivedPicture】上下文不是BackgroundSettingsActivity无法获取图片Uri");
Toast.makeText(mContext, TOAST_MSG_EMPTY_FILE, Toast.LENGTH_SHORT).show();
dismiss();
return;
}
BackgroundSettingsActivity activity = (BackgroundSettingsActivity) mContext;
// 从Intent中获取图片Uri优先getData其次EXTRA_STREAM
mUriRecivedPicture = activity.getIntent().getData();
if (mUriRecivedPicture == null) {
mUriRecivedPicture = activity.getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
LogUtils.d(TAG, "【previewRecivedPicture】从EXTRA_STREAM获取Uri" + mUriRecivedPicture);
} else {
LogUtils.d(TAG, "【previewRecivedPicture】从getData获取Uri" + mUriRecivedPicture);
}
// 解析Uri为文件路径
String szSrcImage = UriUtils.getFilePathFromUri(mContext, mUriRecivedPicture);
if (TextUtils.isEmpty(szSrcImage)) {
LogUtils.w(TAG, "【previewRecivedPicture】解析的文件路径为空");
Toast.makeText(mContext, TOAST_MSG_EMPTY_FILE, Toast.LENGTH_SHORT).show();
dismiss();
return;
}
// 加载图片到预览视图
mBackgroundView.loadImage(szSrcImage);
LogUtils.d(TAG, "【previewRecivedPicture】图片预览完成文件路径" + szSrcImage);
}
}

View File

@@ -23,14 +23,17 @@ import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.R;
import com.a4455jkjh.colorpicker.ColorPickerDialog;
import com.a4455jkjh.colorpicker.view.OnColorChangedListener;
/**
* 调色板对话框支持颜色拾取、RGB输入、透明度/亮度调节,兼容 API29-30+ 小米机型)
* 适配 API30基于 Java7 开发,返回 0xAARRGGBB 格式颜色(含透明度)
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/12/16 11:47
* @Describe 调色板对话框支持颜色拾取、RGB输入、透明度/亮度调节,兼容 API29-30+ 小米机型)
*/
public class ColorPaletteDialog extends Dialog implements View.OnClickListener, SeekBar.OnSeekBarChangeListener {
// ====================== 常量定义(首屏可见,统一管理) ======================
// ====================== 静态常量(首屏可见,统一管理) ======================
public static final String TAG = "ColorPaletteDialog";
private static final int MAX_RGB_VALUE = 255; // RGB分量最大值0-255
private static final int DEFAULT_BRIGHTNESS = 100; // 默认亮度百分比100%,无调节)
@@ -39,6 +42,8 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
private static final int MAX_BRIGHTNESS = 200; // 亮度最大值200%,避免过曝失真)
private static final int MAX_ALPHA_PERCENT = 100; // 透明度最大值100%=不透明)
private static final int MIN_ALPHA_PERCENT = 0; // 透明度最小值0%=完全透明)
private static final String FORMAT_COLOR_HEX = "#%08X"; // 颜色值格式化AARRGGBB
private static final String FORMAT_PERCENT = "%d%%"; // 百分比格式化X%
// ====================== 回调接口(紧跟常量,逻辑关联) ======================
public interface OnColorSelectedListener {
@@ -66,7 +71,7 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
// 并发控制标记:是否是应用程序自身在更新颜色(避免循环回调/重复触发)
private static volatile boolean isAppSelfUpdatingColor = false;
// 控件引用(新增透明度进度条+文本)
// 控件引用
private ImageView ivColorPicker; // 颜色预览拾取框
private ImageView ivColorScaler; // 颜色渐变拾取框
private EditText etR; // R分量输入框显示实时调节值
@@ -93,12 +98,11 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
}
// 2. 解析初始颜色:原始基准值 = 实时值(初始无调节)
// 透明度初始颜色的alpha0-255转百分比0-100%
this.mOriginalAlpha = Color.alpha(initialColor);
this.mOriginalAlphaPercent = alpha2Percent(mOriginalAlpha);
this.mCurrentAlpha = mOriginalAlpha;
this.mCurrentAlphaPercent = mOriginalAlphaPercent;
// RGB初始颜色的RGB分量
this.mOriginalR = Color.red(initialColor);
this.mOriginalG = Color.green(initialColor);
this.mOriginalB = Color.blue(initialColor);
@@ -110,10 +114,11 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
this.mCurrentBrightnessPercent = DEFAULT_BRIGHTNESS;
this.mCurrentColor = initialColor;
LogUtils.d(TAG, "init dialog success | 初始颜色:" + String.format("#%08X", initialColor)
+ " | 原始RGB" + mOriginalR + "," + mOriginalG + "," + mOriginalB
+ " | 原始透明度:" + mOriginalAlphaPercent + "%"
+ " | 初始亮度:" + mCurrentBrightnessPercent + "%");
LogUtils.d(TAG, String.format("init dialog success | 初始颜色:%s | 原始RGB%d,%d,%d | 原始透明度:%s | 初始亮度:%s",
String.format(FORMAT_COLOR_HEX, initialColor),
mOriginalR, mOriginalG, mOriginalB,
String.format(FORMAT_PERCENT, mOriginalAlphaPercent),
String.format(FORMAT_PERCENT, mCurrentBrightnessPercent)));
}
// ====================== 生命周期方法(按执行顺序排列,逻辑清晰) ======================
@@ -142,7 +147,7 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
// ====================== 初始化核心方法(职责单一,便于维护) ======================
/**
* 控件绑定(新增透明度进度条+文本绑定)
* 控件绑定
*/
private void initViewBind(View view) {
ivColorPicker = view.findViewById(R.id.iv_color_picker);
@@ -183,22 +188,23 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
etG.setText(String.valueOf(mCurrentG));
etB.setText(String.valueOf(mCurrentB));
// 3. 颜色值输入框(显示当前最终颜色,格式#AARRGGBB,大写更规范
etColorValue.setText(String.format("#%08X", mCurrentColor));
// 3. 颜色值输入框(显示当前最终颜色,格式#AARRGGBB
etColorValue.setText(String.format(FORMAT_COLOR_HEX, mCurrentColor));
// 4. 透明度控件(进度条+文本,初始=原始透明度)
sbAlpha.setProgress(mCurrentAlphaPercent);
tvAlphaValue.setText(mCurrentAlphaPercent + "%");
tvAlphaValue.setText(String.format(FORMAT_PERCENT, mCurrentAlphaPercent));
// 5. 亮度控件显示默认100%,初始化按钮状态)
tvBrightnessValue.setText(mCurrentBrightnessPercent + "%");
updateBrightnessBtnStatus(); // 禁用边界值按钮初始100%,都可用)
tvBrightnessValue.setText(String.format(FORMAT_PERCENT, mCurrentBrightnessPercent));
updateBrightnessBtnStatus(); // 禁用边界值按钮
LogUtils.d(TAG, "init data complete | 原始透明度:" + mOriginalAlphaPercent + "%");
LogUtils.d(TAG, String.format("init data complete | 原始透明度:%s",
String.format(FORMAT_PERCENT, mOriginalAlphaPercent)));
}
/**
* 监听初始化(新增透明度进度条监听)
* 监听初始化
*/
private void initListener() {
// 点击监听(按钮+颜色拾取框)
@@ -225,7 +231,7 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
// 宽度占屏幕80%,高度自适应(适配不同屏幕尺寸)
lp.width = (int) (getContext().getResources().getDisplayMetrics().widthPixels * 0.8);
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
// 软键盘适配:小米虚拟导航栏兼容,避免输入框被遮挡
// 软键盘适配:小米虚拟导航栏兼容
window.setAttributes(lp);
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
| WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
@@ -261,7 +267,7 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
});
}
// ====================== 透明度进度条监听实现(核心新增) ======================
// ====================== 透明度进度条监听实现 ======================
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// 仅处理用户手动拖动进度条(避免应用自身更新时触发)
@@ -277,7 +283,7 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
public void onStopTrackingTouch(SeekBar seekBar) {}
/**
* 拖动透明度进度条更新颜色(核心新增逻辑)
* 拖动透明度进度条更新颜色
*/
private synchronized void updateAlphaBySeekBar(int alphaPercent) {
if (!isAppSelfUpdatingColor) {
@@ -290,18 +296,17 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
calculateBrightnessAndUpdate();
// 同步所有控件
updateAllViews();
LogUtils.d(TAG, "update alpha by seekbar | 透明度:" + mCurrentAlphaPercent + "%");
LogUtils.d(TAG, String.format("update alpha by seekbar | 透明度:%s",
String.format(FORMAT_PERCENT, mCurrentAlphaPercent)));
} finally {
// 直接释放标记,避免卡顿
isAppSelfUpdatingColor = false;
isAppSelfUpdatingColor = false; // 释放标记
}
}
}
// ====================== 颜色核心逻辑(新增透明度参数,全功能兼容) ======================
// ====================== 颜色核心逻辑 ======================
/**
* 核心计算基于原始RGB+当前亮度+当前透明度计算实时RGB+最终颜色
* 逻辑亮度百分比→调节系数→原始RGB×系数→限制0-255→拼接透明度→最终颜色
*/
private void calculateBrightnessAndUpdate() {
// 亮度百分比转调节系数10%→0.1100%→1.0200%→2.0
@@ -317,98 +322,93 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
}
/**
* 亮度减少每次减5%最低10%,防止过暗
* 亮度减少每次减5%最低10%
*/
private void decreaseBrightness() {
changeBrightness(false);
}
/**
* 亮度增加每次加5%最高200%,防止过曝
* 亮度增加每次加5%最高200%
*/
private void increaseBrightness() {
changeBrightness(true);
}
/**
* 亮度调节核心方法(统一逻辑,加并发控制,同步所有控件
* 亮度调节核心方法(统一逻辑,加并发控制)
*/
private synchronized void changeBrightness(boolean isIncrease) {
// 关键:判断非应用自身更新,才执行调节(避免重复触发/循环)
if (!isAppSelfUpdatingColor) {
isAppSelfUpdatingColor = true; // 标记为应用自身更新
isAppSelfUpdatingColor = true;
try {
if (isIncrease) {
if (mCurrentBrightnessPercent >= MAX_BRIGHTNESS) return; // 达到最大值,不处理
mCurrentBrightnessPercent += BRIGHTNESS_STEP; // 增加步长
if (mCurrentBrightnessPercent >= MAX_BRIGHTNESS) return;
mCurrentBrightnessPercent += BRIGHTNESS_STEP;
} else {
if (mCurrentBrightnessPercent <= MIN_BRIGHTNESS) return; // 达到最小值,不处理
mCurrentBrightnessPercent -= BRIGHTNESS_STEP; // 减少步长
if (mCurrentBrightnessPercent <= MIN_BRIGHTNESS) return;
mCurrentBrightnessPercent -= BRIGHTNESS_STEP;
}
// 计算亮度调节后的实时RGB+最终颜色(含当前透明度)
// 计算亮度调节后的实时RGB+最终颜色
calculateBrightnessAndUpdate();
// 同步所有控件
updateAllViews();
LogUtils.d(TAG, (isIncrease ? "increase" : "decrease") + " brightness | "
+ "亮度:" + mCurrentBrightnessPercent + "% | 实时RGB" + mCurrentR + "," + mCurrentG + "," + mCurrentB);
LogUtils.d(TAG, String.format("%s brightness | 亮度:%s | 实时RGB%d,%d,%d",
isIncrease ? "increase" : "decrease",
String.format(FORMAT_PERCENT, mCurrentBrightnessPercent),
mCurrentR, mCurrentG, mCurrentB));
} finally {
// 直接释放标记,避免卡顿
isAppSelfUpdatingColor = false;
}
}
}
/**
* 解析颜色字符串(支持#RRGGBB/#AARRGGBB容错处理,更新原始基准值+实时值)
* 新增:解析颜色的透明度,同步更新透明度进度条
* 解析颜色字符串(支持#RRGGBB/#AARRGGBB更新原始基准值+实时值)
*/
private void parseColorFromStr(String colorStr, int triggerViewId) {
// 关键:判断非应用自身更新,才执行解析(避免循环回调)
if (!isAppSelfUpdatingColor) {
isAppSelfUpdatingColor = true; // 标记为应用自身更新
isAppSelfUpdatingColor = true;
try {
if (TextUtils.isEmpty(colorStr)) return;
// 补全#前缀(兼容用户输入习惯如直接输AARRGGBB
// 补全#前缀(兼容用户输入习惯)
if (!colorStr.startsWith("#")) {
colorStr = "#" + colorStr;
}
// 格式校验仅支持6位RRGGBB/8位AARRGGBB,避免非法格式
// 格式校验仅支持6位RRGGBB/8位AARRGGBB
if (colorStr.length() != 7 && colorStr.length() != 9) {
LogUtils.e(TAG, "parse color failed | 格式错误(需#RRGGBB/#AARRGGBB输入" + colorStr);
LogUtils.e(TAG, String.format("parse color failed | 格式错误(需#RRGGBB/#AARRGGBB输入%s", colorStr));
return;
}
// 解析颜色系统API安全可靠
// 解析颜色
int parsedColor = Color.parseColor(colorStr);
// 更新原始基准值(用户输入颜色,重置基准)
// 透明度解析颜色的alpha0-255转百分比0-100%
// 更新原始基准值与实时值
mOriginalAlpha = Color.alpha(parsedColor);
mOriginalAlphaPercent = alpha2Percent(mOriginalAlpha);
// RGB解析颜色的RGB分量
mOriginalR = Color.red(parsedColor);
mOriginalG = Color.green(parsedColor);
mOriginalB = Color.blue(parsedColor);
// 更新实时值(原始值=实时值,无调节)
mCurrentAlpha = mOriginalAlpha;
mCurrentAlphaPercent = mOriginalAlphaPercent;
mCurrentR = mOriginalR;
mCurrentG = mOriginalG;
mCurrentB = mOriginalB;
// 重置亮度为100%
mCurrentBrightnessPercent = DEFAULT_BRIGHTNESS;
mCurrentColor = parsedColor;
// 同步所有控件
updateAllViews();
LogUtils.d(TAG, "parse color success | 解析颜色:" + String.format("#%08X", parsedColor)
+ " | 透明度:" + mCurrentAlphaPercent + "% | 重置亮度:" + DEFAULT_BRIGHTNESS + "%");
LogUtils.d(TAG, String.format("parse color success | 解析颜色:%s | 透明度:%s | 重置亮度:%s",
String.format(FORMAT_COLOR_HEX, parsedColor),
String.format(FORMAT_PERCENT, mCurrentAlphaPercent),
String.format(FORMAT_PERCENT, DEFAULT_BRIGHTNESS)));
} catch (IllegalArgumentException e) {
LogUtils.e(TAG, "parse color failed | 非法颜色格式,输入:" + colorStr, e);
LogUtils.e(TAG, String.format("parse color failed | 非法颜色格式,输入:%s", colorStr), e);
} finally {
// 直接释放标记,避免卡顿
isAppSelfUpdatingColor = false;
}
}
@@ -416,77 +416,74 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
/**
* 通过RGB输入框更新颜色用户输入后更新原始基准值+实时值重置亮度为100%
* 新增透明度基准值保持不变仅更新RGB
*/
private synchronized void updateColorByRGB(int triggerViewId) {
// 关键:判断非应用自身更新,才执行更新(避免循环回调)
if (!isAppSelfUpdatingColor) {
isAppSelfUpdatingColor = true; // 标记为应用自身更新
isAppSelfUpdatingColor = true;
try {
// 解析用户输入的RGB值限制0-255非法输入设为0
int inputR = parseInputValue(etR.getText().toString());
int inputG = parseInputValue(etG.getText().toString());
int inputB = parseInputValue(etB.getText().toString());
// 更新原始基准值(用户手动输入,作为新的调节基准)
// 更新原始基准值与实时值
mOriginalR = inputR;
mOriginalG = inputG;
mOriginalB = inputB;
// 更新实时值(输入值=实时值,无亮度调节)
mCurrentR = inputR;
mCurrentG = inputG;
mCurrentB = inputB;
// 重置亮度为100%(透明度保持当前值不变)
mCurrentBrightnessPercent = DEFAULT_BRIGHTNESS;
// 计算最终颜色(无亮度调节,拼接当前透明度)
mCurrentColor = Color.argb(mCurrentAlpha, mCurrentR, mCurrentG, mCurrentB);
// 同步所有控件
updateAllViews();
LogUtils.d(TAG, "update color by RGB | 新原始RGB" + mOriginalR + "," + mOriginalG + "," + mOriginalB
+ " | 透明度:" + mCurrentAlphaPercent + "% | 重置亮度:" + DEFAULT_BRIGHTNESS + "%");
LogUtils.d(TAG, String.format("update color by RGB | 新原始RGB%d,%d,%d | 透明度:%s | 重置亮度:%s",
mOriginalR, mOriginalG, mOriginalB,
String.format(FORMAT_PERCENT, mCurrentAlphaPercent),
String.format(FORMAT_PERCENT, DEFAULT_BRIGHTNESS)));
} catch (Exception e) {
LogUtils.e(TAG, "update color by RGB failed", e);
} finally {
// 直接释放标记,避免卡顿
isAppSelfUpdatingColor = false;
}
}
}
/**
* 核心同步:更新所有控件显示(新增透明度控件同步,统一方法)
* 核心同步:更新所有控件显示
*/
private void updateAllViews() {
// 1. 同步颜色预览(显示最终颜色,含透明度+亮度)
// 1. 同步颜色预览
ivColorPicker.setBackgroundColor(mCurrentColor);
// 2. 同步RGB输入框(显示实时调节值)
// 2. 同步RGB输入框
etR.setText(String.valueOf(mCurrentR));
etG.setText(String.valueOf(mCurrentG));
etB.setText(String.valueOf(mCurrentB));
// 3. 同步颜色值输入框(显示最终颜色,含透明度,格式#AARRGGBB
etColorValue.setText(String.format("#%08X", mCurrentColor));
// 3. 同步颜色值输入框
etColorValue.setText(String.format(FORMAT_COLOR_HEX, mCurrentColor));
// 4. 同步透明度控件(进度条+文本,显示实时透明度)
// 4. 同步透明度控件
sbAlpha.setProgress(mCurrentAlphaPercent);
tvAlphaValue.setText(mCurrentAlphaPercent + "%");
tvAlphaValue.setText(String.format(FORMAT_PERCENT, mCurrentAlphaPercent));
// 5. 同步亮度控件(数值+按钮状态)
tvBrightnessValue.setText(mCurrentBrightnessPercent + "%");
// 5. 同步亮度控件
tvBrightnessValue.setText(String.format(FORMAT_PERCENT, mCurrentBrightnessPercent));
updateBrightnessBtnStatus();
LogUtils.d(TAG, "sync all views complete | 最终颜色:" + String.format("#%08X", mCurrentColor)
+ " | 实时RGB" + mCurrentR + "," + mCurrentG + "," + mCurrentB
+ " | 透明度:" + mCurrentAlphaPercent + "% | 亮度:" + mCurrentBrightnessPercent + "%");
LogUtils.d(TAG, String.format("sync all views complete | 最终颜色:%s | 实时RGB%d,%d,%d | 透明度:%s | 亮度:%s",
String.format(FORMAT_COLOR_HEX, mCurrentColor),
mCurrentR, mCurrentG, mCurrentB,
String.format(FORMAT_PERCENT, mCurrentAlphaPercent),
String.format(FORMAT_PERCENT, mCurrentBrightnessPercent)));
}
/**
* 更新亮度按钮状态(边界值禁用,提升交互体验)
*/
private void updateBrightnessBtnStatus() {
// 亮度≤10%禁用减号文字变浅灰≥200%:禁用加号(文字变浅灰)
boolean canMinus = mCurrentBrightnessPercent > MIN_BRIGHTNESS;
boolean canPlus = mCurrentBrightnessPercent < MAX_BRIGHTNESS;
@@ -496,37 +493,37 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
tvBrightnessPlus.setTextColor(canPlus ? Color.BLACK : Color.parseColor("#CCCCCC"));
}
// ====================== 工具方法(新增透明度转换工具,通用复用) ======================
// ====================== 工具方法 ======================
/**
* 透明度0-255 → 0-100%(颜色计算值转用户直观百分比)
* 透明度0-255 → 0-100%
*/
private int alpha2Percent(int alpha) {
return Math.round((float) alpha / MAX_RGB_VALUE * MAX_ALPHA_PERCENT);
}
/**
* 透明度0-100% → 0-255(用户操作百分比转颜色计算值)
* 透明度0-100% → 0-255
*/
private int percent2Alpha(int percent) {
return Math.round((float) percent / MAX_ALPHA_PERCENT * MAX_RGB_VALUE);
}
/**
* 解析输入值限制0-255非法输入返回0,容错处理
* 解析输入值限制0-255非法输入返回0
*/
private int parseInputValue(String input) {
if (TextUtils.isEmpty(input)) return 0;
try {
int value = Integer.parseInt(input);
return Math.min(Math.max(value, 0), MAX_RGB_VALUE); // 限制范围,避免溢出
return Math.min(Math.max(value, 0), MAX_RGB_VALUE);
} catch (NumberFormatException e) {
LogUtils.e(TAG, "parse input failed | 非法数字,输入:" + input, e);
LogUtils.e(TAG, String.format("parse input failed | 非法数字,输入:%s", input), e);
return 0;
}
}
/**
* RGB输入框监听复用(减少冗余代码,统一逻辑)
* RGB输入框监听复用
*/
private void setEditTextWatcher(EditText editText, final int viewId) {
editText.addTextChangedListener(new TextWatcher() {
@@ -538,16 +535,15 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
@Override
public void afterTextChanged(Editable s) {
// 关键:判断非应用自身更新,才执行更新(避免循环回调)
if (!isAppSelfUpdatingColor) {
updateColorByRGB(viewId); // 输入变化后更新颜色
updateColorByRGB(viewId);
}
}
});
}
/**
* dp转px适配小米不同分辨率,避免尺寸错乱,通用工具
* dp转px适配小米不同分辨率
*/
private int dp2px(float dp) {
return (int) (dp * getContext().getResources().getDisplayMetrics().density + 0.5f);
@@ -555,36 +551,24 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
/**
* 显示系统颜色选择器兼容API29-30无高版本依赖小米机型适配
* 核心调整:新增「水平滚动容器+颜色排列容器」二级结构内置圆形按钮无额外drawable依赖
*/
private void showSystemColorPicker() {
LogUtils.d(TAG, "show system color picker | 兼容小米API29-30支持横向滚动");
final android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(getContext());
builder.setTitle("选择基础颜色");
// 50种常用颜色「红→橙→黄→绿→青→蓝→紫→粉→棕→灰→黑白」彩虹光谱顺序排列
// 50种常用颜色按彩虹光谱顺序排列
final int[] systemColors = {
// 红色系6种深红→大红→浅红→玫红→暗红→橘红
0xFFCC0000, 0xFFFF0000, 0xFFFF6666, 0xFFFF1493, 0xFF8B0000, 0xFFFF4500,
// 橙色系5种深橙→橙→浅橙→橙黄→橘橙
0xFFCC6600, 0xFFFF8800, 0xFFFFAA33, 0xFFFFBB00, 0xFFF5A623,
// 黄色系5种深黄→黄→浅黄→鹅黄→金黄
0xFFCCCC00, 0xFFFFFF00, 0xFFFFEE99, 0xFFFFFACD, 0xFFFFD700,
// 绿色系7种深绿→绿→浅绿→草绿→薄荷绿→翠绿→墨绿
0xFF006600, 0xFF00FF00, 0xFF99FF99, 0xFF66CC66, 0xFF98FB98, 0xFF00FF99, 0xFF003300,
// 青色系5种深青→青→浅青→蓝绿→青绿
0xFF006666, 0xFF00FFFF, 0xFF99FFFF, 0xFF00CCCC, 0xFF40E0D0,
// 蓝色系8种深蓝→藏蓝→蓝→浅蓝→天蓝→宝蓝→湖蓝→靛蓝
0xFF0000CC, 0xFF00008B, 0xFF0000FF, 0xFF6666FF, 0xFF87CEEB, 0xFF0066FF, 0xFF0099FF, 0xFF4B0082,
// 紫色系6种深紫→紫→浅紫→紫罗兰→紫红→蓝紫
0xFF660099, 0xFF8800FF, 0xFFAA99FF, 0xFF9370DB, 0xFFCBC3E3, 0xFF8A2BE2,
// 粉色系5种深粉→粉→浅粉→嫩粉→桃粉
0xFFFF00FF, 0xFFFF99CC, 0xFFFFCCDD, 0xFFFFB6C1, 0xFFFFA5A5,
// 棕色系4种深棕→棕→浅棕→棕黄
0xFF8B4513, 0xFFA0522D, 0xFFD2B48C, 0xFFCD853F,
// 灰色系6种深灰→灰→浅灰→银灰→淡灰→浅银灰
0xFF333333, 0xFF666666, 0xFF888888, 0xFFAAAAAA, 0xFFCCCCCC, 0xFFE6E6E6,
// 黑白系3种黑→白→米白
0xFF000000, 0xFFFFFFFF, 0xFFFFFAFA
};
@@ -600,7 +584,7 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
colorLayout.setGravity(Gravity.CENTER_VERTICAL);
colorLayout.setPadding(dp2px(10), dp2px(10), dp2px(10), dp2px(10));
// 3. 循环添加颜色按钮(内置圆形效果,无额外依赖
// 3. 循环添加颜色按钮(内置圆形效果)
for (int i = 0; i < systemColors.length; i++) {
final int color = systemColors[i];
ImageView colorBtn = new ImageView(getContext());
@@ -610,17 +594,17 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
}
colorBtn.setLayoutParams(lp);
// 核心:内置圆形背景(白色边框+圆形形状无需drawable文件
// 内置圆形背景(白色边框+圆形形状)
GradientDrawable circleBg = new GradientDrawable();
circleBg.setShape(GradientDrawable.OVAL); // 圆形
circleBg.setColor(color); // 按钮颜色
circleBg.setStroke(dp2px(2), Color.WHITE); // 白色边框2dp宽区分颜色
colorBtn.setBackground(circleBg); // 设置圆形背景
circleBg.setShape(GradientDrawable.OVAL);
circleBg.setColor(color);
circleBg.setStroke(dp2px(2), Color.WHITE);
colorBtn.setBackground(circleBg);
colorBtn.setClickable(true);
colorBtn.setFocusable(true);
// 点击事件(逻辑不变)
// 点击事件
colorBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -641,8 +625,9 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
mCurrentColor = color;
updateAllViews();
builder.create().dismiss();
LogUtils.d(TAG, "select system color | 选择颜色:" + String.format("#%08X", color)
+ " | 透明度:" + mCurrentAlphaPercent + "%");
LogUtils.d(TAG, String.format("select system color | 选择颜色:%s | 透明度:%s",
String.format(FORMAT_COLOR_HEX, color),
String.format(FORMAT_PERCENT, mCurrentAlphaPercent)));
} finally {
isAppSelfUpdatingColor = false;
}
@@ -652,46 +637,47 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
colorLayout.addView(colorBtn);
}
// 层级嵌套(滚动容器→颜色容器)
// 层级嵌套
horizontalScrollView.addView(colorLayout);
builder.setView(horizontalScrollView).setNegativeButton("关闭", null).show();
}
// ====================== 点击事件实现(统一处理,逻辑清晰) ======================
// ====================== 点击事件实现 ======================
@Override
public void onClick(View v) {
//ToastUtils.show("onClick");
int id = v.getId();
// 关键:所有点击事件均加判断(避免并发冲突/重复触发)
// 所有点击事件均加并发判断
if (!isAppSelfUpdatingColor) {
if (id == R.id.iv_color_picker) {
showSystemColorPicker(); // 打开系统颜色选择器
} if (id == R.id.iv_color_scaler) {
//ToastUtils.show("iv_color_scale");
openColorScalerDialog(mCurrentColor); // 打开系统颜色选择器
showSystemColorPicker();
} else if (id == R.id.iv_color_scaler) {
openColorScalerDialog(mCurrentColor);
} else if (id == R.id.tv_confirm) {
mListener.onColorSelected(mCurrentColor); // 确认选择,回调颜色
LogUtils.d(TAG, "confirm color | 回调颜色:" + String.format("#%08X", mCurrentColor));
mListener.onColorSelected(mCurrentColor);
LogUtils.d(TAG, String.format("confirm color | 回调颜色:%s",
String.format(FORMAT_COLOR_HEX, mCurrentColor)));
dismiss();
} else if (id == R.id.tv_cancel) {
dismiss(); // 取消,关闭对话框
dismiss();
LogUtils.d(TAG, "cancel color | 取消选择,关闭对话框");
} else if (id == R.id.tv_brightness_minus) {
decreaseBrightness(); // 减少亮度
decreaseBrightness();
} else if (id == R.id.tv_brightness_plus) {
increaseBrightness(); // 增加亮度
increaseBrightness();
}
}
}
/**
* 打开颜色渐变选择器
*/
void openColorScalerDialog(int nColor) {
//ToastUtils.show("openColorPickerDialog");
LogUtils.d(TAG, String.format("openColorScalerDialog | 初始颜色:%s",
String.format(FORMAT_COLOR_HEX, nColor)));
final ColorScalerDialog dlg = new ColorScalerDialog(getContext(), nColor);
dlg.setOnColorChangedListener(new com.a4455jkjh.colorpicker.view.OnColorChangedListener() {
dlg.setOnColorChangedListener(new OnColorChangedListener() {
@Override
public void beforeColorChanged() {
}
public void beforeColorChanged() {}
@Override
public void onColorChanged(int color) {
@@ -699,26 +685,25 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
}
@Override
public void afterColorChanged() {
}
public void afterColorChanged() {}
});
dlg.show();
}
// ====================== 内部类 ======================
class ColorScalerDialog extends ColorPickerDialog {
public int currentColorScalerDialogColor = 0;
public ColorScalerDialog(Context context, int p) {
super(context, p);
this.currentColorScalerDialogColor = p;
}
@Override
public void dismiss() {
super.dismiss();
int color = currentColorScalerDialogColor;
ToastUtils.show(String.format("dismiss color %d", color));
ToastUtils.show(String.format("选择颜色:%s", String.format(FORMAT_COLOR_HEX, color)));
if (!isAppSelfUpdatingColor) {
isAppSelfUpdatingColor = true;
try {
@@ -735,8 +720,9 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
mCurrentBrightnessPercent = DEFAULT_BRIGHTNESS;
mCurrentColor = color;
updateAllViews();
LogUtils.d(TAG, "select system color | 选择颜色:" + String.format("#%08X", color)
+ " | 透明度:" + mCurrentAlphaPercent + "%");
LogUtils.d(TAG, String.format("select scaler color | 选择颜色:%s | 透明度:%s",
String.format(FORMAT_COLOR_HEX, color),
String.format(FORMAT_PERCENT, mCurrentAlphaPercent)));
} finally {
isAppSelfUpdatingColor = false;
}

View File

@@ -1,10 +1,10 @@
package cc.winboll.studio.powerbell.dialogs;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
@@ -15,69 +15,82 @@ import androidx.appcompat.app.AlertDialog;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
import cc.winboll.studio.powerbell.utils.ImageDownloader;
import cc.winboll.studio.powerbell.views.BackgroundView;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import cc.winboll.studio.powerbell.utils.ImageDownloader;
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
import android.text.TextUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/19 20:11
* @Describe 网络后台使用提示对话框
* @Describe 网络背景使用提示对话框
* 继承 AndroidX AlertDialog绑定自定义布局 dialog_networkbackground.xml
* 适配 API30基于 Java7 开发,支持网络图片下载、预览与回调
*/
public class NetworkBackgroundDialog extends AlertDialog {
// ====================== 静态常量(首屏可见,统一管理) ======================
public static final String TAG = "NetworkBackgroundDialog";
// 消息标识:图片加载成功
private static final int MSG_IMAGE_LOAD_SUCCESS = 1001;
// 消息标识:图片加载失败
private static final int MSG_IMAGE_LOAD_FAILED = 1002;
private static final int MSG_IMAGE_LOAD_SUCCESS = 1001; // 图片加载成功消息标识
private static final int MSG_IMAGE_LOAD_FAILED = 1002; // 图片加载失败消息标识
// 控件引用
private TextView tvTitle;
private TextView tvContent;
private Button btnCancel;
private Button btnConfirm;
private Button btnPreview;
private EditText etURL;
BackgroundView mBackgroundView;
Context mContext;
// 主线程 Handler用于接收子线程消息并更新 UI
private Handler mUiHandler;
String mPreviewFilePath;
String mPreviewFileUrl;
String mDownloadSavedPath;
// 按钮点击回调接口Java7 接口实现)
// ====================== 回调接口(紧跟常量,逻辑关联) ======================
/**
* 按钮点击回调接口Java7 接口实现)
*/
public interface OnDialogClickListener {
void onConfirm(String szConfirmFilePath); // 确认按钮点击
void onConfirm(String szConfirmFilePath); // 确认按钮点击,返回图片路径
void onCancel(); // 取消按钮点击
}
private OnDialogClickListener listener;
// ====================== 成员变量(按优先级排序:核心数据→控件引用) ======================
// 核心数据
private OnDialogClickListener listener; // 按钮点击回调
private Context mContext; // 上下文对象
private Handler mUiHandler; // 主线程 Handler用于接收子线程消息更新 UI
private String mPreviewFilePath; // 预览图片文件路径
private String mPreviewFileUrl; // 预览图片网络 URL
private String mDownloadSavedPath; // 下载图片保存路径
// 控件引用
private TextView tvTitle; // 对话框标题
private TextView tvContent; // 对话框内容
private Button btnCancel; // 取消按钮
private Button btnConfirm; // 确认按钮
private Button btnPreview; // 预览按钮
private EditText etURL; // URL 输入框
private BackgroundView mBackgroundView; // 背景预览视图
// Java7 显式构造(必须传入 Context
// ====================== 构造方法Java7 显式构造,按参数重载排序) ======================
/**
* 基础构造(仅传入 Context
* @param context 上下文
*/
public NetworkBackgroundDialog(@NonNull Context context) {
super(context);
initHandler(); // 初始化 Handler
initView(); // 初始化布局和控件
setDismissListener(); // 设置对话框消失监听
}
// 带回调的构造(便于外部处理点击事件)
public NetworkBackgroundDialog(@NonNull Context context, OnDialogClickListener listener) {
super(context);
this.listener = listener;
initHandler(); // 初始化 Handler
LogUtils.d(TAG, "NetworkBackgroundDialog: 基础构造初始化");
initHandler();
initView();
setDismissListener(); // 设置对话框消失监听
setDismissListener();
}
/**
* 初始化主线程 Handler用于更新 UI
* 带回调的构造(便于外部处理点击事件)
* @param context 上下文
* @param listener 按钮点击回调
*/
public NetworkBackgroundDialog(@NonNull Context context, OnDialogClickListener listener) {
super(context);
this.listener = listener;
LogUtils.d(TAG, "NetworkBackgroundDialog: 带回调构造初始化");
initHandler();
initView();
setDismissListener();
}
// ====================== 生命周期相关方法对话框消失监听、Handler 初始化) ======================
/**
* 初始化主线程 Handler用于接收子线程消息并更新 UI
*/
private void initHandler() {
mUiHandler = new Handler() {
@@ -86,22 +99,28 @@ public class NetworkBackgroundDialog extends AlertDialog {
super.handleMessage(msg);
// 对话框已消失时,不再处理 UI 消息
if (!isShowing()) {
LogUtils.d(TAG, "handleMessage: 对话框已消失,忽略消息");
return;
}
switch (msg.what) {
case MSG_IMAGE_LOAD_SUCCESS:
// 图片加载成功,获取文件路径并设置背景
mDownloadSavedPath = (String) msg.obj;
LogUtils.d(TAG, String.format("handleMessage: 图片加载成功,保存路径:%s", mDownloadSavedPath));
mBackgroundView.loadImage(mDownloadSavedPath);
break;
case MSG_IMAGE_LOAD_FAILED:
// 图片加载失败,设置默认背景
LogUtils.e(TAG, "handleMessage: 图片加载失败");
mBackgroundView.setBackgroundResource(R.drawable.ic_launcher);
ToastUtils.show("图片预览失败,请检查链接");
break;
default:
break;
}
}
};
LogUtils.d(TAG, "initHandler: 主线程 Handler 初始化完成");
}
/**
@@ -114,20 +133,22 @@ public class NetworkBackgroundDialog extends AlertDialog {
// 对话框消失时,移除所有未处理的消息和回调
if (mUiHandler != null) {
mUiHandler.removeCallbacksAndMessages(null);
LogUtils.d(TAG, "onDismiss: Handler 消息已清理");
}
LogUtils.d(TAG, "对话框已消失Handler 消息已清理");
LogUtils.d(TAG, "onDismiss: 对话框已消失");
}
});
LogUtils.d(TAG, "setDismissListener: 对话框消失监听已设置");
}
// ====================== 初始化方法(布局、控件、点击事件) ======================
/**
* 初始化布局和控件
*/
private void initView() {
mContext = this.getContext();
// 加载自定义布局
View dialogView = LayoutInflater.from(getContext())
.inflate(R.layout.dialog_networkbackground, null);
View dialogView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_networkbackground, null);
// 设置对话框内容视图
setView(dialogView);
@@ -139,10 +160,21 @@ public class NetworkBackgroundDialog extends AlertDialog {
btnPreview = (Button) dialogView.findViewById(R.id.btn_preview);
etURL = (EditText) dialogView.findViewById(R.id.et_url);
mBackgroundView = (BackgroundView) dialogView.findViewById(R.id.bv_background_preview);
// 控件非空校验
if (tvTitle == null || tvContent == null || btnCancel == null || btnConfirm == null || btnPreview == null
|| etURL == null || mBackgroundView == null) {
LogUtils.e(TAG, "initView: 控件绑定失败请检查布局ID是否正确");
dismiss();
return;
}
// 加载初始图片
mBackgroundView.setBackgroundResource(R.drawable.blank100x100);
// 设置按钮点击事件
setButtonClickListeners();
LogUtils.d(TAG, "initView: 布局和控件初始化完成");
}
/**
@@ -153,13 +185,14 @@ public class NetworkBackgroundDialog extends AlertDialog {
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d("NetworkBackgroundDialog", "取消按钮点击");
LogUtils.d(TAG, "onClick: 取消按钮点击");
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(mContext);
utils.setCurrentSourceToPreview();
dismiss(); // 关闭对话框
if (listener != null) {
listener.onCancel();
LogUtils.d(TAG, "onClick: 取消回调已执行");
}
}
});
@@ -168,14 +201,16 @@ public class NetworkBackgroundDialog extends AlertDialog {
btnConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d("NetworkBackgroundDialog", "确认按钮点击");
LogUtils.d(TAG, "onClick: 确认按钮点击");
dismiss(); // 关闭对话框
if (TextUtils.isEmpty(mDownloadSavedPath)) {
ToastUtils.show("未下载图片。");
LogUtils.w(TAG, "onClick: 确认失败,未下载图片");
return;
}
if (listener != null) {
listener.onConfirm(mDownloadSavedPath);
LogUtils.d(TAG, String.format("onClick: 确认回调已执行,图片路径:%s", mDownloadSavedPath));
}
}
});
@@ -184,22 +219,63 @@ public class NetworkBackgroundDialog extends AlertDialog {
btnPreview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "onClick: 预览按钮点击");
downloadImageToAlbumAndPreview();
}
});
LogUtils.d(TAG, "setButtonClickListeners: 按钮点击监听已设置");
}
// ====================== 业务逻辑方法(图片下载、预览) ======================
/**
* 下载网络图片并预览
*/
void downloadImageToAlbumAndPreview() {
mPreviewFileUrl = etURL.getText().toString().trim();
if (TextUtils.isEmpty(mPreviewFileUrl)) {
ToastUtils.show("请输入图片URL");
LogUtils.w(TAG, "downloadImageToAlbumAndPreview: 图片URL为空");
return;
}
LogUtils.d(TAG, String.format("downloadImageToAlbumAndPreview: 开始下载图片URL%s", mPreviewFileUrl));
ImageDownloader.getInstance(mContext).downloadImage(mPreviewFileUrl, new ImageDownloader.DownloadCallback() {
@Override
public void onSuccess(String savePath) {
LogUtils.d(TAG, String.format("onSuccess: 图片下载成功,保存路径:%s", savePath));
// 发送消息到主线程,携带图片路径
Message successMsg = mUiHandler.obtainMessage(MSG_IMAGE_LOAD_SUCCESS, savePath);
mUiHandler.sendMessage(successMsg);
}
@Override
public void onFailure(String errorMsg) {
LogUtils.e(TAG, String.format("onFailure: 图片下载失败,错误信息:%s", errorMsg));
ToastUtils.show("下载失败:" + errorMsg);
// 发送图片加载失败消息
Message failMsg = mUiHandler.obtainMessage(MSG_IMAGE_LOAD_FAILED);
mUiHandler.sendMessage(failMsg);
}
});
}
/**
* 根据文件路径设置 BackgroundView 背景(主线程调用)
* @param filePath 图片文件路径
* @param previewFilePath 图片文件路径
*/
private void previewBackground(String previewFilePath) {
if (TextUtils.isEmpty(previewFilePath)) {
LogUtils.w(TAG, "previewBackground: 预览文件路径为空");
return;
}
FileInputStream fis = null;
try {
File imageFile = new File(previewFilePath);
if (!imageFile.exists()) {
ToastUtils.show("图片文件不存在:" + previewFilePath);
LogUtils.e(TAG, "图片文件不存在:" + previewFilePath);
LogUtils.e(TAG, String.format("previewBackground: 图片文件不存在,路径:%s", previewFilePath));
mBackgroundView.setBackgroundResource(R.drawable.ic_launcher);
return;
}
@@ -209,65 +285,54 @@ public class NetworkBackgroundDialog extends AlertDialog {
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(mContext);
utils.saveFileToPreviewBean(new File(mPreviewFilePath), mPreviewFileUrl);
mBackgroundView.loadByBackgroundBean(utils.getPreviewBackgroundBean());
LogUtils.d(TAG, String.format("previewBackground: 图片预览成功,路径:%s", previewFilePath));
} catch (Exception e) {
e.printStackTrace();
LogUtils.e(TAG, String.format("previewBackground: 图片预览失败,错误信息:%s", e.getMessage()), e);
mBackgroundView.setBackgroundResource(R.drawable.ic_launcher);
LogUtils.e(TAG, "图片预览失败:" + e.getMessage());
} finally {
// Java7 手动关闭流,避免资源泄漏
if (fis != null) {
try {
fis.close();
LogUtils.d(TAG, "previewBackground: 文件输入流已关闭");
} catch (IOException e) {
e.printStackTrace();
LogUtils.e(TAG, String.format("previewBackground: 关闭文件输入流失败,错误信息:%s", e.getMessage()), e);
}
}
}
}
// ====================== 对外提供方法(灵活适配不同场景) ======================
/**
* 对外提供方法:修改对话框标题(灵活适配不同场景)
* 对外提供方法:修改对话框标题
* @param title 标题文本
*/
public void setTitle(String title) {
if (tvTitle != null) {
if (tvTitle != null && !TextUtils.isEmpty(title)) {
tvTitle.setText(title);
LogUtils.d(TAG, String.format("setTitle: 对话框标题已修改为:%s", title));
}
}
/**
* 对外提供方法:修改对话框内容(灵活适配不同场景)
* 对外提供方法:修改对话框内容
* @param content 内容文本
*/
public void setContent(String content) {
if (tvContent != null) {
if (tvContent != null && !TextUtils.isEmpty(content)) {
tvContent.setText(content);
LogUtils.d(TAG, String.format("setContent: 对话框内容已修改为:%s", content));
}
}
/**
* 对外提供方法:设置按钮点击回调(替代带参构造)
* @param listener 按钮点击回调
*/
public void setOnDialogClickListener(OnDialogClickListener listener) {
this.listener = listener;
}
void downloadImageToAlbumAndPreview() {
//String previewFileUrl = "https://example.com/test.jpg";
mPreviewFileUrl = etURL.getText().toString();
ImageDownloader.getInstance(mContext).downloadImage(mPreviewFileUrl, new ImageDownloader.DownloadCallback(){
@Override
public void onSuccess(String savePath) {
// 发送消息到主线程,携带图片路径
Message successMsg = mUiHandler.obtainMessage(MSG_IMAGE_LOAD_SUCCESS, savePath);
mUiHandler.sendMessage(successMsg);
}
@Override
public void onFailure(String errorMsg) {
ToastUtils.show("下载失败:" + errorMsg);
}
});
LogUtils.d(TAG, "setOnDialogClickListener: 按钮点击回调已设置");
}
}

View File

@@ -1,59 +0,0 @@
package cc.winboll.studio.powerbell.dialogs;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/06/10 19:32:55
* @Describe 用户确定与否选择框
*/
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
public class YesNoAlertDialog {
public static final String TAG = "YesNoAlertDialog";
public static void show(Context context, String szTitle, String szMessage, final OnDialogResultListener listener) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
context);
// set title
alertDialogBuilder.setTitle(szTitle);
// set dialog message
alertDialogBuilder
.setMessage(szMessage)
.setCancelable(true)
.setOnCancelListener(new DialogInterface.OnCancelListener(){
@Override
public void onCancel(DialogInterface dialog) {
listener.onNo();
}
})
.setPositiveButton("YES", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// if this button is clicked, close
// current activity
listener.onYes();
}
})
.setNegativeButton("NO", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// if this button is clicked, just close
// the dialog box and do nothing
dialog.cancel();
}
});
// create alert dialog
AlertDialog alertDialog = alertDialogBuilder.create();
// show it
alertDialog.show();
}
public interface OnDialogResultListener {
abstract void onYes();
abstract void onNo();
}
}

View File

@@ -1,127 +1,175 @@
package cc.winboll.studio.powerbell.models;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/04/29 17:24:53
* @Describe 应用运行参数类
*/
import android.os.Parcel;
import android.os.Parcelable;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.IOException;
import java.io.Serializable;
// 核心修正:新增 Parcelable 接口实现API30 持久化/Intent 传递必备)
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/04/29 17:24:53
* @Describe 应用运行参数类
* 适配 API30支持 Serializable 持久化、Parcelable Intent 传递、JSON 序列化/反序列化
* 包含耗电提醒、充电提醒、电量检测、铃声提醒等核心配置
*/
public class AppConfigBean extends BaseBean implements Serializable, Parcelable {
// ====================== 静态常量(首屏可见,统一管理) ======================
// 序列化版本号Serializable 必备,避免反序列化失败)
private static final long serialVersionUID = 1L;
// 日志标签(全局统一,替换 Log 为 LogUtils
transient public static final String TAG = "AppConfigBean";
// 字段校验常量(统一阈值,避免硬编码)
private static final int MIN_INTERVAL = 500; // 最小检测间隔ms
private static final int MIN_REMIND_INTERVAL = 1000; // 最小提醒间隔ms
private static final int BATTERY_MIN = 0; // 电量最小值
private static final int BATTERY_MAX = 100; // 电量最大值
private static final int INVALID_BATTERY = -1; // 无效电量标识
// 核心配置字段(保留原有字段,统一状态字段命名)
// ====================== 成员变量(按功能分类:提醒配置→电量状态→检测配置) ======================
// 耗电提醒配置
boolean isEnableUsageReminder = false; // 耗电提醒开关
int usageReminderValue = 45; // 耗电提醒阈值0-100
// 充电提醒配置
boolean isEnableChargeReminder = false;// 充电提醒开关
int chargeReminderValue = 100; // 充电提醒阈值0-100
int reminderIntervalTime = 5000; // 铃声提醒间隔ms原有
boolean isCharging = false; // 是否充电(状态字段,原有
int currentBatteryValue = -1; // 修正统一命名为「currentBatteryValue」原 currentValue
int batteryDetectInterval = 2000; // 新增电量检测间隔ms适配 RemindThread
// 铃声提醒配置
int reminderIntervalTime = 5000; // 铃声提醒间隔ms
// 电量状态
boolean isCharging = false; // 是否充电
int currentBatteryValue = INVALID_BATTERY; // 当前电池电量(统一命名,替代原 currentValue
// 电量检测配置
int batteryDetectInterval = 2000; // 电量检测间隔ms适配 RemindThread
// 构造方法初始化默认配置(同步修正字段名,统一默认值)
// ====================== 构造方法初始化默认配置,强化默认值校验) ======================
public AppConfigBean() {
setChargeReminderValue(100);
setEnableChargeReminder(false);
setUsageReminderValue(10);
setEnableUsageReminder(false);
setReminderIntervalTime(5000);
setBatteryDetectInterval(1000); // 新增:默认检测间隔1秒
setCurrentBatteryValue(-1); // 修正:初始化当前电量字段
setBatteryDetectInterval(1000); // 默认检测间隔1秒
setCurrentBatteryValue(INVALID_BATTERY); // 初始化无效电量标识
LogUtils.d(TAG, "AppConfigBean: 初始化默认配置完成");
}
// ====================== 核心修复:补全缺失方法(适配 RemindThread/Receiver 调用 ======================
// ====================== 核心业务方法Setter/Getter按字段功能分类补充调试日志 ======================
// --------------- 电量状态相关 ---------------
/**
* 设置当前电池电量Receiver 监听电池变化时调用,与 RemindThread 字段对齐
* 设置当前电池电量Receiver 监听电池变化时调用)
* @param currentBatteryValue 当前电量0-100
*/
public void setCurrentBatteryValue(int currentBatteryValue) {
// 强化校验:电量范围限制在 0-100异常值置为 -1标识无效
this.currentBatteryValue = (currentBatteryValue >= 0 && currentBatteryValue <= 100)
? currentBatteryValue : -1;
this.currentBatteryValue = (currentBatteryValue >= BATTERY_MIN && currentBatteryValue <= BATTERY_MAX)
? currentBatteryValue : INVALID_BATTERY;
LogUtils.d(TAG, String.format("setCurrentBatteryValue: 当前电量设置为 %d输入值%d",
this.currentBatteryValue, currentBatteryValue));
}
/**
* 获取当前电池电量RemindThread 同步配置时调用,与 set 方法对应
* 获取当前电池电量RemindThread 同步配置时调用)
* @return 当前电量0-100 或 INVALID_BATTERY
*/
public int getCurrentBatteryValue() {
return currentBatteryValue;
}
// ====================== 原有字段 Setter/Getter修正命名强化校验 ======================
// --------------- 铃声提醒配置相关 ---------------
/**
* 设置铃声提醒间隔
* @param reminderIntervalTime 提醒间隔ms不小于 MIN_REMIND_INTERVAL
*/
public void setReminderIntervalTime(int reminderIntervalTime) {
// 校验:提醒间隔不小于 1000ms避免频繁提醒
this.reminderIntervalTime = Math.max(reminderIntervalTime, 1000);
this.reminderIntervalTime = Math.max(reminderIntervalTime, MIN_REMIND_INTERVAL);
LogUtils.d(TAG, String.format("setReminderIntervalTime: 提醒间隔设置为 %dms输入值%d",
this.reminderIntervalTime, reminderIntervalTime));
}
public int getReminderIntervalTime() {
return reminderIntervalTime;
}
public void setIsCharging(boolean isCharging) { // 修正:方法名与字段名统一(原 setCharging
// --------------- 充电状态相关 ---------------
/**
* 设置是否充电
* @param isCharging 充电状态
*/
public void setIsCharging(boolean isCharging) {
this.isCharging = isCharging;
LogUtils.d(TAG, String.format("setIsCharging: 充电状态设置为 %b", isCharging));
}
public boolean isCharging() {
return isCharging;
}
// --------------- 耗电提醒配置相关 ---------------
public void setEnableUsageReminder(boolean isEnableUsageReminder) {
this.isEnableUsageReminder = isEnableUsageReminder;
LogUtils.d(TAG, String.format("setEnableUsageReminder: 耗电提醒开关设置为 %b", isEnableUsageReminder));
}
public boolean isEnableUsageReminder() {
return isEnableUsageReminder;
}
/**
* 设置耗电提醒阈值
* @param usageReminderValue 阈值0-100
*/
public void setUsageReminderValue(int usageReminderValue) {
// 校验:阈值范围 0-100
this.usageReminderValue = Math.min(Math.max(usageReminderValue, 0), 100);
this.usageReminderValue = Math.min(Math.max(usageReminderValue, BATTERY_MIN), BATTERY_MAX);
LogUtils.d(TAG, String.format("setUsageReminderValue: 耗电提醒阈值设置为 %d输入值%d",
this.usageReminderValue, usageReminderValue));
}
public int getUsageReminderValue() {
return usageReminderValue;
}
// --------------- 充电提醒配置相关 ---------------
public void setEnableChargeReminder(boolean isEnableChargeReminder) {
this.isEnableChargeReminder = isEnableChargeReminder;
LogUtils.d(TAG, String.format("setEnableChargeReminder: 充电提醒开关设置为 %b", isEnableChargeReminder));
}
public boolean isEnableChargeReminder() {
return isEnableChargeReminder;
}
/**
* 设置充电提醒阈值
* @param chargeReminderValue 阈值0-100
*/
public void setChargeReminderValue(int chargeReminderValue) {
// 校验:阈值范围 0-100
this.chargeReminderValue = Math.min(Math.max(chargeReminderValue, 0), 100);
this.chargeReminderValue = Math.min(Math.max(chargeReminderValue, BATTERY_MIN), BATTERY_MAX);
LogUtils.d(TAG, String.format("setChargeReminderValue: 充电提醒阈值设置为 %d输入值%d",
this.chargeReminderValue, chargeReminderValue));
}
public int getChargeReminderValue() {
return chargeReminderValue;
}
// ====================== 电量检测间隔 Setter/Getter适配 RemindThread ======================
// --------------- 电量检测配置相关 ---------------
/**
* 设置电量检测间隔
* @param batteryDetectInterval 检测间隔ms不小于 MIN_INTERVAL
*/
public void setBatteryDetectInterval(int batteryDetectInterval) {
this.batteryDetectInterval = Math.max(batteryDetectInterval, MIN_INTERVAL);
LogUtils.d(TAG, String.format("setBatteryDetectInterval: 检测间隔设置为 %dms输入值%d",
this.batteryDetectInterval, batteryDetectInterval));
}
public int getBatteryDetectInterval() {
return batteryDetectInterval;
}
// 强化校验检测间隔不小于500ms避免 CPU 高占用,与 RemindThread 最小休眠一致)
public void setBatteryDetectInterval(int batteryDetectInterval) {
this.batteryDetectInterval = Math.max(batteryDetectInterval, 500);
}
// ====================== JSON 序列化/反序列化(兼容旧配置,同步修正字段) ======================
// ====================== JSON 序列化/反序列化(兼容旧配置,补充调试日志) ======================
@Override
public String getName() {
return AppConfigBean.class.getName();
@@ -131,18 +179,19 @@ public class AppConfigBean extends BaseBean implements Serializable, Parcelable
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
AppConfigBean bean = this;
// 原有字段序列化(保留拼写兼容,同步修正字段名)
// 原有字段序列化
jsonWriter.name("isEnableUsageReminder").value(bean.isEnableUsageReminder());
jsonWriter.name("usageReminderValue").value(bean.getUsageReminderValue());
jsonWriter.name("isEnableChargeReminder").value(bean.isEnableChargeReminder());
jsonWriter.name("chargeReminderValue").value(bean.getChargeReminderValue());
jsonWriter.name("reminderIntervalTime").value(bean.getReminderIntervalTime());
jsonWriter.name("isCharging").value(bean.isCharging());
// 修正:序列化新字段 currentBatteryValue兼容旧字段 currentValue
// 兼容旧字段 currentValue同步新字段 currentBatteryValue
jsonWriter.name("currentBatteryValue").value(bean.getCurrentBatteryValue());
jsonWriter.name("currentValue").value(bean.getCurrentBatteryValue()); // 兼容旧配置,避免数据丢失
// 新增字段序列化:电量检测间隔
jsonWriter.name("currentValue").value(bean.getCurrentBatteryValue());
// 新增字段序列化
jsonWriter.name("batteryDetectInterval").value(bean.getBatteryDetectInterval());
LogUtils.d(TAG, "writeThisToJsonWriter: JSON 序列化完成");
}
@Override
@@ -151,7 +200,7 @@ public class AppConfigBean extends BaseBean implements Serializable, Parcelable
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
// 原有字段反序列化(兼容旧 Key 拼写,同步修正字段
// 兼容拼写错误字段isEnableUsegeReminder → isEnableUsageReminder
if (name.equals("isEnableUsageReminder") || name.equals("isEnableUsegeReminder")) {
bean.setEnableUsageReminder(jsonReader.nextBoolean());
} else if (name.equals("usageReminderValue") || name.equals("usegeReminderValue")) {
@@ -163,58 +212,62 @@ public class AppConfigBean extends BaseBean implements Serializable, Parcelable
} else if (name.equals("reminderIntervalTime")) {
bean.setReminderIntervalTime(jsonReader.nextInt());
} else if (name.equals("isCharging")) {
bean.setIsCharging(jsonReader.nextBoolean()); // 修正:调用新方法名
}
// 核心兼容:优先读取旧字段 currentValue再读取新字段 currentBatteryValue新字段覆盖旧字段
else if (name.equals("currentValue")) {
bean.setIsCharging(jsonReader.nextBoolean());
} else if (name.equals("currentValue")) {
// 优先读取旧字段,兼容历史配置
bean.setCurrentBatteryValue(jsonReader.nextInt());
LogUtils.d(TAG, "readBeanFromJsonReader: 读取旧字段 currentValue 完成");
} else if (name.equals("currentBatteryValue")) {
// 新字段覆盖旧字段,保证数据最新
bean.setCurrentBatteryValue(jsonReader.nextInt());
}
// 新增字段反序列化兼容无此字段的旧配置用默认值1000ms
else if (name.equals("batteryDetectInterval")) {
LogUtils.d(TAG, "readBeanFromJsonReader: 读取新字段 currentBatteryValue 完成");
} else if (name.equals("batteryDetectInterval")) {
bean.setBatteryDetectInterval(jsonReader.nextInt());
} else {
jsonReader.skipValue();
LogUtils.w(TAG, String.format("readBeanFromJsonReader: 跳过未知字段 %s", name));
}
}
jsonReader.endObject();
LogUtils.d(TAG, "readBeanFromJsonReader: JSON 反序列化完成");
return bean;
}
// ====================== Parcelable 接口实现(同步修正字段,确保 Intent 传递正常 ======================
// ====================== Parcelable 接口实现(API30 Intent 传递必备,补充调试日志 ======================
@Override
public int describeContents() {
return 0; // 无特殊内容描述固定返回0
}
// 序列化:将所有字段写入 Parcel同步修正字段名Java7 适配)
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (isEnableUsageReminder ? 1 : 0)); // boolean byte
// 按成员变量顺序写入,boolean byte 存储
dest.writeByte((byte) (isEnableUsageReminder ? 1 : 0));
dest.writeInt(usageReminderValue);
dest.writeByte((byte) (isEnableChargeReminder ? 1 : 0)); // boolean → byte
dest.writeByte((byte) (isEnableChargeReminder ? 1 : 0));
dest.writeInt(chargeReminderValue);
dest.writeInt(reminderIntervalTime);
dest.writeByte((byte) (isCharging ? 1 : 0)); // boolean → byte
dest.writeInt(currentBatteryValue); // 修正:序列化新字段名
dest.writeByte((byte) (isCharging ? 1 : 0));
dest.writeInt(currentBatteryValue);
dest.writeInt(batteryDetectInterval);
LogUtils.d(TAG, "writeToParcel: Parcel 序列化完成");
}
// 反序列化:从 Parcel 读取字段,创建对象(必须 public static final 修饰)
// 反序列化 Creator(必须 public static final 修饰Java7 适配
public static final Parcelable.Creator<AppConfigBean> CREATOR = new Parcelable.Creator<AppConfigBean>() {
@Override
public AppConfigBean createFromParcel(Parcel source) {
AppConfigBean bean = new AppConfigBean();
// 按 writeToParcel 顺序读取,同步修正字段
// 按 writeToParcel 顺序读取
bean.isEnableUsageReminder = source.readByte() != 0;
bean.usageReminderValue = source.readInt();
bean.isEnableChargeReminder = source.readByte() != 0;
bean.chargeReminderValue = source.readInt();
bean.reminderIntervalTime = source.readInt();
bean.isCharging = source.readByte() != 0;
bean.currentBatteryValue = source.readInt(); // 修正:读取新字段名
bean.currentBatteryValue = source.readInt();
bean.batteryDetectInterval = source.readInt();
LogUtils.d(TAG, "createFromParcel: Parcel 反序列化完成");
return bean;
}

View File

@@ -3,51 +3,61 @@ package cc.winboll.studio.powerbell.models;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.IOException;
import java.io.Serializable;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/07/18 11:52:28
* @Describe 应用背景图片数据类(存储正式/预览背景配置支持JSON序列化/反序列化)
* @Describe 应用背景图片数据类
* 适配 API30支持 Serializable 持久化、JSON 序列化/反序列化
* 存储正式/预览背景配置,包含原图、压缩图、裁剪比例、像素颜色等核心字段
*/
public class BackgroundBean extends BaseBean implements Serializable {
// ====================== 静态常量(首屏可见,统一管理) ======================
// 日志标签(全局统一,替换 Log 为 LogUtils
public static final String TAG = "BackgroundBean";
// 兼容旧字段常量(统一管理,避免硬编码)
private static final String OLD_FIELD_USE_SCALED_COMPRESS = "isUseScaledCompress";
// 字段默认值常量(统一管理,避免魔法值)
private static final int DEFAULT_DIMENSION = 100; // 默认宽高
private static final int MIN_DIMENSION = 1; // 最小宽高
public static final String TAG = "BackgroundPictureBean";
// 核心字段背景图片文件名对应应用私有目录下的图片文件与BackgroundSettingsActivity的_mSourceCroppedFile匹配
private String backgroundFileName = "";
// 核心字段背景图片完整路径解决仅存文件名导致的路径拼接错误与backgroundScaledCompressFilePath对应
private String backgroundFilePath = "";
// 附加字段图片信息如Uri、网络地址等仅作备注不参与路径生成
private String backgroundFileInfo = "";
// 控制字段是否启用背景图片true-显示背景图false-显示透明背景)
private boolean isUseBackgroundFile = false;
// 核心字段:压缩背景图片文件名对应应用私有目录下的压缩图片与saveCropBitmap的压缩图匹配
private String backgroundScaledCompressFileName = "";
// 核心字段压缩后背景图片完整路径解决仅存文件名导致的路径拼接错误适配BackgroundSettingsActivity的私有目录
private String backgroundScaledCompressFilePath = "";
// 重命名字段是否启用压缩背景图原isUseScaledCompress → 新isUseBackgroundScaledCompressFile语义更清晰
private boolean isUseBackgroundScaledCompressFile = false;
// 裁剪比例字段背景图宽高比默认1:1用于固定比例裁剪
private int backgroundWidth = 100;
private int backgroundHeight = 100;
// 像素拾取字段:拾取的像素颜色(用于纯色背景)
private int pixelColor = 0;
// ====================== 成员变量(按功能分类:原图配置→压缩图配置→控制字段→裁剪配置→像素颜色) ======================
// 原图配置
private String backgroundFileName = ""; // 背景图片文件名
private String backgroundFilePath = ""; // 背景图片完整路径
private String backgroundFileInfo = ""; // 图片信息Uri、网络地址等
// 压缩图配置
private String backgroundScaledCompressFileName = ""; // 压缩后背景图片文件名
private String backgroundScaledCompressFilePath = ""; // 压缩后背景图片完整路径
// 控制字段
private boolean isUseBackgroundFile = false; // 是否启用背景图片
private boolean isUseBackgroundScaledCompressFile = false; // 是否启用压缩背景图重命名原isUseScaledCompress
// 裁剪配置
private int backgroundWidth = DEFAULT_DIMENSION; // 背景图宽度
private int backgroundHeight = DEFAULT_DIMENSION; // 背景图高度
// 像素颜色
private int pixelColor = 0; // 拾取的像素颜色(纯色背景用)
// ====================== 构造方法无参构造JSON反序列化必备 ======================
/**
* 无参构造器必须JSON反序列化时需默认构造器
*/
public BackgroundBean() {
LogUtils.d(TAG, "BackgroundBean: 无参构造初始化完成");
}
// ====================================== Getter/Setter 方法(全字段,含重命名+新增字段)======================================
// ====================== Getter/Setter 方法(按功能分类,补充调试日志,强化校验) ======================
// --------------- 原图配置相关 ---------------
public String getBackgroundFileName() {
return backgroundFileName;
}
public void setBackgroundFileName(String backgroundFileName) {
this.backgroundFileName = backgroundFileName == null ? "" : backgroundFileName; // 防null避免空指针
this.backgroundFileName = backgroundFileName == null ? "" : backgroundFileName;
LogUtils.d(TAG, String.format("setBackgroundFileName: 背景文件名设置为 %s", this.backgroundFileName));
}
public String getBackgroundFilePath() {
@@ -55,7 +65,8 @@ public class BackgroundBean extends BaseBean implements Serializable {
}
public void setBackgroundFilePath(String backgroundFilePath) {
this.backgroundFilePath = backgroundFilePath == null ? "" : backgroundFilePath; // 防null避免路径拼接错误
this.backgroundFilePath = backgroundFilePath == null ? "" : backgroundFilePath;
LogUtils.d(TAG, String.format("setBackgroundFilePath: 背景文件路径设置为 %s", this.backgroundFilePath));
}
public String getBackgroundFileInfo() {
@@ -63,23 +74,28 @@ public class BackgroundBean extends BaseBean implements Serializable {
}
public void setBackgroundFileInfo(String backgroundFileInfo) {
this.backgroundFileInfo = backgroundFileInfo == null ? "" : backgroundFileInfo; // 防null避免空指针
this.backgroundFileInfo = backgroundFileInfo == null ? "" : backgroundFileInfo;
LogUtils.d(TAG, String.format("setBackgroundFileInfo: 背景文件信息设置为 %s", this.backgroundFileInfo));
}
// --------------- 控制字段相关 ---------------
public boolean isUseBackgroundFile() {
return isUseBackgroundFile;
}
public void setIsUseBackgroundFile(boolean isUseBackgroundFile) {
this.isUseBackgroundFile = isUseBackgroundFile;
LogUtils.d(TAG, String.format("setIsUseBackgroundFile: 是否启用背景图设置为 %b", isUseBackgroundFile));
}
// --------------- 压缩图配置相关 ---------------
public String getBackgroundScaledCompressFileName() {
return backgroundScaledCompressFileName;
}
public void setBackgroundScaledCompressFileName(String backgroundScaledCompressFileName) {
this.backgroundScaledCompressFileName = backgroundScaledCompressFileName == null ? "" : backgroundScaledCompressFileName; // 防null
this.backgroundScaledCompressFileName = backgroundScaledCompressFileName == null ? "" : backgroundScaledCompressFileName;
LogUtils.d(TAG, String.format("setBackgroundScaledCompressFileName: 压缩背景文件名设置为 %s", this.backgroundScaledCompressFileName));
}
public String getBackgroundScaledCompressFilePath() {
@@ -87,7 +103,8 @@ public class BackgroundBean extends BaseBean implements Serializable {
}
public void setBackgroundScaledCompressFilePath(String backgroundScaledCompressFilePath) {
this.backgroundScaledCompressFilePath = backgroundScaledCompressFilePath == null ? "" : backgroundScaledCompressFilePath; // 防null避免路径错误
this.backgroundScaledCompressFilePath = backgroundScaledCompressFilePath == null ? "" : backgroundScaledCompressFilePath;
LogUtils.d(TAG, String.format("setBackgroundScaledCompressFilePath: 压缩背景文件路径设置为 %s", this.backgroundScaledCompressFilePath));
}
/**
@@ -100,14 +117,17 @@ public class BackgroundBean extends BaseBean implements Serializable {
public void setIsUseBackgroundScaledCompressFile(boolean isUseBackgroundScaledCompressFile) {
this.isUseBackgroundScaledCompressFile = isUseBackgroundScaledCompressFile;
LogUtils.d(TAG, String.format("setIsUseBackgroundScaledCompressFile: 是否启用压缩背景图设置为 %b", isUseBackgroundScaledCompressFile));
}
// --------------- 裁剪配置相关 ---------------
public int getBackgroundWidth() {
return backgroundWidth;
}
public void setBackgroundWidth(int backgroundWidth) {
this.backgroundWidth = backgroundWidth <= 0 ? 100 : backgroundWidth; // 防无效值,确保宽高比有效
this.backgroundWidth = backgroundWidth < MIN_DIMENSION ? DEFAULT_DIMENSION : backgroundWidth;
LogUtils.d(TAG, String.format("setBackgroundWidth: 背景宽度设置为 %d输入值%d", this.backgroundWidth, backgroundWidth));
}
public int getBackgroundHeight() {
@@ -115,44 +135,54 @@ public class BackgroundBean extends BaseBean implements Serializable {
}
public void setBackgroundHeight(int backgroundHeight) {
this.backgroundHeight = backgroundHeight <= 0 ? 100 : backgroundHeight; // 防无效值,确保宽高比有效
this.backgroundHeight = backgroundHeight < MIN_DIMENSION ? DEFAULT_DIMENSION : backgroundHeight;
LogUtils.d(TAG, String.format("setBackgroundHeight: 背景高度设置为 %d输入值%d", this.backgroundHeight, backgroundHeight));
}
// --------------- 像素颜色相关 ---------------
public int getPixelColor() {
return pixelColor;
}
public void setPixelColor(int pixelColor) {
this.pixelColor = pixelColor;
LogUtils.d(TAG, String.format("setPixelColor: 像素颜色设置为 0x%08X", pixelColor));
}
// ====================================== 序列化/反序列化方法(适配重命名字段,兼容旧版本======================================
// ====================== 序列化/反序列化方法(适配重命名字段,兼容旧版本,补充调试日志) ======================
@Override
public String getName() {
return BackgroundBean.class.getName(); // 必须重写BaseBean序列化时需类名标识
String className = BackgroundBean.class.getName();
LogUtils.d(TAG, String.format("getName: 类名标识为 %s", className));
return className;
}
/**
* 序列化同步重命名字段原isUseScaledCompress → 新isUseBackgroundScaledCompressFile
* 确保新字段能正常持久化同时兼容旧版本JSON可选:保留旧字段写入,避免旧版本读取异常)
* 确保新字段能正常持久化同时兼容旧版本JSON保留旧字段写入避免旧版本读取异常
*/
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
BackgroundBean bean = this;
// 原图配置序列化
jsonWriter.name("backgroundFileName").value(bean.getBackgroundFileName());
jsonWriter.name("backgroundFilePath").value(bean.getBackgroundFilePath()); // 新增字段:背景原图完整路径
jsonWriter.name("backgroundFilePath").value(bean.getBackgroundFilePath());
jsonWriter.name("backgroundFileInfo").value(bean.getBackgroundFileInfo());
// 控制字段序列化
jsonWriter.name("isUseBackgroundFile").value(bean.isUseBackgroundFile());
// 压缩图配置序列化
jsonWriter.name("backgroundScaledCompressFileName").value(bean.getBackgroundScaledCompressFileName());
jsonWriter.name("backgroundScaledCompressFilePath").value(bean.getBackgroundScaledCompressFilePath());
// 关键:新字段序列化(核心)
jsonWriter.name("isUseBackgroundScaledCompressFile").value(bean.isUseBackgroundScaledCompressFile());
// 兼容旧版本:保留旧字段名写入(可选,避免旧版本Bean读取时缺失字段
jsonWriter.name("isUseScaledCompress").value(bean.isUseBackgroundScaledCompressFile());
// 兼容旧版本保留旧字段名写入避免旧版本Bean读取时缺失字段
jsonWriter.name(OLD_FIELD_USE_SCALED_COMPRESS).value(bean.isUseBackgroundScaledCompressFile());
// 裁剪配置与像素颜色序列化
jsonWriter.name("backgroundWidth").value(bean.getBackgroundWidth());
jsonWriter.name("backgroundHeight").value(bean.getBackgroundHeight());
jsonWriter.name("pixelColor").value(bean.getPixelColor());
LogUtils.d(TAG, "writeThisToJsonWriter: JSON 序列化完成,已兼容旧字段");
}
/**
@@ -172,7 +202,7 @@ public class BackgroundBean extends BaseBean implements Serializable {
bean.setBackgroundFileName(jsonReader.nextString());
break;
case "backgroundFilePath":
bean.setBackgroundFilePath(jsonReader.nextString()); // 新增字段:读取背景原图完整路径
bean.setBackgroundFilePath(jsonReader.nextString());
break;
case "backgroundFileInfo":
bean.setBackgroundFileInfo(jsonReader.nextString());
@@ -186,13 +216,15 @@ public class BackgroundBean extends BaseBean implements Serializable {
case "backgroundScaledCompressFilePath":
bean.setBackgroundScaledCompressFilePath(jsonReader.nextString());
break;
// 关键:读取新字段(优先)
case "isUseBackgroundScaledCompressFile":
// 关键:读取新字段(优先)
bean.setIsUseBackgroundScaledCompressFile(jsonReader.nextBoolean());
LogUtils.d(TAG, "readBeanFromJsonReader: 读取新字段 isUseBackgroundScaledCompressFile 完成");
break;
case OLD_FIELD_USE_SCALED_COMPRESS:
// 兼容旧版本:读取旧字段(若新字段未读取,则用旧字段值)
case "isUseScaledCompress":
tempUseScaledCompress = jsonReader.nextBoolean();
LogUtils.d(TAG, "readBeanFromJsonReader: 读取旧字段 isUseScaledCompress 完成");
break;
case "backgroundWidth":
bean.setBackgroundWidth(jsonReader.nextInt());
@@ -204,32 +236,36 @@ public class BackgroundBean extends BaseBean implements Serializable {
bean.setPixelColor(jsonReader.nextInt());
break;
default:
jsonReader.skipValue(); // 跳过未知字段兼容旧版本Bean避免崩溃
jsonReader.skipValue();
LogUtils.w(TAG, String.format("readBeanFromJsonReader: 跳过未知字段 %s", name));
break;
}
}
jsonReader.endObject();
// 兼容逻辑若新字段未被赋值旧版本JSON无此字段则用旧字段值填充
if (!jsonReader.toString().contains("isUseBackgroundScaledCompressFile")) {
if (!bean.isUseBackgroundScaledCompressFile()) {
bean.setIsUseBackgroundScaledCompressFile(tempUseScaledCompress);
LogUtils.d(TAG, "readBeanFromJsonReader: 旧字段值已填充到新字段");
}
LogUtils.d(TAG, "readBeanFromJsonReader: JSON 反序列化完成");
return bean;
}
// ====================================== 辅助方法(同步更新重命名字段)======================================
// ====================== 辅助方法(重置配置、配置校验,补充调试日志) ======================
/**
* 重置背景配置(适配“取消背景”功能,同步重置重命名字段)
*/
public void resetBackgroundConfig() {
this.backgroundFileName = "";
this.backgroundFilePath = ""; // 新增:重置背景原图完整路径
this.backgroundFilePath = "";
this.backgroundScaledCompressFileName = "";
this.backgroundScaledCompressFilePath = "";
this.backgroundFileInfo = "";
this.isUseBackgroundFile = false;
this.isUseBackgroundScaledCompressFile = false; // 重命名字段重置为false
this.backgroundWidth = 100;
this.backgroundHeight = 100;
this.isUseBackgroundScaledCompressFile = false;
this.backgroundWidth = DEFAULT_DIMENSION;
this.backgroundHeight = DEFAULT_DIMENSION;
LogUtils.d(TAG, "resetBackgroundConfig: 背景配置已重置为默认值");
}
/**
@@ -240,16 +276,21 @@ public class BackgroundBean extends BaseBean implements Serializable {
public boolean isBackgroundConfigValid() {
// 启用背景图时,需确保:原图路径/文件名 或 压缩图路径/文件名 非空
if (!isUseBackgroundFile) {
LogUtils.d(TAG, "isBackgroundConfigValid: 未启用背景图,配置无效");
return false;
}
// 原图校验:路径非空 或 文件名非空
boolean isOriginalValid = !backgroundFilePath.isEmpty() || !backgroundFileName.isEmpty();
// 压缩图校验:启用压缩图时,路径/文件名需非空
boolean isCompressValid = true;
if (isUseBackgroundScaledCompressFile()) { // 重命名字段:判断是否启用压缩图
if (isUseBackgroundScaledCompressFile()) {
isCompressValid = !backgroundScaledCompressFilePath.isEmpty() || !backgroundScaledCompressFileName.isEmpty();
}
// 逻辑:启用压缩图则需压缩图有效;不启用压缩图则需原图有效
return isUseBackgroundScaledCompressFile() ? isCompressValid : isOriginalValid;
boolean isValid = isUseBackgroundScaledCompressFile() ? isCompressValid : isOriginalValid;
LogUtils.d(TAG, String.format("isBackgroundConfigValid: 背景配置有效性为 %b启用压缩图%b原图有效%b压缩图有效%b",
isValid, isUseBackgroundScaledCompressFile(), isOriginalValid, isCompressValid));
return isValid;
}
}

View File

@@ -1,26 +1,83 @@
package cc.winboll.studio.powerbell.models;
import cc.winboll.studio.libappbase.LogUtils;
/**
* 电池报告数据模型
* 适配 API30存储当前电量、放电时间、充电时间核心数据
* 支持参数校验与调试日志输出
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/22 14:30:51
* @Describe 电池报告数据模型
*/
public class BatteryData {
// ====================== 静态常量(首屏可见,统一管理) ======================
public static final String TAG = "BatteryData";
// 字段校验常量(避免硬编码,统一管理)
private static final int BATTERY_MIN = 0;
private static final int BATTERY_MAX = 100;
private static final String EMPTY_TIME = "00:00:00";
private int currentLevel;
private String dischargeTime;
private String chargeTime;
// ====================== 成员变量(按功能分类:电量→时间) ======================
private int currentLevel; // 当前电池电量0-100
private String dischargeTime; // 放电时间
private String chargeTime; // 充电时间
// ====================== 构造方法(按参数重载排序,补充校验与日志) ======================
/**
* 无参构造器(适配 JSON 反序列化、反射实例化场景)
*/
public BatteryData() {
this.currentLevel = BATTERY_MIN;
this.dischargeTime = EMPTY_TIME;
this.chargeTime = EMPTY_TIME;
LogUtils.d(TAG, "BatteryData: 无参构造初始化完成,默认值已设置");
}
/**
* 带参构造器(核心构造,初始化所有字段)
* @param currentLevel 当前电量0-100
* @param dischargeTime 放电时间
* @param chargeTime 充电时间
*/
public BatteryData(int currentLevel, String dischargeTime, String chargeTime) {
this.currentLevel = currentLevel;
this.dischargeTime = dischargeTime;
this.chargeTime = chargeTime;
// 电量范围校验0-100异常值置为0
this.currentLevel = currentLevel >= BATTERY_MIN && currentLevel <= BATTERY_MAX
? currentLevel : BATTERY_MIN;
// 时间字段防 null空值置为默认空时间
this.dischargeTime = dischargeTime == null ? EMPTY_TIME : dischargeTime;
this.chargeTime = chargeTime == null ? EMPTY_TIME : chargeTime;
// 调试日志:输出入参与最终赋值结果
LogUtils.d(TAG, String.format("BatteryData: 带参构造初始化完成 | 当前电量:%d输入%d| 放电时间:%s输入%s| 充电时间:%s输入%s",
this.currentLevel, currentLevel,
this.dischargeTime, dischargeTime,
this.chargeTime, chargeTime));
}
public int getCurrentLevel() { return currentLevel; }
public String getDischargeTime() { return dischargeTime; }
public String getChargeTime() { return chargeTime; }
// ====================== Getter 方法(按成员变量顺序排列,补充日志可选) ======================
/**
* 获取当前电池电量
* @return 当前电量0-100
*/
public int getCurrentLevel() {
return currentLevel;
}
/**
* 获取放电时间
* @return 放电时间
*/
public String getDischargeTime() {
return dischargeTime;
}
/**
* 获取充电时间
* @return 充电时间
*/
public String getChargeTime() {
return chargeTime;
}
}

View File

@@ -3,47 +3,89 @@ package cc.winboll.studio.powerbell.models;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.IOException;
import java.io.Serializable;
/**
* 电池信息数据模型
* 适配 API30存储电量时间戳与电量值支持 JSON 序列化/反序列化
* 修复字段拼写错误,补充数据校验与调试日志
* @Author ZhanGSKen<zhangsken@qq.com>
* @Describe 电池信息数据模型
*/
public class BatteryInfoBean extends BaseBean implements Serializable {
// ====================== 静态常量(首屏可见,统一管理) ======================
public static final String TAG = "BatteryInfoBean";
// 字段校验常量(避免硬编码,统一管理)
private static final int BATTERY_MIN = 0;
private static final int BATTERY_MAX = 100;
private static final long DEFAULT_TIMESTAMP = 0L;
private static final int DEFAULT_BATTERY_VALUE = 0;
// 记录电量的时间戳
long timeStamp;
// 电量值
int battetyValue;
// ====================== 成员变量修复拼写错误battetyValue → batteryValue ======================
private long timeStamp; // 记录电量的时间戳
private int batteryValue; // 电量值0-100
// ====================== 构造方法(按参数重载排序,补充校验与日志) ======================
/**
* 无参构造器JSON 反序列化、反射实例化必备)
*/
public BatteryInfoBean() {
this.timeStamp = 0;
this.battetyValue = 0;
this.timeStamp = DEFAULT_TIMESTAMP;
this.batteryValue = DEFAULT_BATTERY_VALUE;
LogUtils.d(TAG, "BatteryInfoBean: 无参构造初始化完成,默认时间戳:" + timeStamp + ",默认电量:" + batteryValue);
}
public BatteryInfoBean(long timeStamp, int battetyValue) {
/**
* 带参构造器(核心构造,初始化所有字段)
* @param timeStamp 电量记录时间戳
* @param batteryValue 电量值0-100
*/
public BatteryInfoBean(long timeStamp, int batteryValue) {
this.timeStamp = timeStamp;
this.battetyValue = battetyValue;
// 电量范围校验0-100异常值置为默认值
this.batteryValue = batteryValue >= BATTERY_MIN && batteryValue <= BATTERY_MAX
? batteryValue : DEFAULT_BATTERY_VALUE;
LogUtils.d(TAG, String.format("BatteryInfoBean: 带参构造初始化完成 | 时间戳:%d | 电量:%d输入%d",
this.timeStamp, this.batteryValue, batteryValue));
}
// ====================== Setter/Getter 方法(按成员变量顺序排列,修复拼写错误,补充日志) ======================
/**
* 设置电量记录时间戳
* @param timeStamp 时间戳
*/
public void setTimeStamp(long timeStamp) {
this.timeStamp = timeStamp;
LogUtils.d(TAG, "setTimeStamp: 时间戳设置为 " + timeStamp);
}
public long getTimeStamp() {
return timeStamp;
}
public void setBattetyValue(int battetyValue) {
this.battetyValue = battetyValue;
/**
* 设置电量值(修复拼写错误:battetyValuebatteryValue
* @param batteryValue 电量值0-100
*/
public void setBatteryValue(int batteryValue) {
this.batteryValue = batteryValue >= BATTERY_MIN && batteryValue <= BATTERY_MAX
? batteryValue : DEFAULT_BATTERY_VALUE;
LogUtils.d(TAG, String.format("setBatteryValue: 电量设置为 %d输入%d",
this.batteryValue, batteryValue));
}
public int getBattetyValue() {
return battetyValue;
public int getBatteryValue() {
return batteryValue;
}
// ====================== JSON 序列化/反序列化方法(修复字段拼写错误,补充调试日志) ======================
@Override
public String getName() {
return BatteryInfoBean.class.getName();
String className = BatteryInfoBean.class.getName();
LogUtils.d(TAG, "getName: 类名标识为 " + className);
return className;
}
@Override
@@ -51,7 +93,9 @@ public class BatteryInfoBean extends BaseBean implements Serializable {
super.writeThisToJsonWriter(jsonWriter);
BatteryInfoBean bean = this;
jsonWriter.name("timeStamp").value(bean.getTimeStamp());
jsonWriter.name("battetyValue").value(bean.getBattetyValue());
// 修复 JSON 字段名拼写错误battetyValue → batteryValue
jsonWriter.name("batteryValue").value(bean.getBatteryValue());
LogUtils.d(TAG, "writeThisToJsonWriter: JSON 序列化完成 | 时间戳:" + bean.getTimeStamp() + ",电量:" + bean.getBatteryValue());
}
@Override
@@ -60,16 +104,28 @@ public class BatteryInfoBean extends BaseBean implements Serializable {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (name.equals("timeStamp")) {
switch (name) {
case "timeStamp":
bean.setTimeStamp(jsonReader.nextLong());
} else if (name.equals("battetyValue")) {
bean.setBattetyValue(jsonReader.nextInt());
} else {
break;
case "batteryValue":
bean.setBatteryValue(jsonReader.nextInt());
break;
// 兼容旧字段名battetyValue避免旧配置解析失败
case "battetyValue":
int oldBatteryValue = jsonReader.nextInt();
bean.setBatteryValue(oldBatteryValue);
LogUtils.w(TAG, "readBeanFromJsonReader: 读取旧字段 battetyValue已兼容为 batteryValue" + oldBatteryValue);
break;
default:
jsonReader.skipValue();
LogUtils.w(TAG, "readBeanFromJsonReader: 跳过未知字段 " + name);
break;
}
}
// 结束 JSON 对象
jsonReader.endObject();
LogUtils.d(TAG, "readBeanFromJsonReader: JSON 反序列化完成 | 时间戳:" + bean.getTimeStamp() + ",电量:" + bean.getBatteryValue());
return bean;
}
}

View File

@@ -64,7 +64,7 @@ public class AppCacheUtils {
return;
}
// 对比最后一条记录的电量值,避免重复添加
int lastBatteryValue = mBatteryInfoList.get(mBatteryInfoList.size() - 1).getBattetyValue();
int lastBatteryValue = mBatteryInfoList.get(mBatteryInfoList.size() - 1).getBatteryValue();
if (lastBatteryValue != batteryValue) {
addChangingTimeToList(batteryValue);
LogUtils.d(TAG, "addChangingTime():电量变化,添加新记录(原电量:" + lastBatteryValue + ",新电量:" + batteryValue + "");
@@ -107,7 +107,7 @@ public class AppCacheUtils {
}
BatteryInfoBean batteryInfo = new BatteryInfoBean(System.currentTimeMillis(), batteryValue);
mBatteryInfoList.add(batteryInfo);
LogUtils.d(TAG, "addChangingTimeToList():添加新记录 - 电量:" + batteryInfo.getBattetyValue() + ",时间戳:" + batteryInfo.getTimeStamp());
LogUtils.d(TAG, "addChangingTimeToList():添加新记录 - 电量:" + batteryInfo.getBatteryValue() + ",时间戳:" + batteryInfo.getTimeStamp());
saveAppCacheData();
}

View File

@@ -53,7 +53,7 @@ public class StringUtils {
continue;
}
// 获取电量和时间跨度
int batteryValue = currentBean.getBattetyValue();
int batteryValue = currentBean.getBatteryValue();
String timeSpan = getTimespanDifference(currentBean.getTimeStamp(), nextBean.getTimeStamp());
// 倒序拼接
result = batteryValue + "% " + timeSpan + " " + result;
@@ -88,7 +88,7 @@ public class StringUtils {
continue;
}
// 获取电量和时间跨度
int batteryValue = currentBean.getBattetyValue();
int batteryValue = currentBean.getBatteryValue();
String timeSpan = getTimespanDifference(currentBean.getTimeStamp(), nextBean.getTimeStamp());
// 倒序拼接(带换行)
result = "\n" + batteryValue + "%\n " + timeSpan + " " + result;