android 蒙版實現

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,也許那些大牛們認爲有黑線的纔是對的吧 : (
發佈了34 篇原創文章 · 獲贊 115 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章