源码整理
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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,160 +18,191 @@ 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 = "已记录像素值";
|
||||
|
||||
public static final String TAG = "PixelPickerActivity";
|
||||
// ======================== 成员变量 =========================
|
||||
// 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;
|
||||
}
|
||||
// ======================== 接口实现方法 =========================
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
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);
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
// ======================== 生命周期方法 =========================
|
||||
@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) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
imageView = findViewById(R.id.imageView);
|
||||
infoText = findViewById(R.id.infoText);
|
||||
imageContainer = findViewById(R.id.imageContainer);
|
||||
|
||||
// 从Intent获取图片路径并加载
|
||||
String imagePath = getIntent().getStringExtra("imagePath");
|
||||
if (imagePath != null) {
|
||||
loadImage(imagePath);
|
||||
} else {
|
||||
infoText.setText("未找到图片路径");
|
||||
}
|
||||
|
||||
// 设置图片点击事件
|
||||
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;
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【导航栏】点击返回");
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
LogUtils.d(TAG, "【initToolbar】工具栏初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载图片
|
||||
*/
|
||||
private void loadImage(String imagePath) {
|
||||
try {
|
||||
File file = new File(imagePath);
|
||||
if (file.exists()) {
|
||||
// 解码图片
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inSampleSize = 1; // 加载原图
|
||||
originalBitmap = BitmapFactory.decodeStream(new FileInputStream(file), null, options);
|
||||
// ======================== 业务逻辑方法 =========================
|
||||
/**
|
||||
* 从Intent中获取图片路径并加载图片
|
||||
*/
|
||||
private void loadImageFromIntent() {
|
||||
String imagePath = getIntent().getStringExtra(EXTRA_IMAGE_PATH);
|
||||
LogUtils.d(TAG, "【loadImageFromIntent】获取到图片路径:" + imagePath);
|
||||
|
||||
if (originalBitmap != null) {
|
||||
imageView.setImageBitmap(originalBitmap);
|
||||
infoText.setText("图片已加载,点击获取像素值");
|
||||
} else {
|
||||
infoText.setText("图片加载失败");
|
||||
}
|
||||
} else {
|
||||
infoText.setText("图片文件不存在");
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
infoText.setText("图片文件未找到");
|
||||
}
|
||||
}
|
||||
if (imagePath != null) {
|
||||
loadImage(imagePath);
|
||||
} else {
|
||||
infoText.setText(MSG_NO_IMAGE_PATH);
|
||||
LogUtils.w(TAG, "【loadImageFromIntent】未获取到图片路径");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示像素对话框
|
||||
*/
|
||||
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);
|
||||
/**
|
||||
* 加载指定路径的图片
|
||||
* @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;
|
||||
originalBitmap = BitmapFactory.decodeStream(new FileInputStream(file), null, options);
|
||||
|
||||
// 设置像素颜色视图背景
|
||||
TextView colorView = dialog.findViewById(R.id.pixelColorView);
|
||||
colorView.setBackgroundColor(pixelColor);
|
||||
if (originalBitmap != null) {
|
||||
imageView.setImageBitmap(originalBitmap);
|
||||
infoText.setText(MSG_IMAGE_LOADED);
|
||||
LogUtils.d(TAG, "【loadImage】图片加载成功,尺寸:" + originalBitmap.getWidth() + "x" + originalBitmap.getHeight());
|
||||
} else {
|
||||
infoText.setText(MSG_IMAGE_LOAD_FAILED);
|
||||
LogUtils.e(TAG, "【loadImage】图片解码失败");
|
||||
}
|
||||
} else {
|
||||
infoText.setText(MSG_FILE_NOT_EXIST);
|
||||
LogUtils.w(TAG, "【loadImage】图片文件不存在:" + imagePath);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
infoText.setText(MSG_FILE_NOT_FOUND);
|
||||
LogUtils.e(TAG, "【loadImage】图片文件未找到:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 显示颜色信息
|
||||
TextView infoText = dialog.findViewById(R.id.colorInfoText);
|
||||
String colorInfo = String.format(
|
||||
/**
|
||||
* 显示像素颜色信息对话框
|
||||
* @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);
|
||||
|
||||
String colorInfo = String.format(
|
||||
"RGB: (%d, %d, %d)\n" +
|
||||
"ARGB: #%08X\n" +
|
||||
"实际像素位置: (%d, %d)",
|
||||
@@ -184,77 +211,129 @@ 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() {
|
||||
// 确定按钮点击事件
|
||||
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();
|
||||
}
|
||||
dialog.show();
|
||||
LogUtils.d(TAG, "【showPixelDialog】像素对话框已显示");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
// 回收Bitmap资源
|
||||
if (originalBitmap != null && !originalBitmap.isRecycled()) {
|
||||
originalBitmap.recycle();
|
||||
originalBitmap = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 保存拾取的像素颜色到背景配置
|
||||
* @param pixelColor 拾取的像素颜色(ARGB)
|
||||
*/
|
||||
private void savePixelColor(int pixelColor) {
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
|
||||
BackgroundBean bean = utils.getPreviewBackgroundBean();
|
||||
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));
|
||||
}
|
||||
|
||||
void setBackgroundColor() {
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(PixelPickerActivity.this);
|
||||
BackgroundBean bean = utils.getPreviewBackgroundBean();
|
||||
int nPixelColor = bean.getPixelColor();
|
||||
RelativeLayout mainLayout = findViewById(R.id.activitypixelpickerRelativeLayout1);
|
||||
mainLayout.setBackgroundColor(nPixelColor);
|
||||
}
|
||||
// ======================== 事件回调方法 =========================
|
||||
/**
|
||||
* 绑定图片容器的触摸事件,处理像素拾取逻辑
|
||||
*/
|
||||
private void bindImageTouchListener() {
|
||||
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();
|
||||
LogUtils.v(TAG, "【onTouch】触摸坐标:(" + touchX + ", " + touchY + ")");
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
setBackgroundColor();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
// Intent intent = new Intent();
|
||||
// intent.setClass(this, BackgroundSettingsActivity.class);
|
||||
// startActivity(intent);
|
||||
//GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), BackgroundPictureActivity.class);
|
||||
}
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
LogUtils.d(TAG, "【onBackPressed】返回键触发,页面关闭");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,37 +9,58 @@ 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;
|
||||
}
|
||||
// ======================== 接口实现方法 =========================
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
// ======================== 生命周期方法 =========================
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_settings);
|
||||
LogUtils.d(TAG, "【onCreate】SettingsActivity 初始化开始");
|
||||
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
// 初始化工具栏
|
||||
initToolbar();
|
||||
|
||||
LogUtils.d(TAG, "【onCreate】SettingsActivity 初始化完成");
|
||||
}
|
||||
|
||||
// ======================== UI初始化方法 =========================
|
||||
/**
|
||||
* 初始化顶部工具栏,设置导航返回与样式
|
||||
*/
|
||||
private void initToolbar() {
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(mToolbar);
|
||||
mToolbar.setSubtitle(getTag());
|
||||
// 设置工具栏副标题与标题样式
|
||||
mToolbar.setSubtitle(getTag());
|
||||
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
// 显示返回按钮
|
||||
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】工具栏初始化完成");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
// 处理应用级别的切换请求
|
||||
// ======================== 生命周期方法 =========================
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
LogUtils.d(TAG, "【onCreate】ShortcutActionActivity 启动,开始处理快捷方式请求");
|
||||
|
||||
// 处理应用图标快捷菜单的切换请求
|
||||
handleSwitchRequest();
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理应用图标快捷菜单的请求
|
||||
LogUtils.d(TAG, "【onCreate】快捷方式请求处理完成,关闭活动");
|
||||
finish();
|
||||
}
|
||||
|
||||
// ======================== 业务逻辑方法 =========================
|
||||
/**
|
||||
* 处理应用图标快捷菜单的请求,根据意图数据切换应用启动组件
|
||||
*/
|
||||
private void handleSwitchRequest() {
|
||||
Intent intent = getIntent();
|
||||
if (intent != null && "switchto_en1".equals(intent.getDataString())) {
|
||||
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_EN1);
|
||||
ToastUtils.show("切换至" + getString(R.string.app_name) + "图标");
|
||||
//moveTaskToBack(true);
|
||||
if (intent == null) {
|
||||
LogUtils.w(TAG, "【handleSwitchRequest】意图为空,无法处理快捷方式请求");
|
||||
return;
|
||||
}
|
||||
if (intent != null && "switchto_cn1".equals(intent.getDataString())) {
|
||||
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_CN1);
|
||||
ToastUtils.show("切换至" + getString(R.string.app_name_cn1) + "图标");
|
||||
//moveTaskToBack(true);
|
||||
}
|
||||
if (intent != null && "switchto_cn2".equals(intent.getDataString())) {
|
||||
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_CN2);
|
||||
ToastUtils.show("切换至" + getString(R.string.app_name_cn2) + "图标");
|
||||
//moveTaskToBack(true);
|
||||
|
||||
String dataString = intent.getDataString();
|
||||
LogUtils.d(TAG, "【handleSwitchRequest】获取到快捷指令:" + dataString);
|
||||
|
||||
// 匹配快捷指令并切换组件
|
||||
if (ACTION_SWITCH_TO_EN1.equals(dataString)) {
|
||||
APPPlusUtils.switchAppLauncherToComponent(this, App.COMPONENT_EN1);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
frameLayout.addView(mTagView, params);
|
||||
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】未找到工具栏控件(ID:toolbar)", 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);
|
||||
}
|
||||
|
||||
public void changeFullScreen(Activity activity) {
|
||||
Window window = activity.getWindow();
|
||||
if (window == null){
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
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){
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
.inflate(R.layout.item_battery_report, parent, false);
|
||||
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】控件初始化完成");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,9 +71,9 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
|
||||
// 并发控制标记:是否是应用程序自身在更新颜色(避免循环回调/重复触发)
|
||||
private static volatile boolean isAppSelfUpdatingColor = false;
|
||||
|
||||
// 控件引用(新增透明度进度条+文本)
|
||||
// 控件引用
|
||||
private ImageView ivColorPicker; // 颜色预览拾取框
|
||||
private ImageView ivColorScaler; // 颜色渐变拾取框
|
||||
private ImageView ivColorScaler; // 颜色渐变拾取框
|
||||
private EditText etR; // R分量输入框(显示实时调节值)
|
||||
private EditText etG; // G分量输入框(显示实时调节值)
|
||||
private EditText etB; // B分量输入框(显示实时调节值)
|
||||
@@ -93,12 +98,11 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
|
||||
}
|
||||
|
||||
// 2. 解析初始颜色:原始基准值 = 实时值(初始无调节)
|
||||
// 透明度:初始颜色的alpha(0-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,11 +147,11 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
|
||||
|
||||
// ====================== 初始化核心方法(职责单一,便于维护) ======================
|
||||
/**
|
||||
* 控件绑定(新增透明度进度条+文本绑定)
|
||||
* 控件绑定
|
||||
*/
|
||||
private void initViewBind(View view) {
|
||||
ivColorPicker = view.findViewById(R.id.iv_color_picker);
|
||||
ivColorScaler = view.findViewById(R.id.iv_color_scaler);
|
||||
ivColorScaler = view.findViewById(R.id.iv_color_scaler);
|
||||
etR = view.findViewById(R.id.et_r);
|
||||
etG = view.findViewById(R.id.et_g);
|
||||
etB = view.findViewById(R.id.et_b);
|
||||
@@ -161,9 +166,9 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
|
||||
|
||||
// 控件非空校验(小米低版本容错,绑定失败直接关闭对话框)
|
||||
if (ivColorPicker == null || ivColorScaler == null || etR == null || etG == null || etB == null || etColorValue == null
|
||||
|| sbAlpha == null || tvAlphaValue == null
|
||||
|| tvBrightnessMinus == null || tvBrightnessValue == null || tvBrightnessPlus == null
|
||||
|| tvConfirm == null || tvCancel == null) {
|
||||
|| sbAlpha == null || tvAlphaValue == null
|
||||
|| tvBrightnessMinus == null || tvBrightnessValue == null || tvBrightnessPlus == null
|
||||
|| tvConfirm == null || tvCancel == null) {
|
||||
LogUtils.e(TAG, "view bind failed | 请检查布局ID是否正确!");
|
||||
dismiss();
|
||||
return;
|
||||
@@ -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,10 +231,10 @@ 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);
|
||||
| WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
|
||||
LogUtils.d(TAG, "dialog size adjust complete | 适配全面屏+软键盘");
|
||||
}
|
||||
}
|
||||
@@ -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.1,100%→1.0,200%→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);
|
||||
|
||||
// 更新原始基准值(用户输入颜色,重置基准)
|
||||
// 透明度:解析颜色的alpha(0-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,90 +535,77 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示系统颜色选择器(兼容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("选择基础颜色");
|
||||
/**
|
||||
* 显示系统颜色选择器(兼容API29-30,无高版本依赖,小米机型适配)
|
||||
*/
|
||||
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
|
||||
0xFFCC0000, 0xFFFF0000, 0xFFFF6666, 0xFFFF1493, 0xFF8B0000, 0xFFFF4500,
|
||||
0xFFCC6600, 0xFFFF8800, 0xFFFFAA33, 0xFFFFBB00, 0xFFF5A623,
|
||||
0xFFCCCC00, 0xFFFFFF00, 0xFFFFEE99, 0xFFFFFACD, 0xFFFFD700,
|
||||
0xFF006600, 0xFF00FF00, 0xFF99FF99, 0xFF66CC66, 0xFF98FB98, 0xFF00FF99, 0xFF003300,
|
||||
0xFF006666, 0xFF00FFFF, 0xFF99FFFF, 0xFF00CCCC, 0xFF40E0D0,
|
||||
0xFF0000CC, 0xFF00008B, 0xFF0000FF, 0xFF6666FF, 0xFF87CEEB, 0xFF0066FF, 0xFF0099FF, 0xFF4B0082,
|
||||
0xFF660099, 0xFF8800FF, 0xFFAA99FF, 0xFF9370DB, 0xFFCBC3E3, 0xFF8A2BE2,
|
||||
0xFFFF00FF, 0xFFFF99CC, 0xFFFFCCDD, 0xFFFFB6C1, 0xFFFFA5A5,
|
||||
0xFF8B4513, 0xFFA0522D, 0xFFD2B48C, 0xFFCD853F,
|
||||
0xFF333333, 0xFF666666, 0xFF888888, 0xFFAAAAAA, 0xFFCCCCCC, 0xFFE6E6E6,
|
||||
0xFF000000, 0xFFFFFFFF, 0xFFFFFAFA
|
||||
};
|
||||
|
||||
// 1. 第一级:水平滚动容器
|
||||
HorizontalScrollView horizontalScrollView = new HorizontalScrollView(getContext());
|
||||
horizontalScrollView.setHorizontalScrollBarEnabled(true);
|
||||
horizontalScrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
|
||||
horizontalScrollView.setPadding(dp2px(5), dp2px(5), dp2px(5), dp2px(5));
|
||||
// 1. 第一级:水平滚动容器
|
||||
HorizontalScrollView horizontalScrollView = new HorizontalScrollView(getContext());
|
||||
horizontalScrollView.setHorizontalScrollBarEnabled(true);
|
||||
horizontalScrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
|
||||
horizontalScrollView.setPadding(dp2px(5), dp2px(5), dp2px(5), dp2px(5));
|
||||
|
||||
// 2. 第二级:颜色排列容器(横向)
|
||||
LinearLayout colorLayout = new LinearLayout(getContext());
|
||||
colorLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
colorLayout.setGravity(Gravity.CENTER_VERTICAL);
|
||||
colorLayout.setPadding(dp2px(10), dp2px(10), dp2px(10), dp2px(10));
|
||||
// 2. 第二级:颜色排列容器(横向)
|
||||
LinearLayout colorLayout = new LinearLayout(getContext());
|
||||
colorLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
colorLayout.setGravity(Gravity.CENTER_VERTICAL);
|
||||
colorLayout.setPadding(dp2px(10), dp2px(10), dp2px(10), dp2px(10));
|
||||
|
||||
// 3. 循环添加颜色按钮(内置圆形效果,无额外依赖)
|
||||
for (int i = 0; i < systemColors.length; i++) {
|
||||
final int color = systemColors[i];
|
||||
ImageView colorBtn = new ImageView(getContext());
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(dp2px(40), dp2px(40));
|
||||
if (i != systemColors.length - 1) {
|
||||
lp.setMargins(0, 0, dp2px(10), 0); // 按钮间距
|
||||
}
|
||||
colorBtn.setLayoutParams(lp);
|
||||
// 3. 循环添加颜色按钮(内置圆形效果)
|
||||
for (int i = 0; i < systemColors.length; i++) {
|
||||
final int color = systemColors[i];
|
||||
ImageView colorBtn = new ImageView(getContext());
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(dp2px(40), dp2px(40));
|
||||
if (i != systemColors.length - 1) {
|
||||
lp.setMargins(0, 0, dp2px(10), 0); // 按钮间距
|
||||
}
|
||||
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); // 设置圆形背景
|
||||
// 内置圆形背景(白色边框+圆形形状)
|
||||
GradientDrawable circleBg = new GradientDrawable();
|
||||
circleBg.setShape(GradientDrawable.OVAL);
|
||||
circleBg.setColor(color);
|
||||
circleBg.setStroke(dp2px(2), Color.WHITE);
|
||||
colorBtn.setBackground(circleBg);
|
||||
|
||||
colorBtn.setClickable(true);
|
||||
colorBtn.setFocusable(true);
|
||||
colorBtn.setClickable(true);
|
||||
colorBtn.setFocusable(true);
|
||||
|
||||
// 点击事件(逻辑不变)
|
||||
colorBtn.setOnClickListener(new View.OnClickListener() {
|
||||
// 点击事件
|
||||
colorBtn.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (!isAppSelfUpdatingColor) {
|
||||
@@ -641,57 +625,59 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
colorLayout.addView(colorBtn);
|
||||
}
|
||||
colorLayout.addView(colorBtn);
|
||||
}
|
||||
|
||||
// 层级嵌套(滚动容器→颜色容器)
|
||||
horizontalScrollView.addView(colorLayout);
|
||||
builder.setView(horizontalScrollView).setNegativeButton("关闭", null).show();
|
||||
}
|
||||
// 层级嵌套
|
||||
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");
|
||||
final ColorScalerDialog dlg = new ColorScalerDialog(getContext(), nColor);
|
||||
dlg.setOnColorChangedListener(new com.a4455jkjh.colorpicker.view.OnColorChangedListener() {
|
||||
|
||||
/**
|
||||
* 打开颜色渐变选择器
|
||||
*/
|
||||
void openColorScalerDialog(int nColor) {
|
||||
LogUtils.d(TAG, String.format("openColorScalerDialog | 初始颜色:%s",
|
||||
String.format(FORMAT_COLOR_HEX, nColor)));
|
||||
final ColorScalerDialog dlg = new ColorScalerDialog(getContext(), nColor);
|
||||
dlg.setOnColorChangedListener(new OnColorChangedListener() {
|
||||
@Override
|
||||
public void beforeColorChanged() {
|
||||
}
|
||||
public void beforeColorChanged() {}
|
||||
|
||||
@Override
|
||||
public void onColorChanged(int color) {
|
||||
@@ -699,49 +685,49 @@ public class ColorPaletteDialog extends Dialog implements View.OnClickListener,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterColorChanged() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void afterColorChanged() {}
|
||||
});
|
||||
dlg.show();
|
||||
}
|
||||
dlg.show();
|
||||
}
|
||||
|
||||
// ====================== 内部类 ======================
|
||||
class ColorScalerDialog extends ColorPickerDialog {
|
||||
public int currentColorScalerDialogColor = 0;
|
||||
public ColorScalerDialog(Context context, int p) {
|
||||
super(context, p);
|
||||
}
|
||||
public int currentColorScalerDialogColor = 0;
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
super.dismiss();
|
||||
int color = currentColorScalerDialogColor;
|
||||
ToastUtils.show(String.format("dismiss color %d", color));
|
||||
if (!isAppSelfUpdatingColor) {
|
||||
isAppSelfUpdatingColor = true;
|
||||
try {
|
||||
mOriginalAlpha = Color.alpha(color);
|
||||
mOriginalAlphaPercent = alpha2Percent(mOriginalAlpha);
|
||||
mOriginalR = Color.red(color);
|
||||
mOriginalG = Color.green(color);
|
||||
mOriginalB = Color.blue(color);
|
||||
mCurrentAlpha = mOriginalAlpha;
|
||||
mCurrentAlphaPercent = mOriginalAlphaPercent;
|
||||
mCurrentR = mOriginalR;
|
||||
mCurrentG = mOriginalG;
|
||||
mCurrentB = mOriginalB;
|
||||
mCurrentBrightnessPercent = DEFAULT_BRIGHTNESS;
|
||||
mCurrentColor = color;
|
||||
updateAllViews();
|
||||
LogUtils.d(TAG, "select system color | 选择颜色:" + String.format("#%08X", color)
|
||||
+ " | 透明度:" + mCurrentAlphaPercent + "%");
|
||||
} finally {
|
||||
isAppSelfUpdatingColor = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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("选择颜色:%s", String.format(FORMAT_COLOR_HEX, color)));
|
||||
if (!isAppSelfUpdatingColor) {
|
||||
isAppSelfUpdatingColor = true;
|
||||
try {
|
||||
mOriginalAlpha = Color.alpha(color);
|
||||
mOriginalAlphaPercent = alpha2Percent(mOriginalAlpha);
|
||||
mOriginalR = Color.red(color);
|
||||
mOriginalG = Color.green(color);
|
||||
mOriginalB = Color.blue(color);
|
||||
mCurrentAlpha = mOriginalAlpha;
|
||||
mCurrentAlphaPercent = mOriginalAlphaPercent;
|
||||
mCurrentR = mOriginalR;
|
||||
mCurrentG = mOriginalG;
|
||||
mCurrentB = mOriginalB;
|
||||
mCurrentBrightnessPercent = DEFAULT_BRIGHTNESS;
|
||||
mCurrentColor = color;
|
||||
updateAllViews();
|
||||
LogUtils.d(TAG, String.format("select scaler color | 选择颜色:%s | 透明度:%s",
|
||||
String.format(FORMAT_COLOR_HEX, color),
|
||||
String.format(FORMAT_PERCENT, mCurrentAlphaPercent)));
|
||||
} finally {
|
||||
isAppSelfUpdatingColor = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 onCancel(); // 取消按钮点击
|
||||
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;
|
||||
mBackgroundView.loadImage(mDownloadSavedPath);
|
||||
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);
|
||||
// 加载初始图片
|
||||
mBackgroundView.setBackgroundResource(R.drawable.blank100x100);
|
||||
|
||||
// 控件非空校验
|
||||
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)) {
|
||||
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,90 +219,120 @@ 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);
|
||||
mBackgroundView.setBackgroundResource(R.drawable.ic_launcher);
|
||||
ToastUtils.show("图片文件不存在:" + previewFilePath);
|
||||
LogUtils.e(TAG, String.format("previewBackground: 图片文件不存在,路径:%s", previewFilePath));
|
||||
mBackgroundView.setBackgroundResource(R.drawable.ic_launcher);
|
||||
return;
|
||||
}
|
||||
|
||||
// 预览背景
|
||||
mPreviewFilePath = previewFilePath;
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(mContext);
|
||||
utils.saveFileToPreviewBean(new File(mPreviewFilePath), mPreviewFileUrl);
|
||||
mBackgroundView.loadByBackgroundBean(utils.getPreviewBackgroundBean());
|
||||
// 预览背景
|
||||
mPreviewFilePath = previewFilePath;
|
||||
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;
|
||||
LogUtils.d(TAG, "setOnDialogClickListener: 按钮点击回调已设置");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 "isUseScaledCompress":
|
||||
case OLD_FIELD_USE_SCALED_COMPRESS:
|
||||
// 兼容旧版本:读取旧字段(若新字段未读取,则用旧字段值)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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; // 充电时间
|
||||
|
||||
public BatteryData(int currentLevel, String dischargeTime, String chargeTime) {
|
||||
this.currentLevel = currentLevel;
|
||||
this.dischargeTime = dischargeTime;
|
||||
this.chargeTime = chargeTime;
|
||||
// ====================== 构造方法(按参数重载排序,补充校验与日志) ======================
|
||||
/**
|
||||
* 无参构造器(适配 JSON 反序列化、反射实例化场景)
|
||||
*/
|
||||
public BatteryData() {
|
||||
this.currentLevel = BATTERY_MIN;
|
||||
this.dischargeTime = EMPTY_TIME;
|
||||
this.chargeTime = EMPTY_TIME;
|
||||
LogUtils.d(TAG, "BatteryData: 无参构造初始化完成,默认值已设置");
|
||||
}
|
||||
|
||||
public int getCurrentLevel() { return currentLevel; }
|
||||
public String getDischargeTime() { return dischargeTime; }
|
||||
public String getChargeTime() { return chargeTime; }
|
||||
/**
|
||||
* 带参构造器(核心构造,初始化所有字段)
|
||||
* @param currentLevel 当前电量(0-100)
|
||||
* @param dischargeTime 放电时间
|
||||
* @param chargeTime 充电时间
|
||||
*/
|
||||
public BatteryData(int currentLevel, String dischargeTime, String 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));
|
||||
}
|
||||
|
||||
// ====================== Getter 方法(按成员变量顺序排列,补充日志可选) ======================
|
||||
/**
|
||||
* 获取当前电池电量
|
||||
* @return 当前电量(0-100)
|
||||
*/
|
||||
public int getCurrentLevel() {
|
||||
return currentLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取放电时间
|
||||
* @return 放电时间
|
||||
*/
|
||||
public String getDischargeTime() {
|
||||
return dischargeTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取充电时间
|
||||
* @return 充电时间
|
||||
*/
|
||||
public String getChargeTime() {
|
||||
return chargeTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
/**
|
||||
* 设置电量值(修复拼写错误:battetyValue → batteryValue)
|
||||
* @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")) {
|
||||
bean.setTimeStamp(jsonReader.nextLong());
|
||||
} else if (name.equals("battetyValue")) {
|
||||
bean.setBattetyValue(jsonReader.nextInt());
|
||||
} else {
|
||||
jsonReader.skipValue();
|
||||
switch (name) {
|
||||
case "timeStamp":
|
||||
bean.setTimeStamp(jsonReader.nextLong());
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user