如何在 Android 中将年度订阅价格换算为月价?
结论
可以用 NumberFormat.getCurrencyInstance(locale) 格式化换算后的月价。它会按照指定的 Locale 处理货币符号的位置、小数分隔符和分组符,不需要自行判断货币符号应放在金额左侧还是右侧。
年度价格继续使用 mySkuDetails.getPrice(),因为它已经是 Google Play 返回的本地化价格字符串。计算金额时不要使用 float,建议改用 BigDecimal,以减少精度和舍入问题。
实现方式
先把年度价格从微单位转换为货币单位,再除以 12。最后根据订阅的货币代码和当前界面的语言环境进行格式化:
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.Currency;
import java.util.Locale;
public static String formatMonthlyPrice(
Context context,
long yearlyPriceMicros,
String currencyCode
) {
Locale locale;
Configuration configuration = context.getResources().getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
locale = configuration.getLocales().get(0);
} else {
locale = configuration.locale;
}
Currency currency = Currency.getInstance(currencyCode);
BigDecimal yearlyPrice = BigDecimal.valueOf(yearlyPriceMicros)
.movePointLeft(6);
int fractionDigits = Math.max(currency.getDefaultFractionDigits(), 0);
BigDecimal monthlyPrice = yearlyPrice.divide(
BigDecimal.valueOf(12),
fractionDigits,
RoundingMode.HALF_UP
);
NumberFormat formatter = NumberFormat.getCurrencyInstance(locale);
formatter.setCurrency(currency);
formatter.setMinimumFractionDigits(fractionDigits);
formatter.setMaximumFractionDigits(fractionDigits);
return formatter.format(monthlyPrice);
}
调用方式如下:
String monthlyPrice = formatMonthlyPrice(
context,
mySkuDetails.getPriceAmountMicros(),
mySkuDetails.getPriceCurrencyCode()
);
String yearlyPrice = mySkuDetails.getPrice();
根据语言环境的不同,monthlyPrice 可能显示为:
$1.00
1,00 €
1,00 US$
¥100
最终格式由 Locale 和货币共同决定。
不要直接拼接货币代码
getPriceCurrencyCode() 返回的是 ISO 4217 货币代码,例如 USD、EUR,并不是已经格式化的货币符号。如果直接拼接:
sCurrencyCode + fMonthPrice
结果只会是类似 USD1.0 的字符串,而且无法正确处理这些差异:
- 货币符号位于金额之前还是之后
- 符号与金额之间是否需要空格
- 小数点使用
.还是, - 货币符号是否需要消歧,例如
$或US$ - 货币通常保留几位小数
这些格式规则应交给 NumberFormat 处理。
完整文案也应使用字符串资源
不要在 Java 代码中直接拼接 /month 等文本。不同语言的语序可能不同,应使用占位符,让翻译人员可以调整整句话:
<string name="subscription_price_message">
仅需%1$s/月即可订阅(按年支付%2$s)
</string>
然后传入格式化后的月价,以及 Google Play 返回的年度价格:
String message = context.getString(
R.string.subscription_price_message,
monthlyPrice,
yearlyPrice
);
其他语言可以使用不同的语序,例如英文资源:
<string name="subscription_price_message">
Get this subscription for only %1$s/month (billed annually at %2$s)
</string>
注意事项
mySkuDetails.getPrice() 已经包含 Google Play 确定的本地化价格格式,应直接用来展示年度价格,不要解析后再次格式化。
月价只是根据年度总价换算出的展示值,并不表示用户会按月扣款。文案中应明确写明“折合每月”或“按年支付”,避免让用户误以为这是月度订阅。
年度价格不一定能被 12 整除,因此需要确定舍入规则。上面的示例按照货币常用的小数位数进行 HALF_UP 舍入。如果产品需要显示更多精度,可以适当提高 maximumFractionDigits。不过,即使增加显示精度,12 个月的月价之和仍可能因舍入而与年度价格存在差异。
备注:内容仅供参考。
