利用 BitmapShader 制作自帶邊框圓形頭像

目前為止,已經(jīng)掌握了兩種制作圓形頭像的方法,一種是利用 PorterDuffXfermode听诸,一種是 Shader 工具。至于選擇哪一種方法因人而異蚕泽,但幸運的是這兩種方法的使用都并不難晌梨。好了,現(xiàn)在介紹一下如何利用 BitmapShader 制作圓形頭像须妻,為了演示仔蝌,我做了一個 Demo ,可以自定義要顯示的圖片荒吏,邊框顏色敛惊,邊框?qū)挾龋Ч缦聢D所示绰更。

Paste_Image.png

BitmapShader 的簡單介紹

關(guān)于 Shader 是什么豆混,Shader 的種類有哪幾種以及如何使用不屬于本文范疇,對這方面不是很了解的同學动知,建議先去學習一下 Shader 的基本使用。

BitmapShader 主要的作用就是 通過 Paint 對象员辩,對 畫布進行指定的 Bitmap 填充盒粮,實現(xiàn)一系列效果,可以有以下三種模式進行選擇

  • CLAMP - 拉伸奠滑,這里拉伸的是圖片的最后一個元素丹皱,不斷地重復(fù),這個效果宋税,在圖片比較小摊崭,而所要畫的面積比較大的時候會比較明顯。

  • REPEAT - 重復(fù)杰赛,橫向縱向不斷地重復(fù)呢簸,不同于上一模式,這種模式在圖片比較小不能滿足要求是乏屯,會在橫向縱向不斷重復(fù)繪制圖形根时。

  • MIRROR - 翻轉(zhuǎn),這種模式和 REPEAT 是類似的辰晕,只不過這里的重復(fù)是翻轉(zhuǎn)著重復(fù)蛤迎,和折紙的效果差不多。

而我們將要使用的是 CLAMP 模式含友,因為只要我們對圖形的大小進行控制替裆,就可以避免圖像進行拉伸校辩。


具體實現(xiàn)

為了自定義 圖像,邊框?qū)挾群皖伾就覀兪紫仍?res/values 目錄下宜咒,新建一個 attrs.xml文件,里面要書寫的內(nèi)容如下所示

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyCustomView">
        <attr name="mborder_color" format="color"></attr>
        <attr name="mborder_width" format="dimension"></attr>
        <attr name="msrc" format="reference"></attr>
    </declare-styleable>
</resources>

當然胸遇,在這里還可以添加一些其他的特性荧呐。既然定義了我們想要使用的特性,那么我們就要在自定義 View 里面 解析這些屬性并且加以利用纸镊,解析過程如下所示

 TypedArray type = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView);
    mBorderColor = type.getColor(R.styleable.MyCustomView_mborder_color,0);
    mDrawable = type.getDrawable(R.styleable.MyCustomView_msrc);
    
    //將獲得的 Drawable 轉(zhuǎn)換成 Bitmap
    BitmapDrawable bitmapDrawable = (BitmapDrawable) mDrawable;
    mBitmap = bitmapDrawable.getBitmap();

    mBorderWidth = type.getDimensionPixelSize(R.styleable.MyCustomView_mborder_width, 2);

值得注意的是 mSrc 屬性的解析倍阐,由于獲得是 Drawable 對象,所以我們需要將其轉(zhuǎn)換為 Bitmap 對象峰搪。

下面就利用我們獲得的 Bitmap 對象進行圓形頭像的繪制凯旭,對 BitmapShaderPaint 的初始化如下所示

        mSrcBitmap = Bitmap.createScaledBitmap(mBitmap, mWidth, mHeight, false);
        mShader = new BitmapShader(mSrcBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mPaint = new Paint();
        mPaint.setShader(mShader);
        mRadius = (mWidth - mBorderWidth * 2 - 4) / 2;
        mCircleX = (mWidth) / 2;
        mCircleY = (mHeight) / 2;

mSrcBitmap 是對獲得的圖像進行適當?shù)目s小或者放大,以適應(yīng)我們對圖形的要求罐呼,而這里的 mWidth 和 mHeight 又是什么呢鞠柄?實際上就是我們在 定義視圖在 layout_widthlayout_height中傳遞進來的值,不過在這里我對他們進行了處理嫉柴,也就是選取最小值操作,這樣的話就可以避免由于寬大于高或者高大于寬而造成圖像填充不滿指定區(qū)域的現(xiàn)象夯尽。值得注意的是,自定義視圖登馒,需要對 wrap_content 進行特殊處理匙握,否則系統(tǒng)對該屬性的視圖不予以顯示。至于如何進行處理陈轿,可以看看本例的源碼,很簡單济欢,相信很多人一看就知道或者說早就了然于胸。

還有一點需要強調(diào)的是這里的 mRadius 茫叭,也就是將要繪制的圓的半徑半等,為什么要減去邊框的寬度 乘 2 呢呐萨? 要知道,我們的圓是根據(jù) 視圖指定的寬度或者高度來畫的谬擦,如果我們所畫 的圓恰好是指定視圖的內(nèi)切圓朽缎,那么邊框放在哪里呢?它肯定要被畫在視圖的外面北秽,那樣我們就看不到完整的邊框了最筒。所以床蜘,這么減去的意義在于為邊框騰出空間。

經(jīng)過以上操作扬蕊,我們就已經(jīng)將圓形頭像繪制出來了丹擎,下面來繪制邊框,其實非常簡單,我只不過是又定義了一個 Paint 對象庶骄,并且利用它畫了一個圓而已,畫筆的初始化操作如下所示

        mBorderPaint = new Paint();
        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setStrokeWidth(mBorderWidth);
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStrokeCap(Paint.Cap.ROUND);

好了灸异,下面就可以在 onDraw()函數(shù)中羔飞,進行繪制了肺樟,如下所示

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(mCircleX, mCircleY, mRadius, mPaint);
        canvas.drawCircle(mCircleX, mCircleY, mRadius, mBorderPaint);
    }

這樣,整個效果就算實現(xiàn)完畢了么伯。下面來看看如何使用

 <com.example.hwaphon.patheffecttest.MyView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:layout_marginRight="8dp"
            app:mborder_color="@android:color/holo_green_light"
            app:mborder_width="4dp"
            app:msrc="@drawable/myview_test"/>

注意卡儒,一定要在容器中加上這么一句

xmlns:app="http://schemas.android.com/apk/res-auto"

具體實現(xiàn)的核心代碼

package com.example.hwaphon.patheffecttest;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by Hwaphon on 2016/5/12.
 */
public class MyView extends View {

    private Bitmap mBitmap;
    private Drawable mDrawable;
    private Bitmap mSrcBitmap;

    private BitmapShader mShader;
    private Paint mPaint;

    private int mWidth, mHeight;

    private int mRadius;
    private int mCircleX, mCircleY;

    private int mBorderColor;
    private Paint mBorderPaint;
    private int mBorderWidth;

    public MyView(Context context) {
        this(context, null);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray type = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView);
        mBorderColor = type.getColor(R.styleable.MyCustomView_mborder_color,0);
        mDrawable = type.getDrawable(R.styleable.MyCustomView_msrc);

        //將獲得的 Drawable 轉(zhuǎn)換成 Bitmap
        BitmapDrawable bitmapDrawable = (BitmapDrawable) mDrawable;
        mBitmap = bitmapDrawable.getBitmap();

        mBorderWidth = type.getDimensionPixelSize(R.styleable.MyCustomView_mborder_width, 2);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = measureWidth(widthMeasureSpec);
        mHeight = measureHeight(heightMeasureSpec);
        int temp = mWidth > mHeight ? mHeight : mWidth;
        mWidth = mHeight = temp;
        initView();
        setMeasuredDimension(mWidth, mHeight);
    }

    private int measureHeight(int heightMeasureSpec) {
        int size = MeasureSpec.getSize(heightMeasureSpec);
        int sizeMode = MeasureSpec.getMode(heightMeasureSpec);
        int result = 0;
        if (sizeMode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = 200;
            if (sizeMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        return result;
    }

    private int measureWidth(int widthMeasureSpec) {
        int size = MeasureSpec.getSize(widthMeasureSpec);
        int sizeMode = MeasureSpec.getMode(widthMeasureSpec);
        int result = 0;
        if (sizeMode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = 200;
            if (sizeMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        return result;
    }

    private void initView() {

        mSrcBitmap = Bitmap.createScaledBitmap(mBitmap, mWidth, mHeight, false);
        mShader = new BitmapShader(mSrcBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mPaint = new Paint();
        mPaint.setShader(mShader);
        mRadius = (mWidth - mBorderWidth * 2) / 2;
        mCircleX = (mWidth) / 2;
        mCircleY = (mHeight) / 2;

        mBorderPaint = new Paint();
        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setStrokeWidth(mBorderWidth);
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStrokeJoin(Paint.Join.ROUND);
        mBorderPaint.setStrokeCap(Paint.Cap.ROUND);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(mCircleX, mCircleY, mRadius, mPaint);
        canvas.drawCircle(mCircleX, mCircleY, mRadius, mBorderPaint);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末欣舵,一起剝皮案震驚了整個濱河市缀磕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌袜蚕,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件廷没,死亡現(xiàn)場離奇詭異颠黎,居然都是意外死亡,警方通過查閱死者的電腦和手機狭归,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門过椎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疚宇,你說我怎么就攤上這事》蟠” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵榜揖,是天一觀的道長。 經(jīng)常有香客問我思劳,道長妨猩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任钠导,我火速辦了婚禮,結(jié)果婚禮上牡属,老公的妹妹穿的比我還像新娘。我一直安慰自己悴势,他們只是感情好措伐,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著捧存,像睡著了一般担败。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上提前,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天狈网,我揣著相機與錄音,去河邊找鬼拓哺。 笑死,一個胖子當著我的面吹牛窥摄,可吹牛的內(nèi)容都是我干的础淤。 我是一名探鬼主播哨苛,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼玻侥!你這毒婦竟也來了亿蒸?” 一聲冷哼從身側(cè)響起凑兰,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤姑食,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后音半,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡煌茬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年坛善,在試婚紗的時候發(fā)現(xiàn)自己被綠了邻眷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡耗溜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出燎字,到底是詐尸還是另有隱情阿宅,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布蛉鹿,位于F島的核電站往湿,受9級特大地震影響妖异,放射性物質(zhì)發(fā)生泄漏领追。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一棕孙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧钦铺,春花似錦肢预、人聲如沸矛洞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冈钦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間厉熟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工揍瑟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留乍炉,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓底循,卻偏偏與公主長得像槐瑞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子困檩,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容