Android下蒙板效果的实现
摘自:http://site.douban.com/widget/notes/110027/note/88525689/
?
Android下蒙板效果的实现
2010-09-01 10:59:33Layout结构:<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center_horizontal" > <TextView android:id="@+id/left" android:text="@string/left" android:textSize="18px" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginTop="22px" /> <TextView android:id="@+id/middle" android:text="@string/middle" android:textSize="40px" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_alignParentTop="true" /><TextView android:id="@+id/right" android:text="@string/right" android:textSize="18px" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:layout_marginTop="22px" /> <ImageView android:id="@+id/mask" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:src="@drawable/mask" /></RelativeLayout>就是想要下面一个效果: |
? |
使用的蒙板是: |
? |
但是问题出现了, 原本期待出现的效果变成了下图: |
? |
看到了吗?左右两边应该是透明渐变的非常平滑的过渡变成了一条透明一条黑线的巨惨效果,相信你应该能想象到我拿这个效果去给设计师看的时候,他们的头上的黑线不比这少吧…原因分析为什么会出现上述的问题呢?那是因为在OPhone 1.5/Android 1.6之后的版本,带Alpha通道,支持透明的PNG24 的渲染是有问题的。在一般的手机上,都只支持到16位色,而这时系统会把PNG24降为PNG8来渲染,这样就出现了上面的惨不忍睹的情况。所以这时候反而是低版本的手机上是渲染正常的,而这个结果相信是大家都不想看到的。那么有办法解决吗?当然有。解决方案1 – 抖动(Dithering)使用抖动,可以解决这个问题吗?这里的抖动说的不是靠用户本身自己抖动,当频率和那些黑线的频率一致时,蒙板就会变成平滑的渐变… 其实是指使用加入噪点的方式,在过渡的中间进行”抖动 “使渐变更平滑。设计师/美工可以在他们使用的PhotoShop里面设置加噪点或抖动,还有人特意为这个做了一个滤镜,好人啊。而程序员有两种方式:1 直接在代码里面写ImageView v = (ImageView)findViewById(R.id.mask);v.getDrawable().setDither(true);2 使用xml定义drawable <?xml version="1.0" encoding="UTF-8"?><nine-patch xmlns:android="http://schemas.android.com/apk/res/android" android:src="@drawable/mask" android:dither="true" />具体可以参见Android Tales的http://android.amberfog.com/?p=247于是加了抖动之后的效果: |
? |
结论: 失败,无论你怎么抖,还是黑线… 是那些大牛的方子不灵吗?No!他们针对的是渐变,不是透明的渐变!抖动的确可以使渐变更平滑,使原来会有间断线或色块的问题解决,但是不会使一个透明渐变的黑线消失。解决方案2 – 代码生成图像既然上面的方法失败了,直接用代码生成一个遮罩图呢?咱们可是程序员啊!下面是生成渐变透明图的代码: public static Bitmap createLinear(Context context, int width, int height, int from_color, int to_color){ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); int[] data8888 = new int[width]; makeRamp(premultiplyColor(from_color), premultiplyColor(to_color), width, data8888); bitmap.copyPixelsFromBuffer(makeBuffer(data8888, width)); return bitmap; } private static void makeRamp(int from, int to, int n, int[] ramp8888){ int r = getR32(from) << 23; int g = getG32(from) << 23; int b = getB32(from) << 23; int a = getA32(from) << 23; int dr = ((getR32(to) << 23) - r) / (n - 1); int dg = ((getG32(to) << 23) - g) / (n - 1); int db = ((getB32(to) << 23) - b) / (n - 1); int da = ((1 << 23) - a) / (n - 1); for (int i = 0; i < n; i++) { ramp8888[i] = pack8888(r >> 23, g >> 23, b >> 23, a >> 23); r += dr; g += dg; b += db; a += da; } }这段不详细解释了,而且也是从Android的ApiDemos里面扒的,基本原理就是一组像素一组像素的设定颜色和Alpha值达到渐变透明的图片。结论是? 又杯具了。失败的原因应该是这段代码生成的本质还是PNG8,不支持真正的透明。Ok, 咱们可是程序员,还可以使用渐变的画笔吧:public static Bitmap createLinearImage(Context context, int width, int height, int color0, int color1, float rate, boolean left) { //Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8); //Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444); //Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(); Paint paintShader = new Paint(); LinearGradient shader; int w = Math.round(width*rate); if (left) { paint.setColor(color0); canvas.drawRect(0, 0, w, height, paint); shader = new LinearGradient(0, 0, width, 0, color0, color1, Shader.TileMode.CLAMP); paintShader.setShader(shader); canvas.drawRect(w, 0, width, height, paintShader); }else{ shader = new LinearGradient(0, 0, width, 0, color0, color1, Shader.TileMode.CLAMP); paintShader.setShader(shader); canvas.drawRect(0, 0, width - w, height, paintShader); paint.setColor(color1); canvas.drawRect(width - w , 0, width, height, paint); } return bitmap; }这段代码是使用了渐变效果的Painter来生成图像,注意我注释掉的那些图片格式,8888其实对应的就是PNG8, 别的是更挫的格式。结论仍然是不好用。原因应该和上面的一样。解决方案3 – 去掉透明这个其实不算一个真正的解决方案,就是不要透明的蒙版,两边直接遮死,不会露出下面的文字。但是对于一个想做出和IPhone的应用一样精致的程序员是不会接受这样绥靖的方案的。于是最后的解决方案终于到来了。终极解决方案Android平台可以使用xml来定义几种图像,其中就包括渐变图。比如下面的xml<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android"> <gradient android:startColor="#FF000000 " android:centerColor="#FF00000" android:endColor="#00000000" android:shape="rectangle" android:centerX="0.2" android:angle="0" /></shape>上面定义的图像翻译成中文就是,这是一张图,它从左到右渐变,开始到20%的地方都是不透明的黑色,从20%到最后开始渐变,变成纯透明的黑色。它的区域的形状是矩形。所以咱们把这个xml保存到res/drawable目录下,ImageView的src就可以设置成这个文件了。再将原来的layout文件改成一左一右两张蒙版的ImageView, 分别使用上面的drawable和与之成左右镜像的drawble就可以达到最完美的效果了!就是下面的图所示: |
? |
结论:使用xml来定义出来的drawble,可以达到完美的渲染。原因是啥呢?这个我也不知道… 估计要去问google的大牛们了。我推测是前几种的方案系统实现的渲染代码有bug,或者是最后的方案的实现的渲染代码才是bug,也许那些大牛们认为有黑线的才是对的吧 : (