Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義控件三部曲之繪圖篇(十六)——給控件添加陰影效果與發光效果

自定義控件三部曲之繪圖篇(十六)——給控件添加陰影效果與發光效果

編輯:關於Android編程

這節我們將學到如下內容:

傳統地給按鈕添加陰影的方法如何給已有控件添加陰影如何給圖片添加陰影

一、layerlist給按鈕添加陰影效果

給控件添加陰影有很多方法,但平常我們給按鈕添加陰影最常用的就是使用layerlist多層繪圖來添加陰影效果,我們先來看一下給按鈕添加的陰影效果圖:

 

\

從效果圖中可以明顯看出,按鈕的外圍多了一圈灰色的陰影效果。
在開始做陰影效果之前,我們先講解一下有關layerlist的知識。
在xml中,我們有常用的幾個標簽:shape、selector、layerlist;

shape標簽:以前我們講過,就是利用代碼繪制出背景效果,可以定義填充色、描邊、圓角、漸變等。不了解的同學可以參考下:《詳解shape標簽》selector標簽:用於定義在用戶不同的動作狀態下,使用不同的背景值。有關selector的知識,博主沒有講過,也不打算再講了,難度不大,自己搜幾個帖子就能學會了。layerlist標簽:這個標簽的主要作用就是將多個圖層按照順序疊起來,做為一個背景圖來顯示。

1、layerlist示例:

layerlist標簽就是模擬Photoshop中圖層的概念,把每一張圖層按照順序疊加起來,做為背景圖來顯示;
我們先來看一下簡單的例子,我們要顯示一下兩只蝸牛的圖片:

 

\

它由三張圖片組成:
一張純藍色的背景:(blog1_1.png)

\

一只黃蝸牛:(blog1_2.png)

\

一只土色蝸牛:(blog1_3.png)

\

我們先定義一個layerlist的文件(shade.xml)

 



    
    
    
這裡分別將上面的三張圖片做為item添加給layer-list;效果圖就是一開始演示的那樣。layer-list使用起來很簡單,只需要把每一層設置為其中的item即可。
有一點需要注意,layer-list標簽的Item中不僅可以設置drawable,也可以設置shape、selector,我們下面一一做下嘗試:

2、layer-list與shape標簽

編寫控件陰影drawable代碼
上面我們使用使用的是layer-list中item的drawable屬性來直接引入圖片,其實除了drawable屬性,item還有另外幾個屬性:
android:top 頂部的偏移量android:bottom 底部的偏移量android:left 左邊的偏移量android:right 右邊的偏移量這四個偏移量和控件的margin設置差不多,都是外間距的效果。如果不設置偏移量,前面的圖層就完全擋住了後面的圖層,從而也看不到後面的圖層效果了。
言歸正轉,先來看看如何在layer-list中使用shape標簽:


    
        
            
            
        
    
    
        
            
            
        
    
上面的代碼實現的效果是這樣的:
\

 

大家看到類似陰影的效果了吧,不錯,這段代碼就是實現按鈕陰影的代碼,我們來仔細看一下
首先,它使用layer-list將兩層shape疊加在一起,底部的shape代碼為:

 


    
        
        
    
底部是一個灰色的矩形,它的四個角被圓角化,並且填充為灰色。
上層繪制的shape對應的代碼為:

    
        
        
    
它同樣繪制的是一個四個角都被圓角化的矩形,但填充顏色是純白色。為了露出底層的灰色陰影,我們需要給上層的shape加上邊距,這也就是item的 android:left=”2dp” android:top=”2dp” android:bottom=”2dp” android:right=”2dp”這四個屬性的作用,相當於margin的作用。
使用陰影drawable
在寫好layer-list以後,我們需要在按鈕控件中使用它:
我們來看下效果:
\

 

從效果圖中可以看到,我們雖然實現了帶陰影的按鈕效果,但是在點擊時卻沒有任何狀態變化,這對於按鈕是完全不能接受的,所以我們需要給按鈕添加上狀態變化,這就需要用到selector標簽了

3、layer-list與selector標簽

改造方法一:使用layer-list做根結點
下面我們對上面shape的代碼進行改造,當用戶手指按下的時候,將前景色改為黃色,代碼為:


    
        
            
            
        
    
    
        
            
                
                    
                    
                
            
            
                
                    
                    
                
            
        
    
我們先來看一下效果,然後再來看代碼
\

這裡明顯實現了當用戶點擊時前景變化的功能。下面我們再來講解下代碼
首先,這裡同樣是繪制兩層layer,第一層,依然是陰影層,代碼沒動:

 


    
        
        
    
在第一層繪制完成以後,當繪制第二層時就出現問題了:

    
        
            
                
                
            
        
        
            
                
                
            
        
    
第二層中,會對當前用戶狀態做判斷,如果用戶當前是按下狀態,則繪制:

    
        
        
    
如果是其它狀態,則繪制默認圖像:

    
        
        
    
所以對於layer-list標簽,從這裡也可以看出來:它的繪制是逐層繪制的,層與層之間是沒有任何影響的,每一層可以單獨設置selector標簽來響應不同的用戶操作狀態。
改造方法二:使用selector做根結點
上面我們使用layer-list來做根結點來繪制出按鈕的不同狀態響應的效果,對selector、layer-list使用熟悉的同學,應該還可以想到另一種實現方式,使用selector做為根結點來實現響應不同用戶操作。
我們先直接上代碼吧:

 



    
        
            
                
                    
                    
                
            
            
                
                    
                    
                
            
        
    

    
        
            
                
                    
                    
                
            
            
                
                    
                    
                
            
        
    

同樣我們先來看一下使用代碼與效果,然後再來講解實現原理:
使用方法,同樣是做為background引入:
效果圖如下:
\

 

很明顯,實現了與上面layer-list標簽為根同樣的效果,我們現在來看一下代碼原理:
代碼看起來很長,很唬人,其實原理很簡單,它就是根據當前不同的狀態,繪制不同的圖形,當用戶是按壓狀態時,通過layer-list繪制出一下最上層是黃色,底層是灰色的按鈕背景圖像:

 


    
        
            
                
                
            
        
        
            
                
                
            
        
    
然後在其它狀態時,繪制一個前景色是白色,背景色是灰色的按鈕背景圖:

    
        
            
                
                
            
        
        
            
                
                
            
        
    
這部分代碼難度不大,就不再講了。

4、存在問題

由於使用layer-list標簽實現的陰影只能做為background引入,所以如果對你是文字時,它的陰影效果就變成了這樣:
對應效果圖為:
\

 

看起來跟按鈕一個樣 - _ -!!! 很囧有沒有,文字的陰影應該是這樣的才對:

\

所以我們下面就要開始講解如何實現文字的陰影效果啦,嘿嘿

二、Paint.setShadowLayer實現陰影效果

上面我們講了利用layer-list只能實現按鈕的陰影效果,對於文字和圖片都無法實現陰影效果,除了layer-list,我們只能用自定義控件來實現陰影效果了,Paint中有一個專門用來實現陰影效果的函數setShadowLayer,我們先來看看這個函數實現的陰影效果圖:

 

\

從效果圖中可以看出setShadowLayer函數能夠實現:

定制陰影模糊程度定制陰影偏移距離清除陰影和顯示陰影

1、setShadowLayer構造函數

看起來setShadowLayer好像能夠完成陰影定制的方方面面,我們先來看看它的構造函數:

 

 

public void setShadowLayer(float radius, float dx, float dy, int color)
它參數的意義如下:
float radius:意思是模糊半徑,radius越大越模糊,越小越清晰,但是如果radius設置為0,則陰影消失不見;有關清除陰影的問題,下面我們會專門講。float dx:陰影的橫向偏移距離,正值向右偏移,負值向左偏移float dy:陰影的縱向偏移距離,正值向下偏移,負值向上偏移int color:繪制陰影的畫筆顏色,即陰影的顏色(對圖片陰影無效)我們這裡需要著重講兩個點:一個是模糊半徑,另一個是繪制陰影的畫筆顏色為什麼對圖片無效:
模糊半徑的具體意義:
setShadowLayer使用的是高斯模糊算法,高斯模糊的具體算法是:對於正在處理的每一個像素,取周圍若干個像素的RGB值並且平均,然後這個平均值就是模糊處理過的像素,如果對圖片中的所有像素都這麼處理的話,處理完成的圖片就會變得模糊。
取周圍像素的半徑就是模糊半徑.很容易知道,模糊半徑越大,所得平均像素與原始像素相差就越大,也就越模糊
繪制陰影的畫筆顏色為什麼對圖片無效
從上面的效果圖中可以看出,使用setShadowLayer所產生的陰影,對於文字和繪制的圖形的陰影都是使用自定義的陰影畫筆顏色來畫的,而圖片的陰影則是直接產生一張相同的圖片,僅對陰影圖片的邊緣進行模糊。
大家可能會疑問,會什麼對圖片的處理是生成一張相同的背景圖片呢?這是因為為了給圖片添加陰影,如果統一使用某一種顏色來做陰影可能會與圖片的顏色相差很大,而且不協調,比如某張圖片的色彩非常豐富,而陰影如果使用灰色來做,可能就會顯得很突兀,所以為了解決這個問題,針對圖片的陰影就不再是統一顏色了,而是復制出這張圖片,把復制出的圖片的邊緣進行模糊,做為陰影;但這樣又會引起一個問題,就是如果我們想把圖片的陰影做成灰色怎麼辦?使用setShadowLayer自動生成陰影是沒辦法了,在下篇我們會具體來講,如何給圖片添加指定顏色的陰影。

注意:這裡有一點需要非常注意的是setShadowLayer只有文字繪制陰影支持硬件加速,其它都不支持硬件加速,所以為了方便起見,我們需要在自定義控件中禁用硬件加速。

2、示例一:初步使用setShadowLayer

多說無益,還是直接來個例子比較實在,本次示例效果圖如下:
\

 

這裡實現了對文本,圖形,Image的陰影效果;具體的代碼如下:

 

public class ShadowLayerView extends View {
    private Paint mPaint = new Paint();
    private Bitmap mDogBmp;
    public ShadowLayerView(Context context) {
        super(context);
        init();
    }

    public ShadowLayerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ShadowLayerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init(){
        setLayerType( LAYER_TYPE_SOFTWARE , null);
        mPaint.setColor(Color.GREEN);
        mPaint.setTextSize(25);
        mPaint.setShadowLayer(1, 10, 10, Color.GRAY);
        mDogBmp = BitmapFactory.decodeResource(getResources(),R.drawable.dog);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawText("啟艦大SB",100,100,mPaint);

        canvas.drawCircle(200,200,50,mPaint);

        canvas.drawBitmap(mDogBmp,null,new Rect(200,300,200+mDogBmp.getWidth(),300+mDogBmp.getHeight()),mPaint);
    }
}
代碼看起來很長,其實就是自定義了一個控件,在裡面畫了點東東;我們分別來看下吧
首先是初始化,在初始化時設置畫筆的顏色
private void init(){
    setLayerType( LAYER_TYPE_SOFTWARE , null);
    mPaint.setColor(Color.GREEN);
    mPaint.setTextSize(25);
    mPaint.setShadowLayer(1, 10, 10, Color.GRAY);
    mDogBmp = BitmapFactory.decodeResource(getResources(),R.drawable.dog);
}
在初始化的時候,就是先禁用硬件加速,然後設置paint的屬性,由於我們需要畫圖片,所以先把要畫的圖片加載進來。這裡需要注意兩個顏色:
mPaint.setColor(Color.GREEN);
mPaint.setShadowLayer(1, 10, 10, Color.GRAY);
mPaint.setColor指的是設置畫筆的顏色是綠色,從效果圖中也可以看出來畫出來的字體和圓形都是綠色的
而mPaint.setShadowLayer中設置的 Color.GRAY,指的是陰影的顏色,從效果圖中也明顯可以看出,字體和陰影的顏色都是灰色的。
然後就是onDraw的繪圖部分了,這裡就沒什麼好講的了,如果從頭看到這裡的話,canvas的操作應該很熟練了;

3、示例二:setShadowLayer各參數意義

下面我們就來實現一下這部分開篇時的效果,動態添加setShadowLayer中的各個參數,就可以明顯看出來它們的作用:
在上面的代碼上面,我們講setShadowLayer變成了動態設置,代碼如下:
public class ShadowLayerView extends View {
    private Paint mPaint = new Paint();
    private Bitmap mDogBmp;
    private int mRadius = 1,mDx = 10,mDy = 10;
    public ShadowLayerView(Context context) {
        super(context);
        init();
    }

    public ShadowLayerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ShadowLayerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init(){
        setLayerType( LAYER_TYPE_SOFTWARE , null);
        mPaint.setColor(Color.GREEN);
        mPaint.setTextSize(25);
        mDogBmp = BitmapFactory.decodeResource(getResources(),R.drawable.dog);
    }


    public void changeRadius() {
        mRadius++;
        postInvalidate();
    }

    public void changeDx() {
        mDx+=5;
        postInvalidate();
    }

    public void changeDy() {
        mDy+=5;
        postInvalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mPaint.setShadowLayer(mRadius, mDx, mDy, Color.GRAY);

        canvas.drawText("啟艦大SB",100,100,mPaint);

        canvas.drawCircle(200,200,50,mPaint);

        canvas.drawBitmap(mDogBmp,null,new Rect(200,300,200+mDogBmp.getWidth(),300+mDogBmp.getHeight()),mPaint);
    }
}
這段代碼難度並不大,只是將 mPaint.setShadowLayer中的各參數寫成了變量,並向外暴露了幾個接口changeRadius()、changeDx()、changeDy();當外部調用這些接口時,增加對應的變量,並且重繪控件;
由於每次重繪控件都肯定會調用onDraw方法,所以,我們將mPaint.setShadowLayer的設置放到onDraw方法裡來,以確保每次重繪時mPaint.setShadowLayer的設置都會被更新。
在使用時:
public class MyActivity extends Activity implements View.OnClickListener{
    private ShadowLayerView mShadowLayerView;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mShadowLayerView = (ShadowLayerView)findViewById(R.id.shadowlayerview);
        findViewById(R.id.radius_btn).setOnClickListener(this);
        findViewById(R.id.dx_btn).setOnClickListener(this);
        findViewById(R.id.dy_btn).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.radius_btn:
                mShadowLayerView.changeRadius();
                break;
            case R.id.dx_btn:
                mShadowLayerView.changeDx();;
                break;
            case R.id.dy_btn:
                mShadowLayerView.changeDy();
                break;
        }
    }
}
使用代碼很簡單,就不再講了。
效果圖如下:
\

 

從效果圖中可以明顯看到各個參數的區別,但正是通過效果圖,我們可以明顯得看出兩個結論:

1、圖片的陰影是不受陰影畫筆顏色影響的,它是一張圖片的副本;2、無論是圖片還是圖形,模糊時,僅模糊邊界部分,隨著模糊半徑的增大,向內、向外延伸;其實很好理解這個問題:由於模糊半徑的增大,高斯模糊向周邊取值的范圍在增大,所以向內、向外延伸的距離就會更大

4、Paint.clearShadowLayer()清除陰影

上面我們講解了使用setShadowLayer添加陰影的問題,下面我們再來看看如何清除陰影的。清除陰影其實有兩個方法,可以將setShadowLayer的radius的值設為0,也可以使用專門的清除陰影的函數:

 

 

//Paint系函數:清除ShadowLayer陰影
public void clearShadowLayer() 
將setShadowLayer的radius的值設為0來清除陰影的用法,我這裡就不再演示了,大家可以自己試試,我們這裡嘗試下使用clearShadowLayer() 來清除陰影的用法。
在上面函數的基礎上,我們另外添加一個變量來控制當前是否顯示陰影:
public class ShadowLayerView extends View {
    …………
    private boolean mSetShadow = true;
    …………
    public void clearShadow(){
        mSetShadow = false;
        postInvalidate();
    }

    public void showShadow(){
        mSetShadow = true;
        postInvalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mSetShadow) {
            mPaint.setShadowLayer(mRadius, mDx, mDy, Color.GRAY);
        }else {
            mPaint.clearShadowLayer();
        }

        canvas.drawText("啟艦大SB",100,100,mPaint);

        canvas.drawCircle(200,200,50,mPaint);

        canvas.drawBitmap(mDogBmp,null,new Rect(200,300,200+mDogBmp.getWidth(),300+mDogBmp.getHeight()),mPaint);
    }
}
修改的代碼很簡單,增加一個變量mSetShadow來控制當前是否顯示陰影,如果需要顯示陰影就調用mPaint.setShadowLayer(mRadius, mDx, mDy, Color.GRAY);設置陰影,如果不需要顯示陰影就調用mPaint.clearShadowLayer();來清除陰影;
對於使用btn調用clearShadow()、showShadow()這兩個接口的用法,就不再帖代碼了,沒啥難度,源碼裡也有;
效果圖如下:
\

 

在目前的所有例子中,我們的定義控件在xml中使用時,layout_widht、layout_height都統一設置成match_parent或者fill_parent來強制全屏;是時間教大家如何使用wrap_content屬性,如何讓控件自已計算高度了,下篇我們就來看看這個問題。

源碼在文章底部給出

三、TextView及其派生類使用ShadowLayer添加陰影效果

上面我們通過自定義控件來實現了自定義陰影效果,那麼問題來了,如果我需要給已有的控件添加陰影效果,實現下面這樣的效果:

 

\

 

1、XML實現

從上面可以看到,TextView,Button,EditView中的文字都具有陰影效果。那是怎麼實現的呢?難道我們需要在原生控件的甚而上派生一個類在onDraw裡使用setShadowLayer來繪制陰影嗎?
答案當然不是,setShadowLayer是API 1 就已經引入的方法,同樣,對於TextView和從TextView派生的類都自然具體XML屬性來設置陰影。這幾個設置陰影的XML屬性如下:

 

 

這幾個屬性的意義非常容易理解,直接對應setShadowLayer的幾個參數setShadowLayer(float radius, float dx, float dy, int color),但這幾個屬性只有TextVIew及其派生類才會有,其它類是沒有的,TextVIew的派生類如下:
\

 

所以一般我們使用的Button和EditText是可以使用Xml來實現陰影的。

2、代碼實現

既然能通過XML實現,當然也能會代碼版了,TextView及其派生類,都有一個Paint.setShadowLayer的同名方法:

 

 

//TextView中的設置陰影函數
public void setShadowLayer(float radius, float dx, float dy, int color) 
通過該方法就很容易來實現TextView及其派生類的陰影了。
使用示例如下:
TextView tv = (TextView)findViewById(R.id.tv);
tv.setShadowLayer(2,5,5, Color.GREEN);
效果與上面的一樣,這裡就不再講了,源碼裡都會有。
源碼在文章底部給出

四、SetMaskFilter之BlurMaskFilter實現發光效果

前面我們講了如何給控件添加陰影效果,其它跟陰影效果類似的還有一個發光效果:
\

 

上面就是我們這節要講的發光效果,在這個效果圖中,總共涉及了三個內容的發光效果:文字、圖形和Bitmap圖像。
從最後一個小狗的Bitmap所形成的發光效果中可以看到,與setShadowLayer一樣,發光效果也只會影響邊緣部分圖像,內部圖像是不受影響的。
從第三個圖形(紅綠各一半的Bitmap)中可以看到:發光效果是無法指定發光顏色的,采用邊緣部分的顏色取樣來進行模糊發光。所以邊緣是什麼顏色,發出的光也就是什麼顏色的。
所以初步我們對發光效果有如下結論:

與setShadowLayer一樣,發光效果也是使用的高斯模糊,並且只會影響邊緣部分圖像,內部圖像是不受影響的發光效果是無法指定發光顏色的,采用邊緣部分的顏色取樣來進行模糊發光。所以邊緣是什麼顏色,發出的光也就是什麼顏色的。

1、SetMaskFilter之BlurMaskFilter概述

Paint函數的的setMaskFilter聲明如下:

 

public MaskFilter setMaskFilter(MaskFilter maskfilter)

 

前面我們講到setColorFilter來設置顏色濾鏡,與setColorFilter一樣,setMaskFilter中的MaskFilter也是沒有具體實現的,也是通過派生子類來實現具體的不同功能的,MaskFilter有兩個派生類BlurMaskFilter和EmbossMaskFilter,其中BlurMaskFilter就是我們這段要講的實現發光效果的子類,而EmbossMaskFilter是用來實現浮雕效果的,用處很少,這裡就不再講了。另一點需要注意的是,setMaskFilter是不支持硬件加速的,必須關閉硬件加速才可以。
BlurMaskFilter的構造函數如下:

 

public BlurMaskFilter(float radius, Blur style)
其中:
float radius:用來定義模糊半徑,同樣是高斯模糊算法。Blur style:發光樣式,有內發光、外發光、和內外發光,分別對應:Blur.INNER(內發光)、Blur.SOLID(外發光)、Blur.NORMAL(內外發光)、Blur.OUTER(僅發光部分可見),這幾個模式,後面我們會逐個來展示用法。下面我們簡單舉一個例子來看看用法先,這個例子的代碼如下:
public class BlurMaskFilterView extends View {
    private Paint mPaint;
    public BlurMaskFilterView(Context context) {
        super(context);
        init();
    }

    public BlurMaskFilterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public BlurMaskFilterView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init(){
        setLayerType(LAYER_TYPE_SOFTWARE,null);
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setMaskFilter(new BlurMaskFilter(50, Blur.INNER));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawCircle(200,200,100,mPaint);
    }
}
這裡使用起來非常容易,只需要在paint的時候調用setMaskFilter將BlurMaskFilter的實例設置進行就可以了。這裡使用的內發光模式。我們來看下效果圖:
\

 

很明顯的內發光效果。下面我們分別來看看各種模式下的發光效果。

2、BlurStyle發光效果

(1)、Blur.INNER——內發光

 

\

(2)、Blur.SOLID——外發光

\

(3)、Blur.NORMAL——內外發光

\

(4)、Blur.OUTER——僅顯示發光效果

\

Blur.OUTER比較特殊,這種模式下僅會顯示發光效果,會把原圖像中除了發光部分,全部變為透明!
大家是否可以看出來發光效果與setShadowLayer所生成的陰影之間有什麼聯系?
setShadowLayer所生成的陰影,其實就是將新建的陰影圖形副本進行發光效果並且位移一定的距離而言。下篇我們就會利用這個原理來生成圖片指定顏色的陰影效果。
到這裡,這篇文章就結束了,下篇將繼續給大家講解如何給圖片添加指定顏色的陰影效果,並且初步教大家如何將其封裝成一個控件。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved