Android下蒙板效果的實現
阿新 • • 發佈:2019-01-30
Layout結構:
<?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,也許那些大牛們認為有黑線的才是對的吧 : (
<?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="
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="
>
<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,也許那些大牛們認為有黑線的才是對的吧 : (