diff --git a/.github/workflows/.github-ci.yml b/.github/workflows/.github-ci.yml index ea23b49c..60049fb9 100644 --- a/.github/workflows/.github-ci.yml +++ b/.github/workflows/.github-ci.yml @@ -49,7 +49,7 @@ jobs: distribution: 'zulu' # 指定 Java 发行版 - name: Set up Android SDK - uses: android-actions/setup-android@v2 + uses: android-actions/setup-android@v4 with: api-level: 30 build-tools: 30.0.3 diff --git a/pos_android_studio_demo/pos_android_app/build.gradle b/pos_android_studio_demo/pos_android_app/build.gradle index 0ea2ca5a..471e749f 100644 --- a/pos_android_studio_demo/pos_android_app/build.gradle +++ b/pos_android_studio_demo/pos_android_app/build.gradle @@ -31,8 +31,8 @@ android { applicationId "com.dspread.pos_android_app" minSdk 24 targetSdk 34 - versionCode 148 - versionName "8.5.5-2609101405" + versionCode 149 + versionName "8.5.5-2609201905" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ndk { // Set up supported SO library architecture diff --git a/pos_android_studio_demo/pos_android_app/libs/dspread_pos_sdk_8.5.5-min.aar b/pos_android_studio_demo/pos_android_app/libs/dspread_pos_sdk_8.5.5-min.aar index e808cb95..b03ffd5e 100644 Binary files a/pos_android_studio_demo/pos_android_app/libs/dspread_pos_sdk_8.5.5-min.aar and b/pos_android_studio_demo/pos_android_app/libs/dspread_pos_sdk_8.5.5-min.aar differ diff --git a/pos_android_studio_demo/pos_android_app/libs/dspread_pos_sdk_8.5.5.aar b/pos_android_studio_demo/pos_android_app/libs/dspread_pos_sdk_8.5.5.aar index d7de0efd..22f3c506 100644 Binary files a/pos_android_studio_demo/pos_android_app/libs/dspread_pos_sdk_8.5.5.aar and b/pos_android_studio_demo/pos_android_app/libs/dspread_pos_sdk_8.5.5.aar differ diff --git a/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/manager/Constant.java b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/manager/Constant.java new file mode 100644 index 00000000..bd41c923 --- /dev/null +++ b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/manager/Constant.java @@ -0,0 +1,17 @@ +package com.dspread.pos.dualScreen.manager; + +public class Constant { + public static final String VERSION = "1.0.0"; ///< 版本号 + + // ===== 返回码常量 ===== + public class ResultConstant { + public static final int RESULT_SUCCESS = 0; + public static final int RESULT_ERROR_NO_DISPLAY = -1; + public static final int RESULT_ERROR_INVALID_PARAM = -2; + public static final int RESULT_ERROR_FILE_NOT_FOUND = -3; + public static final int RESULT_ERROR_NOT_SUPPORTED = -4; + public static final int RESULT_ERROR_SHOW_FAILED = -5; + } +} + + diff --git a/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/manager/IResultCallback.java b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/manager/IResultCallback.java new file mode 100644 index 00000000..849edc04 --- /dev/null +++ b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/manager/IResultCallback.java @@ -0,0 +1,10 @@ +package com.dspread.pos.dualScreen.manager; + +/** + * 操作结果回调接口 + */ +public interface IResultCallback { + void onSuccess(); + + void onFailure(int code, String msg); +} diff --git a/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/manager/IViceScreen.java b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/manager/IViceScreen.java new file mode 100644 index 00000000..4f75c12d --- /dev/null +++ b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/manager/IViceScreen.java @@ -0,0 +1,53 @@ +package com.dspread.pos.dualScreen.manager; + +import android.view.View; + +import java.util.ArrayList; + +/** + * 副屏控制接口 + * + *
定义了副屏图片显示、视频播放、自定义View显示、 + * 电源控制、亮度调节、开机Logo、分辨率查询等能力。
+ */ +public interface IViceScreen { + + /** 显示单张图片 */ + int showPic(String picPath); + + /** 显示图片轮播 */ + int showPic(ArrayList描述一张图片在副屏上的显示位置和尺寸。
+ */ +public class PictureData implements Parcelable { + private String path; + private int startX; + private int startY; + private int width; + private int height; + + public PictureData() { + } + + public PictureData(String path, int startX, int startY, int width, int height) { + this.path = path; + this.startX = startX; + this.startY = startY; + this.width = width; + this.height = height; + } + + protected PictureData(Parcel in) { + path = in.readString(); + startX = in.readInt(); + startY = in.readInt(); + width = in.readInt(); + height = in.readInt(); + } + + public static final Creator支持三种模式: + *
方案1:用 TextureView + MediaPlayer(走 GLES 合成)替代 VideoView/SurfaceView(走 overlay + * 合成),规避 MTK hwcomposer 长时间合成副屏视频图层时的 vendor 崩溃。
+ * + *方案2:定时重启播放(周期 stop+release 再重新 prepare),释放图层 buffer, + * 避免长时间连续循环合成累积出错。
+ */ +public class VideoPresentation extends Presentation { + + private TextureView textureView; + private MediaPlayer mediaPlayer; + private String videoPath; + private Surface surface; + + /* + private final Handler handler = new Handler(Looper.getMainLooper()); + // 定时重启间隔(毫秒),避免长时间连续合成触发 HWC 崩溃 + private static final long RESTART_INTERVAL_MS = 5 * 60 * 1000L; + + private final Runnable restartRunnable = this::restartPlayback;*/ + + public VideoPresentation(Context context, Display display) { + super(context, display); + FrameLayout root = new FrameLayout(getContext()); + root.setBackgroundColor(Color.BLACK); + root.setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + textureView = new TextureView(getContext()); + textureView.setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + root.addView(textureView); + setContentView(root); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // window 在 show() 之后才真正创建,此时设置 cutout 模式才可靠 + setupCutoutMode(); + } + + @Override + public void hide() { + super.hide(); + if (surface != null && mediaPlayer != null) { + mediaPlayer.pause(); + } + } + + /** + * 让窗口延伸到挖孔(cutout)区域,占满整个副屏 + */ + private void setupCutoutMode() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + Window window = getWindow(); + if (window != null) { + WindowManager.LayoutParams attrs = window.getAttributes(); + attrs.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + window.setAttributes(attrs); + } + } + } + + /** + * 播放指定路径的视频 + */ + public void playVideo(String videoPath) { + if (videoPath == null || videoPath.isEmpty()) return; + if (videoPath.equals(this.videoPath) && surface != null && mediaPlayer != null) { + mediaPlayer.start(); + return; + } + this.videoPath = videoPath; + textureView.setSurfaceTextureListener(surfaceTextureListener); + if (textureView.isAvailable()) { + surface = new Surface(textureView.getSurfaceTexture()); + startPlayback(); + } + } + + private final TextureView.SurfaceTextureListener surfaceTextureListener = + new TextureView.SurfaceTextureListener() { + @Override + public void onSurfaceTextureAvailable(SurfaceTexture st, int width, int height) { + surface = new Surface(st); + startPlayback(); + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture st, int width, int height) { + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture st) { + releasePlayer(); + releaseSurface(); + return true; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture st) { + } + }; + + private void startPlayback() { + if (surface == null || videoPath == null) return; + try { + releasePlayer(); + mediaPlayer = new MediaPlayer(); + mediaPlayer.setDataSource(videoPath); + mediaPlayer.setSurface(surface); + mediaPlayer.setLooping(true); + mediaPlayer.setVolume(0f, 0f); + mediaPlayer.setOnPreparedListener(mp -> { + mp.start(); + //scheduleRestart(); + }); + mediaPlayer.setOnErrorListener((mp, what, extra) -> true); + mediaPlayer.prepareAsync(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /* + // 方案2:周期停止并重新播放,释放 buffer,避免长时间连续合成 + private void restartPlayback() { + startPlayback(); + } + + private void scheduleRestart() { + handler.removeCallbacks(restartRunnable); + handler.postDelayed(restartRunnable, RESTART_INTERVAL_MS); + }*/ + + private void releasePlayer() { + //handler.removeCallbacks(restartRunnable); + final MediaPlayer mp = mediaPlayer; + mediaPlayer = null; + if (mp != null) { + new Thread(() -> { + try { + mp.setOnPreparedListener(null); + mp.setOnErrorListener(null); + if (mp.isPlaying()) { + mp.stop(); + } + mp.release(); + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } + } + + private void releaseSurface() { + if (surface != null) { + surface.release(); + surface = null; + } + } + + @Override + protected void onStop() { + super.onStop(); + releasePlayer(); + releaseSurface(); + } +} diff --git a/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/original/ViewPresentation.java b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/original/ViewPresentation.java new file mode 100644 index 00000000..854c931b --- /dev/null +++ b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/original/ViewPresentation.java @@ -0,0 +1,182 @@ +package com.dspread.pos.dualScreen.original; + +import android.app.Presentation; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.Display; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; + + +import com.dspread.pos.dualScreen.manager.PictureData; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * 自定义View显示 Presentation(原生实现) + * + *将外部传入的 View 显示到副屏,可选叠加图片(图片轮播)。
+ */ +public class ViewPresentation extends Presentation { + + private final FrameLayout rootLayout; + /** 轮播图片载体,固定放在自定义 View 的下层作为背景。 */ + private final ImageView picImageView; + private final Handler handler = new Handler(Looper.getMainLooper()); + + // 轮播相关 + private List图片层在底层轮播,自定义 View 叠加在最上层。
+ * + * @param intervalSeconds 轮播间隔(秒),小于 1 时按 1 秒处理 + */ + public void showViewWithPics(View view, List白底布局:顶部 "Enter amount" 提示文字、中部大号粗体金额、底部分割线, + * 三者整体垂直居中。金额变化时调用 {@link #setAmount(String)} / {@link #setAmount(long)} 更新文本, + * 再通过 {@link com.dspread.pos.dualScreen.manager.ViceScreenManager#show(View)} 刷新到副屏。
+ */ +public class AmountDisplayView extends FrameLayout { + + private final TextView tvAmount; + + public AmountDisplayView(Context context) { + super(context); + setBackgroundColor(Color.WHITE); + // 作为副屏 Presentation 根容器的子 View,铺满整个副屏 + setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + + // 内容容器:垂直排列,整体垂直居中,子元素水平居中 + LinearLayout contentLayout = new LinearLayout(context); + contentLayout.setOrientation(LinearLayout.VERTICAL); + contentLayout.setGravity(Gravity.CENTER_HORIZONTAL); + LayoutParams contentLp = new LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER); + addView(contentLayout, contentLp); + + // 顶部提示文字 + TextView tvHint = new TextView(context); + tvHint.setText(R.string.vice_screen_enter_amount); + tvHint.setTextColor(Color.BLACK); + tvHint.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); + tvHint.setGravity(Gravity.CENTER); + contentLayout.addView(tvHint, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + // 中部金额文字:大号粗体 + tvAmount = new TextView(context); + tvAmount.setText("¥0.00"); + tvAmount.setTextColor(Color.BLACK); + tvAmount.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); + tvAmount.setTypeface(Typeface.DEFAULT_BOLD); + tvAmount.setGravity(Gravity.CENTER); + LinearLayout.LayoutParams amountLp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + amountLp.topMargin = dp(8); + contentLayout.addView(tvAmount, amountLp); + + // 底部分割线:浅灰细线,贯穿整个宽度 + View divider = new View(context); + divider.setBackgroundColor(Color.parseColor("#E0E0E0")); + LinearLayout.LayoutParams dividerLp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, dp(1)); + dividerLp.topMargin = dp(24); + contentLayout.addView(divider, dividerLp); + } + + /** + * 更新副屏显示的金额(原样显示) + */ + public void setAmount(String amountText) { + if (amountText != null) { + tvAmount.setText(amountText); + } + } + + /** + * 更新副屏显示的金额(单位:分),自动格式化为 ¥x.xx + */ + public void setAmount(long amountInCents) { + tvAmount.setText(String.format(Locale.getDefault(), "%s%d.%02d", + getResources().getString(R.string.amount_identification), + amountInCents / 100, amountInCents % 100)); + } + + private int dp(float value) { + return (int) (getResources().getDisplayMetrics().density * value + 0.5f); + } +} diff --git a/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/view/PaymentQRPromptView.java b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/view/PaymentQRPromptView.java new file mode 100644 index 00000000..b17fb7dc --- /dev/null +++ b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/view/PaymentQRPromptView.java @@ -0,0 +1,126 @@ +package com.dspread.pos.dualScreen.view; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.Typeface; +import android.graphics.drawable.GradientDrawable; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.dspread.pos_android_app.R; + +/** + * 副屏扫码支付提示自定义 View + * + *两列布局:左侧红色圆角卡片显示"Trans Amount"+金额, + * 右侧白色描边卡片显示红色圆形扫码图标+提示文字, + * 通过 {@link com.dspread.pos.dualScreen.manager.ViceScreenManager#show(android.view.View)} 显示到副屏。
+ */ +public class PaymentQRPromptView extends LinearLayout { + + private final TextView tvAmount; + + public PaymentQRPromptView(Context context) { + super(context); + setOrientation(HORIZONTAL); + setGravity(Gravity.CENTER); + setBackgroundColor(Color.WHITE); + setPadding(dp(4), dp(4), dp(4), dp(4)); + // 作为副屏 Presentation 根容器的子 View,铺满整个副屏 + setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + + // 左列:红色圆角卡片,显示 Trans Amount + 金额 + LinearLayout amountCard = new LinearLayout(context); + amountCard.setOrientation(LinearLayout.VERTICAL); + amountCard.setGravity(Gravity.CENTER); + amountCard.setPadding(dp(4), dp(4), dp(4), dp(4)); + amountCard.setBackground(roundedDrawable(Color.parseColor("#FFD32F2F"), dp(8), 0, Color.TRANSPARENT)); + LayoutParams amountCardLp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f); + amountCardLp.setMargins(0, 0, dp(4), 0); + addView(amountCard, amountCardLp); + + TextView tvTitle = new TextView(context); + tvTitle.setText("Trans Amount"); + tvTitle.setTextColor(Color.WHITE); + tvTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10); + tvTitle.setGravity(Gravity.CENTER); + amountCard.addView(tvTitle); + + tvAmount = new TextView(context); + tvAmount.setTextColor(Color.WHITE); + tvAmount.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + tvAmount.setTypeface(Typeface.DEFAULT_BOLD); + tvAmount.setGravity(Gravity.CENTER); + LayoutParams amountTextLp = new LayoutParams( + LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + amountTextLp.topMargin = dp(4); + amountCard.addView(tvAmount, amountTextLp); + + // 右列:白色圆角卡片(红色描边),显示扫码图标 + 提示文字 + LinearLayout scanCard = new LinearLayout(context); + scanCard.setOrientation(LinearLayout.VERTICAL); + scanCard.setGravity(Gravity.CENTER); + scanCard.setPadding(dp(4), dp(4), dp(4), dp(4)); + scanCard.setBackground(roundedDrawable(Color.WHITE, dp(8), dp(1), Color.parseColor("#FFD32F2F"))); + LayoutParams scanCardLp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f); + scanCardLp.setMargins(dp(4), 0, 0, 0); + addView(scanCard, scanCardLp); + + // 红色圆形背景 + 白色扫码图标 + FrameLayout iconWrap = new FrameLayout(context); + GradientDrawable circleBg = new GradientDrawable(); + circleBg.setShape(GradientDrawable.OVAL); + circleBg.setColor(Color.parseColor("#FFD32F2F")); + iconWrap.setBackground(circleBg); + scanCard.addView(iconWrap, new LayoutParams(dp(32), dp(32))); + + ImageView ivScan = new ImageView(context); + ivScan.setImageResource(R.mipmap.ic_salemethod_scan); + ivScan.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN); + ivScan.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + iconWrap.addView(ivScan, new FrameLayout.LayoutParams( + dp(18), dp(18), Gravity.CENTER)); + + // 提示文字 + TextView tvPrompt = new TextView(context); + tvPrompt.setText("Please place your payment code above the camera."); + tvPrompt.setTextColor(Color.BLACK); + tvPrompt.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10); + tvPrompt.setGravity(Gravity.CENTER); + LayoutParams promptLp = new LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + promptLp.topMargin = dp(4); + scanCard.addView(tvPrompt, promptLp); + } + + /** + * 更新副屏显示的金额 + */ + public void setAmount(String amountText) { + if (amountText != null) { + tvAmount.setText(amountText); + } + } + + private GradientDrawable roundedDrawable(int fillColor, int radius, int strokeWidth, int strokeColor) { + GradientDrawable drawable = new GradientDrawable(); + drawable.setShape(GradientDrawable.RECTANGLE); + drawable.setCornerRadius(radius); + drawable.setColor(fillColor); + if (strokeWidth > 0) { + drawable.setStroke(strokeWidth, strokeColor); + } + return drawable; + } + + private int dp(int value) { + return (int) (getResources().getDisplayMetrics().density * value + 0.5f); + } +} diff --git a/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/view/PaymentResultView.java b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/view/PaymentResultView.java new file mode 100644 index 00000000..2a6b9a4a --- /dev/null +++ b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/view/PaymentResultView.java @@ -0,0 +1,112 @@ +package com.dspread.pos.dualScreen.view; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.Typeface; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.dspread.pos_android_app.R; + +/** + * 副屏交易结果自定义 View + * + *白底布局:左右排列,左侧为交易成功/失败图标,右侧为交易金额。 + * 通过 {@link #setResult(boolean)} 切换图标、{@link #setAmount(String)} 更新金额, + * 再通过 {@link com.dspread.pos.dualScreen.manager.ViceScreenManager#show(View)} 刷新到副屏。
+ */ +public class PaymentResultView extends FrameLayout { + + private final ImageView ivResult; + private final TextView tvAmount; + private final TextView tvErrorMsg; + + public PaymentResultView(Context context) { + super(context); + setBackgroundColor(Color.WHITE); + // 作为副屏 Presentation 根容器的子 View,铺满整个副屏 + setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + + // 内容容器:垂直排列,整体居中 + LinearLayout contentLayout = new LinearLayout(context); + contentLayout.setOrientation(LinearLayout.VERTICAL); + contentLayout.setGravity(Gravity.CENTER); + addView(contentLayout, new LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER)); + + // 上半部分:水平排列图标与金额 + LinearLayout rowLayout = new LinearLayout(context); + rowLayout.setOrientation(LinearLayout.VERTICAL); + rowLayout.setGravity(Gravity.CENTER); + contentLayout.addView(rowLayout, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + // 左侧结果图标 + ivResult = new ImageView(context); + ivResult.setImageResource(R.mipmap.icon_success); + rowLayout.addView(ivResult, new LinearLayout.LayoutParams(dp(40), dp(40))); + + // 右侧交易金额 + tvAmount = new TextView(context); + tvAmount.setTextColor(Color.BLACK); + tvAmount.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20); + tvAmount.setTypeface(Typeface.DEFAULT_BOLD); + tvAmount.setGravity(Gravity.CENTER_VERTICAL); + LinearLayout.LayoutParams amountLp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + //amountLp.leftMargin = dp(16); + rowLayout.addView(tvAmount, amountLp); + + // 下方错误信息 + tvErrorMsg = new TextView(context); + tvErrorMsg.setTextColor(Color.BLACK); + tvErrorMsg.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); + tvErrorMsg.setGravity(Gravity.CENTER); + LinearLayout.LayoutParams errorLp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + errorLp.topMargin = dp(2); + contentLayout.addView(tvErrorMsg, errorLp); + } + + /** + * 设置交易结果图标 + * + * @param success true 显示成功图标,false 显示失败图标 + */ + public void setResult(boolean success) { + ivResult.setImageResource(success ? R.mipmap.icon_success : R.mipmap.payment_failed); + } + + /** + * 更新副屏显示的交易金额 + */ + public void setAmount(String amountText) { + if (amountText != null) { + tvAmount.setText(amountText); + } + } + + /** + * 更新副屏显示的错误信息 + */ + public void setErrorMsg(String errorMsg) { + if (!TextUtils.isEmpty(errorMsg)) { + tvErrorMsg.setText(errorMsg); + } + } + + private int dp(float value) { + return (int) (getResources().getDisplayMetrics().density * value + 0.5f); + } +} diff --git a/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/view/ProcessingDisplayView.java b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/view/ProcessingDisplayView.java new file mode 100644 index 00000000..8004b5b0 --- /dev/null +++ b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/dualScreen/view/ProcessingDisplayView.java @@ -0,0 +1,96 @@ +package com.dspread.pos.dualScreen.view; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.Typeface; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * 副屏"执行中"提示自定义 View + * + *白底布局:居中显示循环转动的加载动画,下方居中显示处理提示文本。 + * 文本变化时调用 {@link #setMessage(String)} 更新, + * 再通过 {@link com.dspread.pos.dualScreen.manager.ViceScreenManager#show(View)} 刷新到副屏。
+ */ +public class ProcessingDisplayView extends FrameLayout { + + private final TextView tvAmount; + private final TextView tvMessage; + + public ProcessingDisplayView(Context context) { + super(context); + setBackgroundColor(Color.WHITE); + // 作为副屏 Presentation 根容器的子 View,铺满整个副屏 + setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + + // 内容容器:垂直排列,整体居中 + LinearLayout contentLayout = new LinearLayout(context); + contentLayout.setOrientation(LinearLayout.VERTICAL); + contentLayout.setGravity(Gravity.CENTER); + addView(contentLayout, new LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER)); + + // 顶部加载动画:无限循环的圆形进度动画 + /* ProgressBar progressBar = new ProgressBar(context); + progressBar.setIndeterminate(true); + LinearLayout.LayoutParams pbLp = new LinearLayout.LayoutParams(dp(24), dp(24)); + pbLp.gravity = Gravity.CENTER_HORIZONTAL; + contentLayout.addView(progressBar, pbLp);*/ + + // 金额文本:位于提示文本上方,大号加粗显示 + tvAmount = new TextView(context); + tvAmount.setTextColor(Color.BLACK); + tvAmount.setTextSize(TypedValue.COMPLEX_UNIT_SP, 26); + tvAmount.setTypeface(Typeface.DEFAULT_BOLD); + tvAmount.setGravity(Gravity.CENTER); + tvAmount.setVisibility(View.GONE); + LinearLayout.LayoutParams amountLp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + amountLp.topMargin = dp(12); + contentLayout.addView(tvAmount, amountLp); + + // 底部处理提示文本 + tvMessage = new TextView(context); + tvMessage.setTextColor(Color.BLACK); + tvMessage.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + tvMessage.setGravity(Gravity.CENTER); + LinearLayout.LayoutParams msgLp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + msgLp.topMargin = dp(16); + contentLayout.addView(tvMessage, msgLp); + } + + /** + * 更新副屏显示的处理提示文本 + */ + public void setMessage(String msg) { + if (msg != null) { + tvMessage.setText(msg); + } + } + + /** + * 更新副屏显示的金额文本,金额为空时隐藏 + */ + public void setAmount(String amount) { + if (amount == null || amount.isEmpty()) { + tvAmount.setVisibility(View.GONE); + } else { + tvAmount.setText(amount); + tvAmount.setVisibility(View.VISIBLE); + } + } + + private int dp(float value) { + return (int) (getResources().getDisplayMetrics().density * value + 0.5f); + } +} diff --git a/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/ui/home/HomeFragment.java b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/ui/home/HomeFragment.java index a3ad190c..a60c5309 100644 --- a/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/ui/home/HomeFragment.java +++ b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/ui/home/HomeFragment.java @@ -5,22 +5,27 @@ import android.os.CountDownTimer; import android.view.KeyEvent; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; -import com.dspread.pos.common.base.BaseFragmentWithViewCache; -import com.dspread.pos.TitleProviderListener; -import com.dspread.pos.ui.payment.PaymentMethodActivity; -import com.dspread.pos.utils.TRACE; -import com.dspread.pos_android_app.BR; -import com.dspread.pos_android_app.R; -import com.dspread.pos_android_app.databinding.FragmentHomeBinding; - -import me.goldze.mvvmhabit.utils.SPUtils; - - +import com.dspread.pos.common.base.BaseFragmentWithViewCache; +import com.dspread.pos.TitleProviderListener; +import com.dspread.pos.dualScreen.manager.ViceScreenManager; +import com.dspread.pos.dualScreen.view.AmountDisplayView; +import com.dspread.pos.ui.payment.PaymentMethodActivity; +import com.dspread.pos.utils.DeviceModelUtils; +import com.dspread.pos.utils.TRACE; +import com.dspread.pos_android_app.BR; +import com.dspread.pos_android_app.R; +import com.dspread.pos_android_app.databinding.FragmentHomeBinding; + +import me.goldze.mvvmhabit.utils.SPUtils; + + public class HomeFragment extends BaseFragmentWithViewCache