场景:去年公司对外的水泥商城APP来了一个水印需求---对重要页面,如商品详情、订单详情页面等添加水印。水印的具体实现逻辑也比较简单,通过自定义drawable绘制水印,并作为背景添加到fragment容器中,再将该fragment添加至根布局,就完成了水印的实现。
然而,在上线几个月后,直至今日,线上总共反馈了3起水印文字透明度失效的问题,按照时间先后,分别为:OPPO的一款、华为Mate 30 Pro、华为畅享10e。
排查:当时在同款手机上测试时,均正常,就猜测可能是字体导致的,但在切换多种字体后,水印透明度也都正常,于是排除了字体原因。直至今日,主管在查阅资料时,发现了手机有个“高对比度文字”的开关,就是这个开关,导致了文字透明度失效。这个开关在Android 9.0以上手机中存在,下图是我Mate 40 Pro中,该开关位置:
解决方案:在定位到问题并复现后,就比较好解决了。我这里采用了比较简单的方式,整体思路如下:既然“高对比度文字”这个设置是针对文字,那我就放弃对文字透明度的设置,而对水印布局背景设置透明度。下面贴一下我项目中的水印实现方式,封装了一个水印单例管理类,开箱即用:
/**
* @author Flash
* @date 2020-04-18 11:19
* @description 水印
*/
public class WaterIconUtils {
private static WaterIconUtils wInstance;
public static WaterIconUtils getInstance(){
if (wInstance == null) {
synchronized (WaterIconUtils.class) {
wInstance = new WaterIconUtils();
}
}
return wInstance;
}
public void show(Activity activity, String text) {
WatermarkDrawable drawable = new WatermarkDrawable(activity);
drawable.mText = text;
drawable.mTextColor = 0xFF000000;
drawable.mTextSize = 40;
drawable.mRotation = -32.4f;
ViewGroup rootView = activity.findViewById(android.R.id.content);
FrameLayout layout = new FrameLayout(activity);
//对添加的水印布局添加透明度,移除字体、画笔等透明度,解决用户手机设置"高对比度颜色"时,水印透明度失效的问题
layout.setAlpha(0.1f);
layout.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
layout.setBackground(drawable);
rootView.addView(layout);
}
private class WatermarkDrawable extends Drawable {
private Paint mPaint;
/**
* 水印文本
*/
private String mText;
/**
* 字体颜色,十六进制形式,例如:0xAEAEAEAE
*/
private int mTextColor;
/**
* 字体大小,单位为sp
*/
private float mTextSize;
/**
* 旋转角度
*/
private float mRotation;
private Context mContext;
private WatermarkDrawable(Context context) {
mPaint = new Paint();
this.mContext = context;
}
@Override
public void draw(@NonNull Canvas canvas) {
int width = getBounds().right;
int height = getBounds().bottom;
int diagonal = (int) Math.sqrt(width * width + height * height); // 对角线的长度
mPaint.setColor(mTextColor);
mPaint.setTextSize(mTextSize);
mPaint.setAntiAlias(true);
float textWidth = mPaint.measureText(mText);
canvas.drawColor(mContext.getResources().getColor(R.color.transparent));
//角度
canvas.rotate(mRotation);
int index = 0;
float fromX;
// 以对角线的长度来做高度,这样可以保证竖屏和横屏整个屏幕都能布满水印
for (int positionY = diagonal / 15; positionY <= diagonal; positionY += diagonal / 15) {
fromX = -width + (index++ % 2) * textWidth; // 上下两行的X轴起始点不一样,错开显示
for (float positionX = fromX; positionX < width; positionX += textWidth * 2) {
canvas.drawText(mText, positionX, positionY, mPaint);
}
}
canvas.save();
canvas.restore();
}
@Override
public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
}
总结: 至此,从第一次线上报这个问题到现在,快1年了,终于解决了该项目水印问题。主要这个问题太少见,不好复现,且线上也只有极少数的用户会开启“高对比度文字”开关。
另外,在查阅资料的过程中,发现另一种实现方案:通过项目中自定义Canvas类实现,Canvas有一个@hide方法:setHighContrastText(),入参为boolean类型,该方法可以设置文字是否高对比度显示。但该方案成本有点大,有兴趣的话可以自行查找相关实现方式。