高級 UI 成長之路 (四) Paint 渲染/濾鏡/xfermode 使用

前言

在前面三篇文章中咱們學習了 「View 基礎」、「事件分發機制」、「自定義 View 入門和 View 工做流程」 那麼該篇文章將爲你們帶來自定義 View 中一個必不可少的類 Paint 畫筆。有了它可讓咱們繪製出來的圖形更加絢麗。該篇主要講解 Paint 高級使用,基礎部分我就大概過一下 API。java

Paint API

// 重置Paint。
void reset();
// 是否抗鋸齒
void setAntiAlias(boolean aa);
// 設定是否使用圖像抖動處理,會使繪製出來的圖片顏色更加平滑和飽滿,圖像更加清晰 
void setDither(boolean dither);
// 設置線性文本
void setLinearText(boolean linearText);
// 設置該項爲true,將有助於文本在LCD屏幕上的顯示效果 
void setSubpixelText(boolean subpixelText);
// 設置下劃線
void setUnderlineText(boolean underlineText);
// 設置帶有刪除線的效果 
void setStrikeThruText(boolean strikeThruText);
// 設置僞粗體文本,設置在小字體上效果會很是差 
void setFakeBoldText(boolean fakeBoldText);
// 若是該項設置爲true,則圖像在動畫進行中會濾掉對Bitmap圖像的優化操做
// 加快顯示速度,本設置項依賴於dither和xfermode的設置 
void setFilterBitmap(boolean filter);
// 設置畫筆風格,空心或者實心 FILL,FILL_OR_STROKE,或STROKE
// Paint.Style.STROKE 表示當前只繪製圖形的輪廓,而Paint.Style.FILL表示填充圖形。 
void setStyle(Style style);
// 設置顏色值
void setColor(int color);
// 設置透明圖0~255,要在setColor後面設置才生效
void setAlpha(int a);   
// 設置RGB及透明度
void setARGB(int a, int r, int g, int b);  
// 當畫筆樣式爲STROKE或FILL_OR_STROKE時,設置筆刷的粗細度 
void setStrokeWidth(float width);
void setStrokeMiter(float miter);
// 當畫筆樣式爲STROKE或FILL_OR_STROKE時,設置筆刷末端的圖形樣式
// 如圓形樣式Cap.ROUND,或方形樣式Cap.SQUARE 
void setStrokeCap(Cap cap);
// 設置繪製時各圖形的結合方式,如平滑效果等 
void setStrokeJoin(Join join);
// 設置圖像效果,使用Shader能夠繪製出各類漸變效果 
Shader setShader(Shader shader);
// 設置顏色過濾器,能夠在繪製顏色時實現不用顏色的變換效果 
ColorFilter setColorFilter(ColorFilter filter);
// 設置圖形重疊時的處理方式,如合併,取交集或並集,常常用來製做橡皮的擦除效果 
Xfermode setXfermode(Xfermode xfermode);
// 設置繪製路徑的效果,如點畫線等 
PathEffect setPathEffect(PathEffect effect);
// 設置MaskFilter,能夠用不一樣的MaskFilter實現濾鏡的效果,如濾化,立體等 
MaskFilter setMaskFilter(MaskFilter maskfilter);
// 設置Typeface對象,即字體風格,包括粗體,斜體以及襯線體,非襯線體等 
Typeface setTypeface(Typeface typeface);
// 設置光柵化
Rasterizer setRasterizer(Rasterizer rasterizer);
// 在圖形下面設置陰影層,產生陰影效果,radius爲陰影的角度,dx和dy爲陰影在x軸和y軸上的距離,color爲陰影的顏色
// 注意:在Android4.0以上默認開啓硬件加速,有些圖形的陰影沒法顯示。關閉View的硬件加速 view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
void setShadowLayer(float radius, float dx, float dy, int color);
// 設置文本對齊
void setTextAlign(Align align);
// 設置字體大小
void setTextSize(float textSize);
// 設置文本縮放倍數,1.0f爲原始
void setTextScaleX(float scaleX);
// 設置斜體文字,skewX爲傾斜弧度 
void setTextSkewX(float skewX);

複製代碼

Paint 高級使用

渲染

1. 繪製圖像陰影效果 setShadowLayergit

val paint = Paint()
        paint.setStyle(Paint.Style.FILL)
        paint.setColor(Color.BLACK)
        // 設置透明度,要在setColor後面設置才生效
        paint.setAlpha(80)
         // 若是不關閉硬件加速,setShadowLayer無效
        setLayerType(View.LAYER_TYPE_SOFTWARE, null)
        // (陰影的半徑,X軸方向上相對主體的位移,Y軸相對位移)
        paint.setShadowLayer(50f, 10f, 10f, Color.RED)
        paint.setTextSize(50f)

        // cx和cy爲圓點的座標
        val radius = 200
        val offest = 40

        val startX = width / 2 - radius
        val startY = height / 2
        canvas.drawText("畫一個圓", width / 2 - 100f, height / 2f - 300, paint)
        canvas.drawCircle(startX.toFloat(), startY.toFloat(), radius.toFloat(), paint)

        paint.setStyle(Paint.Style.STROKE)
        paint.setStrokeWidth(5f)
        paint.setShadowLayer(50f, -20f, 10f, Color.RED)
        canvas.drawCircle(startX + radius * 2 + offest.toFloat(), startY.toFloat(), radius.toFloat(), paint)
複製代碼

2. 爲 Bitmap 設置圖形渲染 BitmapShadergithub

class MyGradientView : View {
    private var mPaint: Paint? = null
    private var mBitMap: Bitmap? = null
    private var mWidth: Int = 0
    private var mHeight: Int = 0
    private val mColors = intArrayOf(Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW)
    constructor(context: Context?) : super(context) {
        init()
    }

    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
        init()
    }
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    private fun init() {
        mBitMap = (resources.getDrawable(R.mipmap.girl_gaitubao) as BitmapDrawable).bitmap
        mPaint = Paint()
        mWidth = mBitMap!!.getWidth()
        mHeight = mBitMap!!.getHeight()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        /** * TileMode.CLAMP 拉伸最後一個像素去鋪滿剩下的地方 * TileMode.MIRROR 經過鏡像翻轉鋪滿剩下的地方。 * TileMode.REPEAT 重複圖片平鋪整個畫面(電腦設置壁紙) * 在圖片和顯示區域大小不符的狀況進行擴充渲染 */
        /** * 位圖渲染,BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY) * Bitmap:構造shader使用的bitmap * tileX:X軸方向的TileMode * tileY:Y軸方向的TileMode */
        val bitMapShader = BitmapShader(
            mBitMap!!, Shader.TileMode.MIRROR,
            Shader.TileMode.MIRROR
        )
        //設置圖片效果
        mPaint!!.setShader(bitMapShader)
        //抗鋸齒
        mPaint!!.setAntiAlias(true)
        //繪製圓
        canvas.drawCircle(width/2f,height/2f,mHeight.toFloat(),mPaint!!)
    }
}
複製代碼

參數的意思我註釋很詳細,我就不在過多說明了。shell

3. 線性渲染 LinearGradientcanvas

/**線性渲染 * x0, y0, 起始點 * x1, y1, 結束點 * int[] mColors, 中間依次要出現的幾個顏色 * float[] positions 位置數組,position的取值範圍[0,1],做用是指定幾個顏色分別放置在那個位置上, * 若是傳null,漸變就線性變化。 * tile 用於指定控件區域大於指定的漸變區域時,空白區域的顏色填充方法 */  
			var linearGradient = LinearGradient(
            0f, 0f, 800f, 800f,
            mColors, null, Shader.TileMode.CLAMP
        )
// var linearGradient = LinearGradient(0f, 0f, 400f, 400f, mColors, null, Shader.TileMode.REPEAT)
        mPaint!!.setShader(linearGradient)
        canvas.drawRect(0f, 0f, 800f, 800f, mPaint!!)
複製代碼

4. 環形渲染 RadialGradient數組

/** * * 環形渲染 * centerX ,centerY:shader的中心座標,開始漸變的座標 * radius:漸變的半徑 * centerColor,edgeColor:中心點漸變顏色,邊界的漸變顏色 * colors:漸變顏色數組 * stops:漸變位置數組,相似掃描漸變的positions數組,取值[0,1],中心點爲0,半徑到達位置爲1.0f * tileMode:shader未覆蓋之外的填充模式 */
        val mRadialGradient = RadialGradient(
            width/2f,height/2f,width/3.toFloat(),
            mColors, null, Shader.TileMode.REPEAT
        )
        mPaint!!.setShader(mRadialGradient)
        canvas.drawCircle(width/2f,height/2f,width/3.toFloat(), mPaint!!)
複製代碼

5. 掃描渲染 SweepGradientide

/** * 掃描渲染 * cx,cy 漸變中心座標 * color0,color1:漸變開始結束顏色 * colors,positions:相似LinearGradient,用於多顏色漸變,positions爲null時,根據顏色線性漸變 */
        val mSweepGradient = SweepGradient(width/2f,height/2f, mColors, null)
        mPaint!!.setShader(mSweepGradient)
        canvas.drawCircle(width/2f,height/2f,width/3.toFloat(), mPaint!!)
複製代碼

6. 組合渲染 ComposeShaderpost

/** * 組合渲染, * ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, Xfermode mode) * ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, PorterDuff.Mode mode) * shaderA,shaderB:要混合的兩種shader * Xfermode mode: 組合兩種shader顏色的模式 * PorterDuff.Mode mode: 組合兩種shader顏色的模式 */
        val bitMapShader = BitmapShader(
            mBitMap!!, Shader.TileMode.REPEAT,
            Shader.TileMode.REPEAT
        )
        val linearGradient = LinearGradient(
            0f, 0f, 800f, 800f,
            mColors, null, Shader.TileMode.CLAMP
        )
        val mComposeShader = ComposeShader(linearGradient, bitMapShader, PorterDuff.Mode.SRC_OVER)
        mPaint!!.setShader(mComposeShader)
        canvas.drawRect(0f, 0f, 800f, 1000f, mPaint!!)
複製代碼

7. 繪製心型 ComposeShader學習

//建立BitmapShader,用以繪製心
        val mBitmap = (resources.getDrawable(R.mipmap.heart) as BitmapDrawable).bitmap
        val bitmapShader = BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
        //建立LinearGradient,用以產生從左上角到右下角的顏色漸變效果
        val linearGradient = LinearGradient(
            0f, 0f, mWidth.toFloat(), mHeight.toFloat(),
            Color.BLUE, Color.RED, Shader.TileMode.CLAMP
        )
        //bitmapShader對應目標像素,linearGradient對應源像素,像素顏色混合採用MULTIPLY模式
        val composeShader = ComposeShader(linearGradient, bitmapShader, PorterDuff.Mode.MULTIPLY)
// ComposeShader composeShader2 = new ComposeShader(composeShader, linearGradient, PorterDuff.Mode.MULTIPLY);
        //將組合的composeShader做爲畫筆paint繪圖所使用的shader
        mPaint!!.setShader(composeShader)
        //用composeShader繪製矩形區域
        canvas.drawRect(0f, 0f, mBitmap.width.toFloat(), mBitmap.height.toFloat(), mPaint!!)
        //所謂渲染就是對於咱們繪製區域進行按照上訴渲染規則進行色彩的填充
複製代碼

9. 線性渲染-字體漸變 LinearGradient字體

class LinearGradientTextView : TextView {

    /** * 定義線性漸變 */
    private var mLinearGradient: LinearGradient? = null

    /** * 定義一個矩陣 */
    private var mGradientatrix: Matrix? = null

    /** * 定義一個畫筆 */
    private var mPaint: Paint? = null

    private var mViewWidth = 0
    private var mTranslate = 0

    private var delta = 15



    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)


    /** * 當字改變的時候回調 */
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        if (mViewWidth ==0){
            //拿到當前 text 寬度
            mViewWidth = measuredWidth
            if (mViewWidth > 0){
                //拿到當前畫筆
                mPaint = paint
                //拿到 text
                var text = text.toString()
                //mViewWidth除字體總數就獲得了每一個字的像素 而後*3 表示3個文字的像素
                var size = 0;
                //若是當前 text 長度大於 0
                if (text.length > 0){
                    //拿到當前 3 個文字的像素
                    size = mViewWidth / text.length * 3

                }else{//說明沒有文字
                    size = mViewWidth
                }
                /**線性渲染 * x0, y0, 起始點 * x1, y1, 結束點 * int[] mColors, 中間依次要出現的幾個顏色 * float[] positions 位置數組,position的取值範圍[0,1],做用是指定幾個顏色分別放置在那個位置上, * 若是傳null,漸變就線性變化。 * tile 用於指定控件區域大於指定的漸變區域時,空白區域的顏色填充方法 */
                //從左邊 size 開始,左邊看不見的地方開始,以滾動掃描的形式過來
                mLinearGradient = LinearGradient(-size.toFloat(),0f,0f,0f, intArrayOf(0x33ffffff, -0x1, 0x33ffffff),
                    floatArrayOf(0f, 0.2f, 1f), Shader.TileMode.CLAMP)
                //將線性漸變添加到 paint 中
                mPaint!!.setShader(mLinearGradient)
                //定義一個矩陣
                mGradientatrix = Matrix()
            }
        }
    }

    /** * 開始繪製 */
    override fun draw(canvas: Canvas?) {
        super.draw(canvas)
        val measureWindth = paint.measureText(text.toString())
        mTranslate += delta
        /** * 若是位置已經移動到了邊界,那麼文字就開始往回滾動 * 可是若是小於 1 那麼又開始遞增,執行另外一個邏輯 */
        if (mTranslate > measureWindth + 1 || mTranslate < 1){
            delta = -delta
        }

        //將矩陣平移
        mGradientatrix!!.setTranslate(mTranslate.toFloat(),0f)
        mLinearGradient!!.setLocalMatrix(mGradientatrix)
        //paint是textview的因此只須要不斷色控制畫筆的shader 而後利用矩陣控制位移便可
        postInvalidateDelayed(30)

    }
}
複製代碼

10. 雷達掃描 SweepGradient

/** * <pre> * author : devyk on 2019-11-30 18:50 * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts * github : https://github.com/yangkun19921001 * mailbox : yang1001yk@gmail.com * desc : This is RadarGradientView 漸變渲染/梯度渲染 * </pre> */
class RadarGradientView : View {


    private var mWidth: Int = 0
    private var mHeight: Int = 0

    private val    TAG = javaClass.simpleName

    //五個圓
    private val pots = floatArrayOf(0.05f, 0.1f, 0.15f, 0.2f, 0.25f, 0.3f, 0.35f)

    private var scanShader: Shader? = null // 掃描渲染shader
    private val scanSpeed = 10 // 掃描速度
    private var scanAngle: Int = 0 // 掃描旋轉的角度

    private lateinit var mMatrix: Matrix // 旋轉須要的矩陣

    private  var mPaintCircle = Paint() // 畫圓用到的paint
    private  var mPaintRadar = Paint() // 掃描用到的paint

    private val run = object : Runnable {
        override fun run() {
            scanAngle = (scanAngle + scanSpeed) % 125 //
            Log.d(TAG,"scanAngle:$scanAngle")
            mMatrix.postRotate(scanSpeed.toFloat(), (mWidth / 2).toFloat(), (mHeight / 2).toFloat()) // 旋轉矩陣
            invalidate() // 通知view重繪
            postDelayed(this, 50) // 調用自身 重複繪製
        }
    }

    constructor(context: Context) : super(context) {
        init()
    }

    private fun init() {
        mMatrix = Matrix()
        // 畫圓用到的paint
        mPaintCircle = Paint()
        mPaintCircle.style = Paint.Style.STROKE // 描邊
        mPaintCircle.strokeWidth = 1f // 寬度
        mPaintCircle.alpha = 100 // 透明度
        mPaintCircle.isAntiAlias = true // 抗鋸齒
        mPaintCircle.color = Color.parseColor("#B0C4DE") // 設置顏色 亮鋼蘭色

        // 掃描用到的paint
        mPaintRadar = Paint()
        mPaintRadar.style = Paint.Style.FILL_AND_STROKE // 填充
        mPaintRadar.isAntiAlias = true // 抗鋸齒


        post(run)
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}


    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        Log.d(TAG,"onDraw()")
        for (i in pots.indices) {
            canvas.drawCircle((mWidth / 2).toFloat(), (mHeight / 2).toFloat(), mWidth * pots[i], mPaintCircle)
        }

        // 畫布的旋轉變換 須要調用save() 和 restore()
        canvas.save()

        scanShader = SweepGradient(
            (mWidth / 2).toFloat(), (mHeight / 2).toFloat(),
            intArrayOf(Color.TRANSPARENT, Color.parseColor("#84B5CA")), null
        )
        mPaintRadar.shader = scanShader // 設置着色器
        canvas.concat(mMatrix)
        canvas.drawCircle((mWidth / 2).toFloat(), (mHeight / 2).toFloat(), mWidth * pots[6], mPaintRadar)

        canvas.restore()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        Log.d(TAG,"onMeasure()")
        // 取屏幕的寬高是爲了把雷達放在屏幕的中間
        mWidth = measuredWidth
        mHeight = measuredHeight
        mHeight = Math.min(mWidth, mHeight)
        mWidth = mHeight
    }

}
複製代碼

11. 放大鏡 BitmapShader

class ZoomImageView : View {
    // 原圖
    private val mBitmap: Bitmap
    // 放大後的圖
    private var mBitmapScale: Bitmap? = null
    // 製做的圓形的圖片(放大的局部),蓋在Canvas上面
    private val mShapeDrawable: ShapeDrawable

    private val mMatrix: Matrix

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    init {

        mBitmap = BitmapFactory.decodeResource(resources, R.mipmap.gild_3)
        mBitmapScale = mBitmap
        //放大後的整個圖片
        mBitmapScale = Bitmap.createScaledBitmap(
            mBitmapScale!!, mBitmapScale!!.width * FACTOR,
            mBitmapScale!!.height * FACTOR, true
        )
        val bitmapShader = BitmapShader(
            mBitmapScale!!, Shader.TileMode.CLAMP,
            Shader.TileMode.CLAMP
        )

        mShapeDrawable = ShapeDrawable(OvalShape())
        mShapeDrawable.paint.shader = bitmapShader
        // 切出矩形區域,用來畫圓(內切圓)
        mShapeDrawable.setBounds(0, 0, RADIUS * 2, RADIUS * 2)

        mMatrix = Matrix()
    }




    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)


        // 一、畫原圖
        canvas.drawBitmap(mBitmap, 0f, 0f, null)

        // 二、畫放大鏡的圖
        mShapeDrawable.draw(canvas)
    }


    override fun onTouchEvent(event: MotionEvent): Boolean {
        val x = event.x.toInt()
        val y = event.y.toInt() - RADIUS

        Log.d("onTouchEvent", "x:" + x + "y:" + y)

        // 將放大的圖片往相反的方向挪動
        mMatrix.setTranslate((RADIUS - x * FACTOR).toFloat(), (RADIUS - y * FACTOR).toFloat())
        mShapeDrawable.paint.shader.setLocalMatrix(mMatrix)
        // 切出手勢區域點位置的圓
        mShapeDrawable.setBounds(x - RADIUS, y - RADIUS, x + RADIUS, y + RADIUS)
// invalidate()
        postInvalidate()
        return true
    }

    companion object {

        //放大倍數
        private val FACTOR = 3
        //放大鏡的半徑
        private val RADIUS = 300
    }
}
複製代碼

濾鏡

//平移運算---加法
ColorMatrix colorMartrix = new ColorMatrix(new float[]{
                        1, 0,0,0,0,
                        0,1,0,0,100,
                        0,0,1,0,0,
                        0,0,0,1,0,
                });

//反相效果 -- 底片效果
ColorMatrix colorMartrix = new ColorMatrix(new float[]{
                        -1, 0,0,0,255,
                        0,-1,0,0,255,
                        0,0,-1,0,255,
                        0,0,0,1,0,
                });
//縮放運算---乘法 -- 顏色加強
ColorMatrix colorMartrix = new ColorMatrix(new float[]{
                        1.2f, 0,0,0,0,
                        0,1.2f,0,0,0,
                        0,0,1.2f,0,0,
                        0,0,0,1.2f,0,
});
        /** 黑白照片 * 是將咱們的三通道變爲單通道的灰度模式 * 去色原理:只要把R G B 三通道的色彩信息設置成同樣,那麼圖像就會變成灰色, * 同時爲了保證圖像亮度不變,同一個通道里的R+G+B =1 */
ColorMatrix colorMartrix = new ColorMatrix(new float[]{
                        0.213f, 0.715f,0.072f,0,0,
                        0.213f, 0.715f,0.072f,0,0,
                        0.213f, 0.715f,0.072f,0,0,
                        0,0,0,1,0,
                });


//髮色效果---(好比紅色和綠色交換)
ColorMatrix colorMartrix = new ColorMatrix(new float[]{
                        1,0,0,0,0,
                        0, 0,1,0,0,
                        0,1,0,0,0,
                        0,0,0,0.5F,0,
                });
//復古效果
ColorMatrix colorMartrix = new ColorMatrix(new float[]{
                        1/2f,1/2f,1/2f,0,0,
                        1/3f, 1/3f,1/3f,0,0,
                        1/4f,1/4f,1/4f,0,0,
                        0,0,0,1,0,
});
複製代碼
class FilterView : View {

    private var paint = Paint()

    lateinit var bitmap: Bitmap

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    /** * 顯示的高 */
    var showHeight = 0

    init {

        init()
    }

    private fun init() {

        paint = Paint(Paint.ANTI_ALIAS_FLAG)
        paint.color = Color.RED

        bitmap = BitmapFactory.decodeResource(resources, R.mipmap.gild_3)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
// //關閉單個View的硬件加速功
// // setLayerType(View.LAYER_TYPE_SOFTWARE,null);




        //1. 縮放運算---乘法 -- 顏色加強
        val colorMartrix = ColorMatrix(
            floatArrayOf(
                1.2f, 0f, 0f, 0f, 0f,
                0f, 1.2f, 0f, 0f, 0f,
                0f, 0f, 1.2f, 0f, 0f,
                0f, 0f, 0f, 1.2f, 0f
            )
        )
        val rectF = RectF(
                0f,
            showHeight.toFloat() ,
                (bitmap.width / 2).toFloat(),
                (bitmap.height / 4).toFloat()
            )
        drawFilterBitmap(colorMartrix, canvas,rectF)

        showHeight += bitmap.height / 4



        //2 平移運算---加法
     var colorMartrix2 = ColorMatrix(floatArrayOf(
                        1f, 0f,0f,0f,0f,
                        0f,1f,0f,0f,100f,
                        0f,0f,1f,0f,0f,
                        0f,0f,0f,1f,0f
                    ))

        val rectF2 = RectF(
            0f,
            showHeight.toFloat(),
            (bitmap.width / 2).toFloat(),
            (bitmap.height /4) * 2.toFloat()
        )
        drawFilterBitmap(colorMartrix2, canvas,rectF2)


        showHeight += bitmap.height / 4



        //3. 反相效果 -- 底片效果
               var colorMartrix3 =  ColorMatrix(floatArrayOf(
                        -1f, 0f,0f,0f,255f,
                        0f,-1f,0f,0f,255f,
                        0f,0f,-1f,0f,255f,
                        0f,0f,0f,1f,0f
                   ));

        val rectF3 = RectF(
            0f,
            showHeight.toFloat(),
            (bitmap.width / 2).toFloat(),
            (bitmap.height /4) * 3.toFloat()
        )
        drawFilterBitmap(colorMartrix3, canvas,rectF3)


        /** * 4.黑白照片 * 是將咱們的三通道變爲單通道的灰度模式 * 去色原理:只要把R G B 三通道的色彩信息設置成同樣,那麼圖像就會變成灰色, * 同時爲了保證圖像亮度不變,同一個通道里的R+G+B =1 */
                var colorMartrix4 =  ColorMatrix(floatArrayOf(
                        0.213f, 0.715f,0.072f,0f,0f,
                        0.213f, 0.715f,0.072f,0f,0f,
                        0.213f, 0.715f,0.072f,0f,0f,
                        0f,0f,0f,1f,0f
            ));


        showHeight += bitmap.height / 4
        val rectF4 = RectF(
            bitmap.width/2f,
            bitmap.height /2f,
            (bitmap.width).toFloat(),
            (bitmap.height /4) * 3.toFloat()
        )
        drawFilterBitmap(colorMartrix4, canvas,rectF4)



         //5.髮色效果---(好比紅色和綠色交換)
                var colorMartrix5 =  ColorMatrix(floatArrayOf(
                        1f,0f,0f,0f,0f,
                        0f, 0f,1f,0f,0f,
                        0f,1f,0f,0f,0f,
                        0f,0f,0f,0.5F,0f
                    ));

        val rectF5 = RectF(
            bitmap.width / 2f,
            0f,
            (bitmap.width / 2 * 2).toFloat(),
            (bitmap.height /4) .toFloat()
        )
        drawFilterBitmap(colorMartrix5, canvas,rectF5)



        //6.復古效果
       var colorMartrix6=  ColorMatrix(floatArrayOf(
                        1/2f,1/2f,1/2f,0f,0f,
                        1/3f, 1/3f,1/3f,0f,0f,
                        1/4f,1/4f,1/4f,0f,0f,
                        0f,0f,0f,1f,0f
                    ));

        val rectF6 = RectF(
            bitmap.width / 2f,
            bitmap.height /4f,
            (bitmap.width / 2 * 2).toFloat(),
            (bitmap.height /4 * 2) .toFloat()
        )
        drawFilterBitmap(colorMartrix6, canvas,rectF6)



    }

    private fun drawFilterBitmap(colorMartrix: ColorMatrix, canvas: Canvas,rectF: RectF) {

        paint.colorFilter = ColorMatrixColorFilter(colorMartrix)
        canvas.drawBitmap(bitmap, null, rectF, paint)
    }


}
複製代碼

xfermode

private static final Xfermode[] sModes = {
        new PorterDuffXfermode(PorterDuff.Mode.CLEAR),      // 清空全部,要閉硬件加速,不然無效
        new PorterDuffXfermode(PorterDuff.Mode.SRC),        // 顯示前都圖像,不顯示後者
        new PorterDuffXfermode(PorterDuff.Mode.DST),        // 顯示後者圖像,不顯示前者
        new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),   // 後者疊於前者
        new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),   // 前者疊於後者
        new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),     // 顯示相交的區域,但圖像爲後者
        new PorterDuffXfermode(PorterDuff.Mode.DST_IN),     // 顯示相交的區域,但圖像爲前者
        new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),    // 顯示後者不重疊的圖像
        new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),    // 顯示前者不重疊的圖像
        new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),   // 顯示前者圖像,與後者重合的圖像
        new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),   // 顯示後者圖像,與前者重合的圖像
        new PorterDuffXfermode(PorterDuff.Mode.XOR),        // 顯示持有不重合的圖像
        new PorterDuffXfermode(PorterDuff.Mode.DARKEN),     // 後者疊於前者上,後者與前者重疊的部份透明。要閉硬件加速,不然無效
        new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),    // 前者疊於前者,前者與後者重疊部份透明。要閉硬件加速,不然無效
        new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),   // 顯示重合的圖像,且顏色會合拼
        new PorterDuffXfermode(PorterDuff.Mode.SCREEN)    // 顯示持有圖像,重合的會變白
    };
複製代碼

class XfermodeView : View {

    lateinit var mPaint: Paint
    internal var mItemSize = 0f
    internal var mItemHorizontalOffset = 0f
    internal var mItemVerticalOffset = 0f
    internal var mCircleRadius = 0f
    internal var mRectSize = 0f
    internal var mCircleColor = -0x33bc//黃色
    internal var mRectColor = -0x995501//藍色
    internal var mTextSize = 25f

    constructor(context: Context) : super(context) {
        init(null, 0)
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        init(attrs, 0)
    }

    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
        init(attrs, defStyle)
    }

    private fun init(attrs: AttributeSet?, defStyle: Int) {
        if (Build.VERSION.SDK_INT >= 11) {
            setLayerType(View.LAYER_TYPE_SOFTWARE, null)
        }
        mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        mPaint.textSize = mTextSize
        mPaint.textAlign = Paint.Align.CENTER
        mPaint.strokeWidth = 2f
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //設置背景色
        // canvas.drawARGB(255, 139, 197, 186);

        val canvasWidth = canvas.width
        val canvasHeight = canvas.height

        for (row in 0..3) {
            for (column in 0..3) {
                canvas.save()
                val layer =
                    canvas.saveLayer(0f, 0f, canvasWidth.toFloat(), canvasHeight.toFloat(), null, Canvas.ALL_SAVE_FLAG)
                mPaint.xfermode = null
                val index = row * 4 + column
                val translateX = (mItemSize + mItemHorizontalOffset) * column
                val translateY = (mItemSize + mItemVerticalOffset) * row
                canvas.translate(translateX, translateY)
                //畫文字
                val text = sLabels[index]
                mPaint.color = Color.BLACK
                val textXOffset = mItemSize / 2
                val textYOffset = mTextSize + (mItemVerticalOffset - mTextSize) / 2
                canvas.drawText(text, textXOffset, textYOffset, mPaint)
                canvas.translate(0f, mItemVerticalOffset)
                //畫邊框
                mPaint.style = Paint.Style.STROKE
                mPaint.color = -0x1000000
                canvas.drawRect(2f, 2f, mItemSize - 2, mItemSize - 2, mPaint)
                mPaint.style = Paint.Style.FILL
                //畫圓
                mPaint.color = mCircleColor
                val left = mCircleRadius + 3
                val top = mCircleRadius + 3
                canvas.drawCircle(left, top, mCircleRadius, mPaint)
                mPaint.xfermode = sModes[index]
                //畫矩形
                mPaint.color = mRectColor
                val rectRight = mCircleRadius + mRectSize
                val rectBottom = mCircleRadius + mRectSize
                canvas.drawRect(left, top, rectRight, rectBottom, mPaint)
                mPaint.xfermode = null
                //canvas.restore();
                canvas.restoreToCount(layer)
            }
        }
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mItemSize = w / 4.5f
        mItemHorizontalOffset = mItemSize / 6
        mItemVerticalOffset = mItemSize * 0.426f
        mCircleRadius = mItemSize / 3
        mRectSize = mItemSize * 0.6f
    }

    companion object {

        private val sModes = arrayOf<Xfermode>(
            PorterDuffXfermode(PorterDuff.Mode.CLEAR),
            PorterDuffXfermode(PorterDuff.Mode.SRC),
            PorterDuffXfermode(
                PorterDuff.Mode.DST
            ),
            PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),
            PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
            PorterDuffXfermode(
                PorterDuff.Mode.SRC_IN
            ),
            PorterDuffXfermode(PorterDuff.Mode.DST_IN),
            PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),
            PorterDuffXfermode(
                PorterDuff.Mode.DST_OUT
            ),
            PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
            PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
            PorterDuffXfermode(
                PorterDuff.Mode.XOR
            ),
            PorterDuffXfermode(PorterDuff.Mode.DARKEN),
            PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
            PorterDuffXfermode(
                PorterDuff.Mode.MULTIPLY
            ),
            PorterDuffXfermode(PorterDuff.Mode.SCREEN)
        )

        private val sLabels = arrayOf(
            "Clear",
            "Src",
            "Dst",
            "SrcOver",
            "DstOver",
            "SrcIn",
            "DstIn",
            "SrcOut",
            "DstOut",
            "SrcATop",
            "DstATop",
            "Xor",
            "Darken",
            "Lighten",
            "Multiply",
            "Screen"
        )
    }
}
複製代碼

1. 畫圓角 PorterDuff.Mode.DST_IN

public class RoudImageView : View {

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
        init()
    }
    var mBitPaint =  Paint()
    private lateinit var mBmpDST: Bitmap
    private lateinit var mBmpSRC: Bitmap
    private fun init() {
        setLayerType(View.LAYER_TYPE_SOFTWARE, null)
        mBmpDST = changeBitmapSize(R.mipmap.gild_3)
        mBmpSRC = changeBitmapSize(R.drawable.shade)
    }

    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)


    override fun draw(canvas: Canvas) {
        super.draw(canvas)
        val saveLayer = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG)
        canvas.drawBitmap(mBmpDST,0f,0f,mBitPaint)
        mBitPaint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_IN))
        canvas.drawBitmap(mBmpSRC,0f,0f,mBitPaint)

        mBitPaint.setXfermode(null)
        canvas.restoreToCount(saveLayer)
    }
    private fun changeBitmapSize(res: Int) : Bitmap {
        var bitmap = BitmapFactory.decodeResource(getResources(), res);
        var width = bitmap.getWidth();
        var height = bitmap.getHeight();
        Log.e("width","width:"+width);
        Log.e("height","height:"+height);
        //設置想要的大小
        var newWidth=500;
        var newHeight=500;

        //計算壓縮的比率
        var scaleWidth=(newWidth)/width .toFloat()
        var scaleHeight=(newHeight)/height.toFloat();

        //獲取想要縮放的matrix
        var matrix =  Matrix();
        matrix.postScale(scaleWidth,scaleHeight.toFloat());

        //獲取新的bitmap
        bitmap=Bitmap.createBitmap(bitmap,0,0,width,height,matrix,true);
        bitmap.getWidth();
        bitmap.getHeight();
        Log.e("newWidth","newWidth"+bitmap.getWidth());
        Log.e("newHeight","newHeight"+bitmap.getHeight());
        return bitmap;
    }

}
複製代碼

2. 倒影 PorterDuff.Mode.DST_IN

class InvertedImageView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private val mBitPaint: Paint
    private val BmpDST: Bitmap
    private val BmpSRC: Bitmap
    private val BmpRevert: Bitmap

    init {
        setLayerType(View.LAYER_TYPE_SOFTWARE, null)
        mBitPaint = Paint()
        BmpDST = BitmapUtis.changeBitmapSize(context,R.mipmap.gild_3)
        BmpSRC = BitmapUtis.changeBitmapSize(context,R.drawable.invert_shade)

        val matrix = Matrix()
        matrix.setScale(1f, -1f)
        // 生成倒影圖
        BmpRevert = Bitmap.createBitmap(BmpDST, 0, 0, BmpDST.width, BmpDST.height, matrix, true)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        canvas.drawColor(Color.BLACK)


        //先畫出原始圖片
        canvas.drawBitmap(BmpDST, 0f, 0f, mBitPaint)

        //再畫出倒影
        val layerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG)
        canvas.translate(0f, BmpSRC.height.toFloat())

        canvas.drawBitmap(BmpRevert, 0f, 0f, mBitPaint)
        mBitPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
        canvas.drawBitmap(BmpSRC, 0f, 0f, mBitPaint)

        mBitPaint.xfermode = null

        canvas.restoreToCount(layerId)
    }
}
複製代碼

4. 不規則波浪 PorterDuff.Mode.DST_IN

class IrregularWaveView(context: Context, attrs: AttributeSet) : View(context, attrs) {

    private val mPaint: Paint
    private var mItemWaveLength = 0
    private var dx = 0

    private val BmpSRC: Bitmap
    private val BmpDST: Bitmap

    init {
        mPaint = Paint()

        BmpDST = BitmapFactory.decodeResource(resources, R.drawable.wav, null)
        BmpSRC = BitmapFactory.decodeResource(resources, R.drawable.circle_shape, null)
        //不要讓它超出邊界
        mItemWaveLength = BmpDST.width - BmpSRC.width

        startAnim()
    }


    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        //先畫上圓形
        canvas.drawBitmap(BmpSRC, 0f, 0f, mPaint)

        //再畫上結果
        val layerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG)
        canvas.drawBitmap(
            BmpDST,
            Rect(dx, 0, dx + BmpSRC.width, BmpSRC.height),
            Rect(0, 0, BmpSRC.width, BmpSRC.height),
            mPaint
        )
        mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
        canvas.drawBitmap(BmpSRC, 0f, 0f, mPaint)
        mPaint.xfermode = null
        canvas.restoreToCount(layerId)


    }


    fun startAnim() {
        val animator = ValueAnimator.ofInt(0, mItemWaveLength)
        animator.duration = 2000
        animator.repeatCount = ValueAnimator.INFINITE
        animator.interpolator = LinearInterpolator()
        animator.addUpdateListener { animation ->
            dx = animation.animatedValue as Int
            postInvalidate()
        }
        animator.start()
    }
}
複製代碼

5.心電圖 PorterDuff.Mode.DST_IN

class HeartView(context: Context, attrs: AttributeSet) : View(context, attrs) {

    private val mPaint: Paint
    private var mItemWaveLength = 0
    private var dx = 0

    private val BmpSRC: Bitmap
    private val BmpDST: Bitmap

    init {

        mPaint = Paint()
        mPaint.color = Color.RED

        BmpDST = BitmapFactory.decodeResource(resources, R.drawable.heartmap, null)
        BmpSRC = Bitmap.createBitmap(BmpDST.width, BmpDST.height, Bitmap.Config.ARGB_8888)

        mItemWaveLength = BmpDST.width

        startAnim()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        val c = Canvas(BmpSRC)
        //清空bitmap
        c.drawColor(Color.RED, PorterDuff.Mode.CLEAR)
        Log.d("onDraw","左移動:${BmpDST.width - dx}");

        //畫上矩形
        c.drawRect((BmpDST.width - dx).toFloat(), 0f, BmpDST.width.toFloat(), BmpDST.height.toFloat(), mPaint)

        //模式合成
        val layerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG)
        canvas.drawBitmap(BmpDST, 0f, 0f, mPaint)
        mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
        canvas.drawBitmap(BmpSRC, 0f, 0f, mPaint)
        mPaint.xfermode = null
        canvas.restoreToCount(layerId)
    }


    fun startAnim() {
        val animator = ValueAnimator.ofInt(0, mItemWaveLength)
        animator.duration = 6000
        animator.repeatCount = ValueAnimator.INFINITE
        animator.interpolator = LinearInterpolator()
        animator.addUpdateListener { animation ->
            dx = animation.animatedValue as Int
            postInvalidate()
        }
        animator.start()
    }
}
複製代碼

6. PorterDuff.Mode.MULTIPLY

class TwitterView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private val mBitPaint: Paint
    private val BmpDST: Bitmap
    private val BmpSRC: Bitmap

    init {
        setLayerType(View.LAYER_TYPE_SOFTWARE, null)
        mBitPaint = Paint()
        //目標圖像
        BmpDST = BitmapFactory.decodeResource(resources, R.drawable.twiter_bg, null)
        //原圖像
        BmpSRC = BitmapFactory.decodeResource(resources, R.drawable.twiter_light, null)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        val layerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG)

        canvas.drawBitmap(BmpDST, 0f, 0f, mBitPaint)
        mBitPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)
        canvas.drawBitmap(BmpSRC, 0f, 0f, mBitPaint)

        mBitPaint.xfermode = null
        canvas.restoreToCount(layerId)
    }
}
複製代碼

7. PorterDuff.Mode.SRC_OUT

class GuaGuaCardView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private val mBitPaint: Paint
    private val BmpDST: Bitmap
    private val BmpSRC: Bitmap
    private val BmpText: Bitmap
    private val mPath: Path
    private var mPreX: Float = 0.toFloat()
    private var mPreY: Float = 0.toFloat()

    init {

        setLayerType(View.LAYER_TYPE_SOFTWARE, null)
        mBitPaint = Paint()
        mBitPaint.color = Color.RED
        mBitPaint.style = Paint.Style.STROKE
        mBitPaint.strokeWidth = 45f

        BmpText = BitmapFactory.decodeResource(resources, R.drawable.guaguaka_text1, null)
        BmpSRC = BitmapFactory.decodeResource(resources, R.drawable.guaguaka, null)
        BmpDST = Bitmap.createBitmap(BmpSRC.width, BmpSRC.height, Bitmap.Config.ARGB_8888)
        mPath = Path()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        canvas.drawBitmap(BmpText, 0f, 0f, mBitPaint)

        val layerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG)

        //先把手指軌跡畫到目標Bitmap上
        val c = Canvas(BmpDST)
        c.drawPath(mPath, mBitPaint)

        //而後把目標圖像畫到畫布上
        canvas.drawBitmap(BmpDST, 0f, 0f, mBitPaint)

        //計算源圖像區域
        mBitPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
        canvas.drawBitmap(BmpSRC, 0f, 0f, mBitPaint)

        mBitPaint.xfermode = null
        canvas.restoreToCount(layerId)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                mPath.moveTo(event.x, event.y)
                mPreX = event.x
                mPreY = event.y
                return true
            }
            MotionEvent.ACTION_MOVE -> {
                val endX = (mPreX + event.x) / 2
                val endY = (mPreY + event.y) / 2
                mPath.quadTo(mPreX, mPreY, endX, endY)
                mPreX = event.x
                mPreY = event.y
            }
            MotionEvent.ACTION_UP -> {
            }
        }
        postInvalidate()
        return super.onTouchEvent(event)
    }
}
複製代碼

總結

能夠發現 Paint 其實也能幹不少事兒,上面代碼已上傳至 GitHub

參考

相關文章
相關標籤/搜索