1. 程式人生 > >android 使用BitmapShader實現圓形以及放大鏡效果

android 使用BitmapShader實現圓形以及放大鏡效果

在一些顯示使用者頭像的時候,大多數都是顯示圓形的,而不是顯示一個正方形或者長方形,這樣顯得很呆板,視覺效果也不好看,今天就用二種方式實現圖片圓形顯示的效果,在先講這個效果實現之前,講下BitmapShader到底能幹嘛,android有幾個關於渲染的類,使用它能實現一些不錯的效果,比如今天講的BitmapShader的就是關於影象渲染,還有其他4種,這都會再以後都會以部落格的形式寫出來,今天就單獨講講BitmapShader,進入BitmapShader原始碼發現這是你見過最簡單的系統類原始碼了

package android.graphics;
public class BitmapShader extends 
android.graphics.Shader { public BitmapShader(android.graphics.Bitmap bitmap, android.graphics.Shader.TileMode tileX, android.graphics.Shader.TileMode tileY) { /* compiled code */ } }
這建構函式中就三個引數,簡單說明下:

第一個bitmap:就是你渲染影象物件

第二個引數tileX:就是你x軸方向渲染模式

第三個引數tileY:就是你y軸方向渲染模式

關於渲染模式有三種:

從上面的程式碼中知道BitmapShader是繼承了Shader類,而模式就是定義在Shader中的一個列舉而已

package android.graphics;
public class Shader {
    public Shader() { /* compiled code */ }

    public boolean getLocalMatrix(android.graphics.Matrix localM) { /* compiled code */ }

    public void setLocalMatrix(android.graphics.Matrix localM) { /* compiled code */ }

    protected void finalize() throws 
java.lang.Throwable { /* compiled code */ } public static enum TileMode { CLAMP, MIRROR, REPEAT; private TileMode() { /* compiled code */ } } }
從程式碼中我們就清晰的看到定義的TileMode有三種

CLAMP:當view控制元件寬或者高大於你渲染圖形的大小時,拉伸最後一個畫素去鋪滿剩下的地方

MIRROR:當view控制元件寬或者高大於你渲染圖形的大小時,通過映象翻轉鋪滿剩下的地方

REPEAT:當view控制元件寬或者高大於你渲染圖形的大小時,重複圖片平鋪整個畫面(電腦設定桌布)

等下會說這個三個模式,先把第一種實現圓形的方式程式碼貼下:

package com.bitmapshaderdemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
 * Created by admin on 2016/11/22.
 */
public class RoundHeadView extends View {
    private static final String TAG ="RoundHeadView" ;
    private Paint mPaint;
    private ShapeDrawable shapeDrawable;
    private BitmapShader bitmapShaderp;
    public RoundHeadView(Context context) {
        this(context,null);
}
    public RoundHeadView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
}
    public RoundHeadView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
initPaint();
init();
setBackgroundColor(Color.RED);
}
    private void init() {
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.gird);
Log.e(TAG,"-"+bitmap.getWidth()+"-"+""+bitmap.getHeight());
shapeDrawable = new ShapeDrawable(new OvalShape());
bitmapShaderp = new BitmapShader(bitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
shapeDrawable.getPaint().setShader(bitmapShaderp);
}
    private void initPaint() {
        mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
}

    @Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
shapeDrawable.draw(canvas);
}
}
執行起來看效果:


發現螢幕一片空白,這是為什麼呢? 是因為你沒給圖片顯示設定位置,哪可能會說我我這個自定義view的寬和高就是螢幕的寬和高,但是你必須要給shapeDrawable設定顯示區域,

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
shapeDrawable.setBounds(200,200,400,400);
shapeDrawable.draw(canvas);
}
我們在onDraw()方法中指定了顯示的區域,就是一個矩形區域,現在執行看效果


ok,一個美女頭像出來了,這是一種實現方式,下面不使用ShapeDrawable再實現下:

package com.bitmapshaderdemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
 * Created by admin on 2016/11/22.
 */
public class RoundHeadView extends View {
    private static final String TAG ="RoundHeadView" ;
    private Paint mPaint;
    private BitmapShader bitmapShaderp;
    public RoundHeadView(Context context) {
        this(context,null);
}
    public RoundHeadView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
}
    public RoundHeadView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
initPaint();
init();
}
    private void init() {
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.gird);
Log.e(TAG,"-"+bitmap.getWidth()+"-"+""+bitmap.getHeight());
bitmapShaderp = new BitmapShader(bitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
mPaint.setShader(bitmapShaderp);
}
    private void initPaint() {
        mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
}
    @Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
canvas.drawCircle(200,200,160,mPaint);
}
}
這就是使用Paint中的setShader()方法把圖片渲染成一個圓形,因為這個渲染是作用於這個畫筆上的也就是程式碼中的mPaint,而我們又用這個畫筆去畫圓,這就是為啥一個長方形的圖片變成圓形的,要變成圓角矩形也很簡單,就一行程式碼的事:
 @Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
//        canvas.drawCircle(200,200,160,mPaint);
canvas.drawRoundRect(100,100,400,300,15,15,mPaint);
}
效果:


上面二種做法都有問題,我們知道顯示一個圓,其實是一個矩形的內切圓,如果這個圖片不是正方形是長方形,那麼這個內切圓肯定是有問題的,認真看下上面幾個圓形的頭像就會發現問題,在這先把上面遺留的渲染模式講下:

我繪製圓以後再使用這個畫筆繪製一個矩形

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
canvas.drawCircle(200,200,160,mPaint);
canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
}
設定圓形圖片的資源:


這是整張圖,在繪製了一個400*400的矩形後效果如下:


沒繪製矩形之前效果:


現在把這二個圖片放在一起對比:


還有二種模式,這樣吧,把圖片設定成圓形的不太好看效果,就設定成長方形吧,

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
canvas.drawRect(100,100,300,300,mPaint);
canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
}
bitmapShaderp = new BitmapShader(bitmap, Shader.TileMode.MIRROR,Shader.TileMode.MIRROR);
模式改成了MIRROR,效果如下:


MIRROR其實就是我們生活中照鏡子一樣,這個很好理解:

現在把x軸方向設定成重複的模式

bitmapShaderp = new BitmapShader(bitmap, Shader.TileMode.REPEAT,Shader.TileMode.MIRROR);
效果:


通過上面的例子把三種模式都講了下,現在把上面說到的如果圖片不是正方形的問題解決下,解決方案有二種,一種是是使用最小的邊把圖片切割,一種是放大

方案一:

解決思路:以圖片最小的邊去切一個正方形,比如這張圖:


那麼現在開始按照這個思路解決剛才問題

private Bitmap getBitmap() {
    Canvas canvas = new Canvas();
Bitmap tempBp = null;
Paint paint = new Paint();
    if(width>height){
        tempBp = Bitmap.createBitmap(bitmap,(width-height)/2,0,height,height);
}else if(width<height){
        tempBp = Bitmap.createBitmap(bitmap,0,(height-width)/2,width,width);
}else{//等於
tempBp = Bitmap.createBitmap(bitmap,0,0,height,height);
}
    canvas.drawBitmap(tempBp,10,10,paint);
    return  tempBp;
}
這個就是得到在原來的圖片上切一個矩形出來,使用了Bitmap的一個靜態函式
public static android.graphics.Bitmap createBitmap(android.graphics.Bitmap source, int x, int y, int width, int height)
第一個引數 source:就是在那個圖片上進行切

第二個引數在以圖片為座標軸 x軸方向的值

第三個引數:同理第二個引數

第四個引數:新圖片的寬度

第五個引數:新圖片的高度

如圖:


同理寬>高的情況下,相信也可以算出(x,y)座標

整個程式碼如下:

package com.bitmapshaderdemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/11/22.
 */
public class RoundHeadView extends View {
    private static final String TAG ="RoundHeadView" ;
    private Paint mPaint;
    private BitmapShader bitmapShaderp;
    private int width;//圖片的寬度
private int height;//圖片的高度
private Bitmap bitmap;
    public RoundHeadView(Context context) {
        this(context,null);
}
    public RoundHeadView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
}
    public RoundHeadView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
initPaint();
init();
}
    private void init() {
        bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.gird);
width = bitmap.getWidth();
height = bitmap.getHeight();
bitmapShaderp = new BitmapShader(getBitmap(), Shader.TileMode.REPEAT,Shader.TileMode.MIRROR);
mPaint.setShader(bitmapShaderp);
}
    private void initPaint() {
        mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
}
    @Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
canvas.drawCircle(width/2,width/2,width/2,mPaint);
}

    private Bitmap getBitmap() {
        Canvas canvas = new Canvas();
Bitmap tempBp = null;
Paint paint = new Paint();
        if(width>height){
            tempBp = Bitmap.createBitmap(bitmap,(width-height)/2,0,height,height);
width = height;
}else if(width<height){
            tempBp = Bitmap.createBitmap(bitmap,0,(height-width)/2,width,width);
height = width;
}else{//等於
height = width;
tempBp = Bitmap.createBitmap(bitmap,0,0,height,height);
}
        canvas.drawBitmap(tempBp,0,0,paint);
        return  tempBp;
}
}
效果:

但是這個做法也有缺點就是如果使用者想看圖片上面的內容這個就個做法就不太好了,這也算是個需求

方案二:

週末了雖然這麼晚還是想把這部落格寫完,不然感覺總欠什麼,好了,廢話不多說,之前解決方案是縮小,而這方案是放大,是通過矩陣的方式放大,那麼放大比例是多少呢,是根據最小的邊框來計算,就是最大的邊框/最小的邊框=縮放比例,程式碼如下:

package com.bitmapshaderdemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
 * Created by admin on 2016/11/22.
 */
public class RoundHeadView extends View {
    private static final String TAG ="RoundHeadView" ;
    private Paint mPaint;
    private BitmapShader bitmapShaderp;
    private int width;//圖片的寬度
private int height;//圖片的高度
private Bitmap bitmap;
    public RoundHeadView(Context context) {
        this(context,null);
}
    public RoundHeadView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
}
    public RoundHeadView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
initPaint();
init();
}
    private void init() {
        bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.gird);
width = bitmap.getWidth();
height = bitmap.getHeight();
bitmapShaderp = new BitmapShader(bitmap, Shader.TileMode.REPEAT,Shader.TileMode.MIRROR);
mPaint.setShader(bitmapShaderp);
}
    private void initPaint() {
        mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
}
    @Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
//設定畫素矩陣,來調整大小,為了解決寬高不一致的問題。
float scale = Math.max(width, height)*1.0f/Math.min(width, height);
Matrix matrix = new Matrix();
matrix.setScale(scale, scale);//縮放比例
bitmapShaderp.setLocalMatrix(matrix);
canvas.drawCircle(Math.max(width, height)/2f, scale*Math.max(width, height)/2f, Math.max(width, height)/2f, mPaint);
}
}
效果:


現在實現放大鏡的功能

思路:其實將一張圖片放大,然後截圖這張大圖中的部分圖片,我先顯示一個普通的圖片

package com.bitmapshaderdemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/11/28.
 */
public class MirrorImageView extends View {
    private Bitmap bitmap;
    private Paint paint;
    public MirrorImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
paint = new Paint();
paint.setStrokeWidth(8);
bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ee);
}
    @Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
canvas.drawBitmap(bitmap,0,0,null);
}
}
效果:


然後建立以這個圖片寬和高度都乘以2,

package com.bitmapshaderdemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/11/28.
 */
public class MirrorImageView extends View {
    private Bitmap bitmap;
    private Paint paint;
    private int width;
    private int height;
    private Bitmap scaleBitmap;
    private int SCALE = 2;//縮放的倍數
public MirrorImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
paint = new Paint();
paint.setStrokeWidth(8);
bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ee);
width = bitmap.getWidth();
height = bitmap.getHeight();
scaleBitmap = Bitmap.createScaledBitmap(bitmap,width*SCALE,height*SCALE,true);
}
    @Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
canvas.drawBitmap(bitmap,0,0,null);
canvas.drawBitmap(scaleBitmap,0,0,null);
}
}
效果:


這是圖片寬和高都放大2倍後的效果,我現在在這個圖片上畫一個圓,那麼怎麼等同和圖片沒放大的時候位置一樣呢?在畫板上把2張圖片放在一起,然後畫個圓看看位置怎麼分析:


第二張放大2倍是用於放大鏡效果的