diff --git a/apputils/README.md b/apputils/README.md new file mode 100644 index 00000000..0130d03e --- /dev/null +++ b/apputils/README.md @@ -0,0 +1,35 @@ +# APPUtils + +#### 介绍 +应用开发工具套件类 + +#### 软件架构 +适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。 +也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。 + + +#### Gradle 编译说明 +调试版编译命令 :gradle assembleBetaDebug +阶段版编译命令 :git pull && bash .winboll/bashPublishAPKAddTag.sh apputils +阶段版类库发布命令 :git pull &&bash .winboll/bashPublishLIBAddTag.sh libapputils + +#### 使用说明 + +#### 参与贡献 + +1. Fork 本仓库 +2. 新建 Feat_xxx 分支 +3. 提交代码 : ZhanGSKen(ZhanGSKen) +4. 新建 Pull Request + + +#### 特技 + +1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md +2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) +3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 +4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 +5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) +6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) + +#### 参考文档 diff --git a/apputils/build.gradle b/apputils/build.gradle index e0e7c882..5991d785 100644 --- a/apputils/build.gradle +++ b/apputils/build.gradle @@ -29,7 +29,7 @@ android { // versionName 更新后需要手动设置 // 项目模块目录的 build.gradle 文件的 stageCount=0 // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" - versionName "15.8" + versionName "15.10" if(true) { versionName = genVersionName("${versionName}") } diff --git a/apputils/build.properties b/apputils/build.properties index 2be9ccd8..2e6e211a 100644 --- a/apputils/build.properties +++ b/apputils/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Mon Sep 01 07:56:33 HKT 2025 -stageCount=7 +#Mon Sep 29 01:16:05 HKT 2025 +stageCount=3 libraryProject=libapputils -baseVersion=15.8 -publishVersion=15.8.6 +baseVersion=15.10 +publishVersion=15.10.2 buildCount=0 -baseBetaVersion=15.8.7 +baseBetaVersion=15.10.3 diff --git a/apputils/src/main/AndroidManifest.xml b/apputils/src/main/AndroidManifest.xml index 41176ff3..a7e0389f 100644 --- a/apputils/src/main/AndroidManifest.xml +++ b/apputils/src/main/AndroidManifest.xml @@ -2,14 +2,20 @@ - + + + + + android:supportsRtl="true" + android:resizeableActivity="true" + android:screenOrientation="unspecified" + android:requestLegacyExternalStorage="true"> + + - \ No newline at end of file + diff --git a/apputils/src/main/java/cc/winboll/studio/apputils/AssetsHtmlActivity.java b/apputils/src/main/java/cc/winboll/studio/apputils/AssetsHtmlActivity.java index 822af56f..2480eeab 100644 --- a/apputils/src/main/java/cc/winboll/studio/apputils/AssetsHtmlActivity.java +++ b/apputils/src/main/java/cc/winboll/studio/apputils/AssetsHtmlActivity.java @@ -15,12 +15,11 @@ import android.view.MenuItem; import android.widget.Toolbar; import cc.winboll.studio.apputils.R; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; import cc.winboll.studio.libapputils.views.SimpleWebView; import java.io.IOException; import java.io.InputStream; -public class AssetsHtmlActivity extends WinBoLLActivity implements IWinBoLLActivity { +public class AssetsHtmlActivity extends Activity { public static final String TAG = "AssetsHtmlActivity"; @@ -32,16 +31,6 @@ public class AssetsHtmlActivity extends WinBoLLActivity implements IWinBoLLActiv // Assets 文件夹里的 Html 文件的名称 String mszHtmlFileName; - - @Override - public Activity getActivity() { - return this; - } - - @Override - public String getTag() { - return TAG; - } @Override public boolean onCreateOptionsMenu(Menu menu) { diff --git a/apputils/src/main/java/cc/winboll/studio/apputils/MainActivity.java b/apputils/src/main/java/cc/winboll/studio/apputils/MainActivity.java index 20eb17e4..3f89366c 100644 --- a/apputils/src/main/java/cc/winboll/studio/apputils/MainActivity.java +++ b/apputils/src/main/java/cc/winboll/studio/apputils/MainActivity.java @@ -15,9 +15,10 @@ import android.widget.Toolbar; import cc.winboll.studio.apputils.R; import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.LogView; -import cc.winboll.studio.libappbase.utils.ToastUtils; +import cc.winboll.studio.libappbase.ToastUtils; import java.util.List; import java.util.Set; +import cc.winboll.studio.libappbase.LogActivity; final public class MainActivity extends Activity { @@ -26,21 +27,21 @@ final public class MainActivity extends Activity { public static final int REQUEST_QRCODEDECODE_ACTIVITY = 0; Toolbar mToolbar; - LogView mLogView; + //LogView mLogView; // // @Override // public Activity getActivity() { // return this; // } - + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - mLogView = findViewById(R.id.logview); - mLogView.start(); +// mLogView = findViewById(R.id.logview); +// mLogView.start(); // 初始化工具栏 mToolbar = findViewById(R.id.toolbar); @@ -145,13 +146,21 @@ final public class MainActivity extends Activity { } public void onTestLogActivity(View view) { -// Intent intent = new Intent(this, LogActivity.class); -// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); -// intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); -// startActivity(intent); + /* 分屏代码有效 + // 1. 创建启动 SecondActivity 的 Intent + Intent splitIntent = new Intent(MainActivity.this, LogActivity.class); - //WinBoLLActivityManager.getInstance().printAvtivityListInfo(); - //WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, LogActivity.class); + // 2. 添加分屏启动必需的两个标志(API 30 兼容) + // FLAG_ACTIVITY_LAUNCH_ADJACENT:相邻分屏显示 + // FLAG_ACTIVITY_NEW_TASK:分屏需要新任务栈 + splitIntent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT + | Intent.FLAG_ACTIVITY_NEW_TASK); + + // 3. 启动分屏活动(若设备不支持分屏,会默认全屏启动) + startActivity(splitIntent); + */ + + LogActivity.startLogActivity(this); } // @@ -217,10 +226,9 @@ final public class MainActivity extends Activity { if (item.getItemId() == R.id.item_exit) { //exit(); return true; -// } else if (item.getItemId() == R.id.item_teststringtoqrcodeview) { -// Intent intent = new Intent(this, TestStringToQRCodeViewActivity.class); -// startActivityForResult(intent, REQUEST_QRCODEDECODE_ACTIVITY); -// //WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, TestStringToQrCodeViewActivity.class); + } else if (item.getItemId() == R.id.item_testqrgeneratoractivity) { + Intent intent = new Intent(this, QRGeneratorActivity.class); + startActivity(intent); } else if (item.getItemId() == R.id.item_testqrcodedecodeactivity) { Intent intent = new Intent(this, QRCodeDecodeActivity.class); startActivityForResult(intent, REQUEST_QRCODEDECODE_ACTIVITY); @@ -268,7 +276,7 @@ final public class MainActivity extends Activity { // } } - + public void onTestAssetsHtmlActivity(View view) { Intent intent = new Intent(this, AssetsHtmlActivity.class); intent.putExtra(AssetsHtmlActivity.EXTRA_HTMLFILENAME, "javascript_test.html"); @@ -281,7 +289,7 @@ final public class MainActivity extends Activity { @Override protected void onResume() { super.onResume(); - mLogView.start(); + //mLogView.start(); } /*@Override diff --git a/apputils/src/main/java/cc/winboll/studio/apputils/QRCodeDecodeActivity.java b/apputils/src/main/java/cc/winboll/studio/apputils/QRCodeDecodeActivity.java index 8561e18f..50fa432f 100644 --- a/apputils/src/main/java/cc/winboll/studio/apputils/QRCodeDecodeActivity.java +++ b/apputils/src/main/java/cc/winboll/studio/apputils/QRCodeDecodeActivity.java @@ -1,89 +1,323 @@ package cc.winboll.studio.apputils; /** - * @Author ZhanGSKen + * @Author ZhanGSKen&豆包大模型 * @Date 2025/01/18 10:32:21 - * @Describe 二维码扫码解码窗口 + * @Describe 二维码解码窗口 */ import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.net.Uri; import android.os.Bundle; +import android.provider.MediaStore; +import android.view.View; +import android.widget.Button; +import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toolbar; import cc.winboll.studio.apputils.R; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.DecodeHintType; +import com.google.zxing.LuminanceSource; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.NotFoundException; +import com.google.zxing.RGBLuminanceSource; +import com.google.zxing.Result; import com.google.zxing.ResultPoint; +import com.google.zxing.common.HybridBinarizer; import com.journeyapps.barcodescanner.BarcodeCallback; import com.journeyapps.barcodescanner.BarcodeResult; import com.journeyapps.barcodescanner.DecoratedBarcodeView; +import com.journeyapps.barcodescanner.DefaultDecoderFactory; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Hashtable; import java.util.List; public class QRCodeDecodeActivity extends Activity { public static final String TAG = "QRCodeDecodeActivity"; - public static final String EXTRA_RESULT = "EXTRA_RESULT"; private static final int REQUEST_CAMERA_PERMISSION = 1; + private static final int REQUEST_PICK_IMAGE = 2; + private static final int REQUEST_READ_STORAGE_PERMISSION = 3; + // 图片压缩阈值:超过1000px时压缩,避免内存溢出和解码效率低 + private static final int MAX_BITMAP_SIZE = 1000; - TextView resultTextView; - DecoratedBarcodeView barcodeView; - -// @Override -// public Activity getActivity() { -// return this; -// } + private TextView resultTextView; + private DecoratedBarcodeView barcodeView; + private Button btnDecodeFromAlbum; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_qrcodedecode); - - // 初始化工具栏 -// Toolbar mToolbar = findViewById(R.id.toolbar); -// setActionBar(mToolbar); - - //resultTextView = findViewById(R.id.activityqrcodedecodeTextView1); - barcodeView = findViewById(R.id.activityqrcodedecodeDecoratedBarcodeView1); - // 请求相机权限 -// if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) -// != PackageManager.PERMISSION_GRANTED) { -// ActivityCompat.requestPermissions(this, -// new String[]{android.Manifest.permission.CAMERA}, -// REQUEST_CAMERA_PERMISSION); -// } else { -// startScanning(); -// } - startScanning(); + initToolbar(); + initViews(); + checkCameraPermission(); + setAlbumDecodeClickListener(); } - private void startScanning() { - barcodeView.getBarcodeView().setDecoderFactory(null); - barcodeView.decodeContinuous(barcodeCallback); - } - - @Override - public void onRequestPermissionsResult(int requestCode, - String[] permissions, int[] grantResults) { - if (requestCode == REQUEST_CAMERA_PERMISSION) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - startScanning(); - } else { - // 权限被拒绝的处理 + private void initToolbar() { + Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar); + if (mToolbar != null) { + setActionBar(mToolbar); + if (getActionBar() != null) { + getActionBar().setDisplayHomeAsUpEnabled(true); + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); } } } - BarcodeCallback barcodeCallback = new BarcodeCallback(){ + private void initViews() { + resultTextView = (TextView) findViewById(R.id.activityqrcodedecodeTextView1); + barcodeView = (DecoratedBarcodeView) findViewById(R.id.activityqrcodedecodeDecoratedBarcodeView1); + btnDecodeFromAlbum = (Button) findViewById(R.id.btn_decode_from_album); + // 初始化扫码解码器(支持所有常见码制,避免仅支持QR_CODE的局限) + List formats = new ArrayList(); + formats.add(BarcodeFormat.QR_CODE); + formats.add(BarcodeFormat.CODE_128); + formats.add(BarcodeFormat.EAN_13); + barcodeView.getBarcodeView().setDecoderFactory(new DefaultDecoderFactory(formats)); + } + + private void setAlbumDecodeClickListener() { + btnDecodeFromAlbum.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE}, + REQUEST_READ_STORAGE_PERMISSION); + } else { + openAlbum(); + } + } + }); + } + + private void openAlbum() { + Intent pickImageIntent = new Intent(Intent.ACTION_PICK); + pickImageIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); + startActivityForResult(pickImageIntent, REQUEST_PICK_IMAGE); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_PICK_IMAGE && resultCode == RESULT_OK && data != null) { + Uri selectedImageUri = data.getData(); + if (selectedImageUri != null) { + try { + // 1. 读取图片并压缩(关键优化:避免大图片解码失败) + InputStream imageStream = getContentResolver().openInputStream(selectedImageUri); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; // 先获取图片尺寸,不加载像素 + BitmapFactory.decodeStream(imageStream, null, options); + imageStream.close(); // 关闭流,重新读取 + + // 计算压缩比例:超过MAX_BITMAP_SIZE时按比例压缩 + options.inSampleSize = calculateInSampleSize(options, MAX_BITMAP_SIZE, MAX_BITMAP_SIZE); + options.inJustDecodeBounds = false; // 开始加载压缩后的像素 + imageStream = getContentResolver().openInputStream(selectedImageUri); + Bitmap originalBitmap = BitmapFactory.decodeStream(imageStream, null, options); + imageStream.close(); + + if (originalBitmap == null) { + showToast("图片损坏,无法解析"); + return; + } + + // 2. 图片预处理:转为灰度图+提高对比度(解决模糊/低对比度图片识别问题) + Bitmap processedBitmap = processBitmap(originalBitmap); + + // 3. 解码预处理后的图片 + String decodeResult = decodeQrFromBitmap(processedBitmap); + + // 4. 结果处理 + if (decodeResult != null && !decodeResult.isEmpty()) { + resultTextView.setText("图片解码结果:" + decodeResult); + showDecodeResultDialog(decodeResult); + returnResultToPreviousPage(decodeResult); + } else { + // 尝试直接解码原图(防止预处理过度导致识别失败) + String originalResult = decodeQrFromBitmap(originalBitmap); + if (originalResult != null && !originalResult.isEmpty()) { + resultTextView.setText("图片解码结果:" + originalResult); + showDecodeResultDialog(originalResult); + returnResultToPreviousPage(originalResult); + } else { + new AlertDialog.Builder(this) + .setTitle("解码失败") + .setMessage("图片中未识别到二维码/条码,建议选择清晰、完整的图片") + .setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .show(); + } + } + + // 回收Bitmap,避免内存泄漏 + if (!originalBitmap.isRecycled()) originalBitmap.recycle(); + if (!processedBitmap.isRecycled() && processedBitmap != originalBitmap) { + processedBitmap.recycle(); + } + + } catch (Exception e) { + e.printStackTrace(); + showToast("图片处理失败:" + e.getMessage()); + } + } else { + showToast("未选择图片"); + } + } + } + + /** + * 核心优化1:计算图片压缩比例 + */ + private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + final int halfHeight = height / 2; + final int halfWidth = width / 2; + // 找到最接近reqWidth/reqHeight的压缩比例(2的倍数,保证图片质量) + while ((halfHeight / inSampleSize) >= reqHeight + && (halfWidth / inSampleSize) >= reqWidth) { + inSampleSize *= 2; + } + } + return inSampleSize; + } + + /** + * 核心优化2:图片预处理(灰度化+提高对比度) + * 解决模糊、低亮度、低对比度图片识别率低的问题 + */ + private Bitmap processBitmap(Bitmap bitmap) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + // 创建灰度图 + Bitmap grayBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(grayBitmap); + Paint paint = new Paint(); + + // 1. 灰度化矩阵 + ColorMatrix grayMatrix = new ColorMatrix(); + grayMatrix.setSaturation(0); // 饱和度设为0,转为灰度 + + // 2. 提高对比度矩阵(alpha=1.5,亮度=0,可根据需求调整) + ColorMatrix contrastMatrix = new ColorMatrix(); + contrastMatrix.set(new float[]{ + 1.5f, 0, 0, 0, 0, // 红通道对比度 + 0, 1.5f, 0, 0, 0, // 绿通道对比度 + 0, 0, 1.5f, 0, 0, // 蓝通道对比度 + 0, 0, 0, 1f, 0 // alpha通道不变 + }); + + // 合并灰度+对比度矩阵 + ColorMatrix combinedMatrix = new ColorMatrix(); + combinedMatrix.postConcat(grayMatrix); + combinedMatrix.postConcat(contrastMatrix); + + paint.setColorFilter(new ColorMatrixColorFilter(combinedMatrix)); + canvas.drawBitmap(bitmap, 0, 0, paint); + + return grayBitmap; + } + + /** + * 核心优化3:修复解码参数,支持更多场景 + */ + private String decodeQrFromBitmap(Bitmap bitmap) { + if (bitmap == null) return null; + + try { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + + // 修复1:使用RGBLuminanceSource,避免YUV格式导致的颜色偏差 + LuminanceSource source = new RGBLuminanceSource(width, height, pixels); + BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); + + // 修复2:完善解码参数,解决模糊、变形二维码识别问题 + Hashtable hints = new Hashtable(); + // 支持所有常见码制(不仅限于QR_CODE) + List formats = new ArrayList(); + formats.add(BarcodeFormat.QR_CODE); + formats.add(BarcodeFormat.CODE_128); + formats.add(BarcodeFormat.EAN_8); + formats.add(BarcodeFormat.EAN_13); + hints.put(DecodeHintType.POSSIBLE_FORMATS, formats); + // 字符编码:支持中文等多语言 + hints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); + // 容错模式:允许二维码有一定损坏(关键!解决轻微变形/污染的二维码) + hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); + // 不使用纯条码模式,兼容带logo的二维码 + hints.put(DecodeHintType.PURE_BARCODE, Boolean.FALSE); + + MultiFormatReader reader = new MultiFormatReader(); + reader.setHints(hints); + Result result = reader.decode(binaryBitmap); + return result.getText(); + + } catch (NotFoundException e) { + // 正常未识别到,不打印异常(避免日志冗余) + return null; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + // ==================== 原有逻辑(不变) ==================== + private void checkCameraPermission() { + if (checkSelfPermission(android.Manifest.permission.CAMERA) + != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{android.Manifest.permission.CAMERA}, + REQUEST_CAMERA_PERMISSION); + } else { + startScanning(); + } + } + + private void startScanning() { + barcodeView.decodeContinuous(barcodeCallback); + } + + private BarcodeCallback barcodeCallback = new BarcodeCallback() { @Override public void barcodeResult(BarcodeResult result) { - if (result.getText() != null) { - //Toast.makeText(MainActivity.this, "Scanned: " + result.getText(), Toast.LENGTH_SHORT).show(); - //ToastUtils.show("Scanned: " + result.getText()); + if (result != null && result.getText() != null) { barcodeView.pause(); - Intent intent = new Intent(); - intent.putExtra(EXTRA_RESULT, result.getText()); - setResult(RESULT_OK, intent); - finish(); + String decodeResult = result.getText(); + resultTextView.setText("扫码结果:" + decodeResult); + showDecodeResultDialog(decodeResult); + returnResultToPreviousPage(decodeResult); } } @@ -92,16 +326,111 @@ public class QRCodeDecodeActivity extends Activity { } }; + private void showDecodeResultDialog(String result) { + ScrollView scrollView = new ScrollView(this); + scrollView.setPadding(dip2px(16), dip2px(16), dip2px(16), dip2px(16)); + + TextView dialogTv = new TextView(this); + dialogTv.setTextSize(16); + dialogTv.setTextColor(getResources().getColor(android.R.color.black)); + dialogTv.setText(result); + scrollView.addView(dialogTv); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("解码结果"); + builder.setView(scrollView); + builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + barcodeView.resume(); + } + }); + builder.setCancelable(false); + builder.show(); + } + + private void returnResultToPreviousPage(String result) { + Intent intent = new Intent(); + intent.putExtra(EXTRA_RESULT, result); + setResult(RESULT_OK, intent); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_CAMERA_PERMISSION) { + if (grantResults != null && grantResults.length > 0) { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + startScanning(); + } else { + new AlertDialog.Builder(this) + .setTitle("权限申请") + .setMessage("扫码需要相机权限,请在设置中开启") + .setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + finish(); + } + }) + .setCancelable(false) + .show(); + } + } + } else if (requestCode == REQUEST_READ_STORAGE_PERMISSION) { + if (grantResults != null && grantResults.length > 0) { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + openAlbum(); + } else { + new AlertDialog.Builder(this) + .setTitle("权限申请") + .setMessage("从相册解码需要存储权限,请在设置中开启") + .setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .setCancelable(false) + .show(); + } + } + } + } + @Override protected void onResume() { super.onResume(); - barcodeView.resume(); + if (barcodeView != null) { + barcodeView.resume(); + } } @Override protected void onPause() { super.onPause(); - barcodeView.pause(); + if (barcodeView != null) { + barcodeView.pause(); + } + } + + private int dip2px(float dpValue) { + final float scale = getResources().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + @Override + public boolean onOptionsItemSelected(android.view.MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void showToast(String message) { + android.widget.Toast.makeText(this, message, android.widget.Toast.LENGTH_SHORT).show(); } } diff --git a/apputils/src/main/java/cc/winboll/studio/apputils/QRGeneratorActivity.java b/apputils/src/main/java/cc/winboll/studio/apputils/QRGeneratorActivity.java new file mode 100644 index 00000000..87c2f2f1 --- /dev/null +++ b/apputils/src/main/java/cc/winboll/studio/apputils/QRGeneratorActivity.java @@ -0,0 +1,97 @@ +package cc.winboll.studio.apputils; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/09/22 07:09 + * @Describe 二维码生成窗口 + */ +import android.app.Activity; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.Toast; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.WriterException; +import com.journeyapps.barcodescanner.BarcodeEncoder; + +public class QRGeneratorActivity extends Activity { + public static final String TAG = "QrGeneratorActivity"; + + // 控件引用 + private EditText etInputText; + private ImageView ivQrPreview; + private Button btnGenerateQr; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_qrgenerator); + + // 初始化控件 + initViews(); + // 设置按钮点击事件 + setGenerateClickListener(); + } + + /** + * 初始化布局控件 + */ + private void initViews() { + etInputText = findViewById(R.id.et_input_text); + ivQrPreview = findViewById(R.id.iv_qr_preview); + btnGenerateQr = findViewById(R.id.btn_generate_qr); + } + + /** + * 设置生成按钮点击事件:获取输入文字 → 生成二维码 → 显示到 ImageView + */ + private void setGenerateClickListener() { + btnGenerateQr.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // 1. 获取输入框文字(去除前后空格) + String inputText = etInputText.getText().toString().trim(); + + // 2. 空输入判断 + if (inputText.isEmpty()) { + Toast.makeText(QRGeneratorActivity.this, "请先输入要生成二维码的文字", Toast.LENGTH_SHORT).show(); + return; + } + + // 3. 生成二维码 Bitmap(宽高 500px,可调整) + Bitmap qrBitmap = generateQrCodeBitmap(inputText, 500, 500); + + // 4. 显示二维码到 ImageView + if (qrBitmap != null) { + ivQrPreview.setImageBitmap(qrBitmap); + } else { + Toast.makeText(QRGeneratorActivity.this, "二维码生成失败,请重试", Toast.LENGTH_SHORT).show(); + } + } + }); + } + + /** + * 核心方法:生成二维码 Bitmap + * @param content 二维码内容(输入的文字) + * @param width 二维码宽度(px) + * @param height 二维码高度(px) + * @return 生成的二维码 Bitmap,失败返回 null + */ + private Bitmap generateQrCodeBitmap(String content, int width, int height) { + try { + // 初始化二维码编码器(指定格式为 QR_CODE) + BarcodeEncoder encoder = new BarcodeEncoder(); + // 生成二维码 Bitmap(参数:内容、格式、宽、高) + return encoder.encodeBitmap(content, BarcodeFormat.QR_CODE, width, height); + } catch (WriterException e) { + // 生成失败(如内容过长、宽高非法),打印异常信息 + e.printStackTrace(); + return null; + } + } +} + diff --git a/apputils/src/main/java/cc/winboll/studio/apputils/WinBoLLActivity.java b/apputils/src/main/java/cc/winboll/studio/apputils/WinBoLLActivity.java index f57a8673..1d670480 100644 --- a/apputils/src/main/java/cc/winboll/studio/apputils/WinBoLLActivity.java +++ b/apputils/src/main/java/cc/winboll/studio/apputils/WinBoLLActivity.java @@ -6,19 +6,9 @@ package cc.winboll.studio.apputils; * @Describe WinBoLLActivity */ import android.app.Activity; -import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; -public class WinBoLLActivity extends Activity implements IWinBoLLActivity { +public class WinBoLLActivity extends Activity { public static final String TAG = "WinBoLLActivity"; - @Override - public Activity getActivity() { - return this; - } - - @Override - public String getTag() { - return TAG; - } } diff --git a/apputils/src/main/res/layout/activity_main.xml b/apputils/src/main/res/layout/activity_main.xml index 7a07e441..802ac582 100644 --- a/apputils/src/main/res/layout/activity_main.xml +++ b/apputils/src/main/res/layout/activity_main.xml @@ -54,13 +54,6 @@ - - diff --git a/apputils/src/main/res/layout/activity_qrcodedecode.xml b/apputils/src/main/res/layout/activity_qrcodedecode.xml index ba7ff278..1f28ad5b 100644 --- a/apputils/src/main/res/layout/activity_qrcodedecode.xml +++ b/apputils/src/main/res/layout/activity_qrcodedecode.xml @@ -1,20 +1,43 @@ - + android:layout_height="match_parent" + android:orientation="vertical"> - + + + + + +