自定義控件繪制(Paint之Shader)篇十一

參考:

  1. https://blog.csdn.net/harvic880925/article/details/52039081

shader稱為著色器,用來給圖片上色用的;
Shader類只是一個基類,只有兩個方法setLocalMatrix(Matrix localM)getLocalMatrix(Matrix localM)用來設(shè)置坐標(biāo)變換矩陣的欣范;
Shader類與ColorFiler一樣,其實是一個空類弊仪,它的功能的實現(xiàn)熙卡,主要是靠它的派生類來實現(xiàn)的。

Shader子類

BitmapShader

構(gòu)造函數(shù):

public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY)

參數(shù):

  • bitmap: 用來指定圖案励饵;
  • tileX: 用來指定當(dāng)X軸超出單個圖片大小時時所使用的重復(fù)策略;
  • tileY: 同上驳癌,用于指定當(dāng)Y軸超出單個圖片大小時時所使用的重復(fù)策略;取值有:
    • TileMode.CLAMP:用邊緣色彩填充多余空間
    • TileMode.REPEAT:重復(fù)原圖像來填充多余空間
    • TileMode.MIRROR:重復(fù)使用鏡像模式的圖像來填充多余空間
val bmp = BitmapFactory.decodeResource(resources, R.mipmap.sanjing)
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    shader = BitmapShader(bmp, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
}
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    // 在矩形內(nèi)使用指定了shader的畫筆作畫
   canvas.drawRect(0f, 0f, width.toFloat(), height * 2 / 3.toFloat(), paint)
}
Repeat

如上圖,效果:
使用X軸和Y軸都使用REPEAT模式下役听,在超出單個圖像的區(qū)域后颓鲜,就會重復(fù)繪制這個圖像

Clamp

如上圖表窘,效果:
當(dāng)控件區(qū)域超過當(dāng)前單個圖片的大小時,空白位置的顏色填充就用圖片的邊緣顏色來填充甜滨;

要填充橫向和豎向時乐严,是先填充豎向的!上圖中的右下部分

mirror

如上圖衣摩,效果:
鏡相效果其實就是在顯示下一圖片的時候昂验,就相當(dāng)于兩張圖片中間放了一個鏡子一樣;

填充模式混用

Mirror與Repeat混用
// x軸上重復(fù)艾扮,y上鏡像
BitmapShader(bmp, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR)
如上效果圖既琴,注意:紅色線上下左右

無論哪兩種模式混合,我們在理解時只需要記著填充順序是先填充Y軸泡嘴,然后再填充X軸甫恩!這樣效果圖就很好理解了;

其他混用也很好理解;

繪圖位置與模式關(guān)系

上面的rect的設(shè)置酌予,都是比較大的磺箕,我們將rect縮小,看看效果

// 矩形小于圖片大小
canvas.drawRect(100f, 100f, 320f, 200f, paint)
image.png

如上圖抛虫,可以看到圖片松靡,像是從原圖片上裁剪了一塊,進(jìn)行了繪制建椰;

其實這正說明了一個問題:無論你利用繪圖函數(shù)繪多大一塊击困,在哪繪制,與Shader無關(guān)广凸。因為Shader總是在**控件的左上角**開始,而你繪制的部分只是顯示出來的部分而已蛛枚。沒有繪制的部分雖然已經(jīng)生成谅海,但只是不會顯示出來罷了。

望眼鏡效果

Paint設(shè)置了Shader以后蹦浦,無論我們繪圖位置在哪扭吁,Shader中的圖片都是從控件的左上角開始填充的,而我們所使用的繪圖函數(shù)只是用來指定哪部分顯示出來盲镶,所以當(dāng)我們在手指按下位置畫上一個圓形時侥袜,就會把圓形部分的圖像顯示出來了,看起來就是個望遠(yuǎn)鏡效果溉贿。

val bmp = BitmapFactory.decodeResource(resources, R.mipmap.animal_)
var bmpBG: Bitmap? = null

var dx = -1f
var dy = -1f

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    if (bmpBG == null) {
        // bitmap設(shè)置為控件寬高
        bmpBG = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        val canvasBG = Canvas(bmpBG)
        canvasBG.drawBitmap(bmp, null, RectF(0f, 0f, width * 1.0f, height * 1.0f), paint)
    }

    // 畫出局部
    if (dx != -1f && dy != -1f) {
        paint.shader = BitmapShader(bmpBG, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
        canvas.drawCircle(dx, dy, 150f, paint)
    }
}

override fun onTouchEvent(event: MotionEvent): Boolean {
    when (event.action) {
        MotionEvent.ACTION_DOWN,
        MotionEvent.ACTION_MOVE -> {
            dx = event.x
            dy = event.y
        }
        else -> {
            dx = -1f
            dy = -1f
        }
    }
    postInvalidate()
    return true
}
效果圖

BitmapShader生成不規(guī)則頭像

用 xfermode可以實現(xiàn)枫吧,這里采用BitmapShader來實現(xiàn);

val bmp = BitmapFactory.decodeResource(resources, R.mipmap.sanjing)

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    val shader = BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)

    val matrix = Matrix()
    val size = Math.min(bmp.width, bmp.height)
    val scale = size / Math.min(width, height).toFloat()
    matrix.setScale(scale, scale)  // Matrix 縮放
    shader.setLocalMatrix(matrix)

    paint.shader = shader
    canvas.drawCircle(size / 2.toFloat(), size / 2.toFloat(),
            size / 2.toFloat(), paint)
}

// 強(qiáng)制控件大小(正方形)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val size = Math.min(bmp.width, bmp.height)
    setMeasuredDimension(size, size)
}
shader實現(xiàn)圓角圖片

如果是其他形狀宇色,如五角星九杂,通過path來繪制即可颁湖;

LinearGradient線性漸變

構(gòu)造函數(shù)

/**
 * (x0, y0), (x1,y1)分別表示開始點與結(jié)束點
 * color0 起始點顏色,顏色值必須使用0xAARRGGBB形式的16進(jìn)制表示例隆!表示透明度的AA一定不能少甥捺;
 * color1 終點顏色
 * tile 填充模式
 **/
public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
           TileMode tile) 
/**
 * 基本與上類似
 * colors[]用于指定漸變的顏色值數(shù)組;
 * positions[]與漸變的顏色相對應(yīng)镀层,取值是0-1的float類型镰禾,表示在每一個顏色在整條漸 變線中的百分比位置 
 **/
public LinearGradient(float x0, float y0, float x1, float y1, int colors[],
           float positions[], TileMode tile) 

雙色漸變示例:

paint.shader = LinearGradient(0f, (height / 2).toFloat(), width.toFloat(),
            (height / 2).toFloat(), -0x10000, -0xff0100, Shader.TileMode.CLAMP)
canvas.drawRect(0f, 0f, width * 1.0f, height * 1.0f, paint)
雙色漸變

多色漸變示例:

val colors = intArrayOf(-0x10000, -0xff0100, -0xffff01, -0x100, -0xff0001)
val pos = floatArrayOf(0f, 0.2f, 0.4f, 0.6f, 1.0f)  // 20%,20%唱逢,20%吴侦,20%,40%
val multiGradient = LinearGradient(0f, (height / 2).toFloat(), width.toFloat(),
     (height / 2).toFloat(), colors, pos, Shader.TileMode.CLAMP)
paint.shader = multiGradient
canvas.drawRect(0f, 0f, width * 1.0f, height * 1.0f, paint)
image.png

??如上圖惶我,這里的 變線中的百分比位置不太理解

填充模式

上面都是 clamp邊緣填充妈倔,我們使用下repeat填充,漸變點是從(0,0)到屏幕的中間點(width/2,height.2):

val multiGradient = LinearGradient(0f, 0f, 
        width / 2.toFloat(), height / 2.toFloat(),
        colors, pos, Shader.TileMode.REPEAT)
repeat填充

Mirror很容易理解绸贡;

填充方式是什么盯蝴?

類似bitmapShader也是從控件左上角開始填充;

// 多個顏色
val colors = intArrayOf(-0x10000, -0xff0100, -0xffff01, -0x100, -0xff0001)
val pos = floatArrayOf(0f, 0.2f, 0.4f, 0.6f, 1.0f)  // 20%听怕,20%捧挺,20%,20%尿瞭,40%
val multiGradient = LinearGradient(0f, 0f, width / 2.toFloat(), height / 2.toFloat(), colors, pos, Shader.TileMode.REPEAT)
paint.shader = multiGradient
// 減小區(qū)域
canvas.drawRect(100f, 100f, 260f, 200f, paint)
類似切片

無論哪種Shader闽烙,都是從控件的左上角開始填充的,利用canvas.drawXXX系列函數(shù)只是用來指定顯示哪一塊

image.png
文字閃動效果声搁,請參考原博客黑竞,??
init {
    setLayerType(LAYER_TYPE_SOFTWARE, null)
}

private var shade: LinearGradient? = null
private var mDx = 0f
private var anim: ValueAnimator? = null

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    super.onLayout(changed, left, top, right, bottom)

    if (shade == null) {
        shade = LinearGradient((-measuredWidth).toFloat(), 0f, 0f, 0f,
                intArrayOf(currentTextColor, -0xff0100, currentTextColor),
                floatArrayOf(0f, 0.5f, 1f),
                Shader.TileMode.CLAMP
        )

        anim = ValueAnimator.ofFloat(0f, 2 * measuredWidth * 1.0f).apply {
            duration = 1500
            repeatMode = ValueAnimator.RESTART
            repeatCount = ValueAnimator.INFINITE
            addUpdateListener { it ->
                mDx = it.animatedValue as Float
                postInvalidate()
            }
        }
        anim?.start()
    }
}

override fun onDraw(canvas: Canvas?) {
    val matrix = Matrix()
    matrix.setTranslate(mDx, 0f)  // 設(shè)置偏移
    shade?.setLocalMatrix(matrix)
    paint.shader = shade
    super.onDraw(canvas)
}

override fun onDetachedFromWindow() {
    super.onDetachedFromWindow()
    anim?.let {
        it.cancel()
    }
}
效果圖

RadialGradient 放射漸變

構(gòu)造函數(shù):

// 雙色
public RadialGradient(float centerX, float centerY, float radius,
             int centerColor, int edgeColor,TileMode tileMode) 
// 多色
public RadialGradient(float centerX, float centerY, float radius,
             int colors[], float stops[],
             TileMode tileMode) {

參數(shù)說明:

  • centerX,Y: 漸變中心點;
  • radius: 漸變半徑疏旨;
  • centerColor:漸變起始顏色很魂,取值類型必須是八位的0xAARRGGBB色值!透明底Alpha值不能省略檐涝,不然不會顯示出顏色遏匆。
  • edgeColor:結(jié)束顏色,同上谁榜;
  • colors 與 stops 與 LinearGradient類似幅聘;stop數(shù)組的起始和終止數(shù)值設(shè)為0和1;

示例

    private var shade: RadialGradient? = null
    val paint = Paint().apply {}
    val radius = 400f

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        // 雙色
//        shade = RadialGradient(width / 2.toFloat(), height / 2.toFloat(),
//                100f, 0xffff0000.toInt(), 0xff00ff00.toInt(), Shader.TileMode.REPEAT)

        // 多色
        val colors = intArrayOf(-0x10000, -0xff0100, -0xffff01, -0x100)
        val stops = floatArrayOf(0f, 0.2f, 0.5f, 1f)
        shade = RadialGradient(width / 2.toFloat(), height / 2.toFloat(),
                radius, colors, stops, Shader.TileMode.REPEAT)

        paint.shader = shade
        canvas.drawCircle(width / 2.toFloat(), height / 2.toFloat(),
                radius, paint)
    }
多顏色
雙色

填充模式

比較好理解窃植;

填充方式

shader都是從控件的左上角開始填充的帝蒿;不信,就看

val colors = intArrayOf(-0x10000, -0xff0100, -0xffff01, -0x100)
val stops = floatArrayOf(0f, 0.2f, 0.5f, 1f)
shade = RadialGradient(width / 2.toFloat(), height / 2.toFloat(),
                100f, colors, stops, Shader.TileMode.REPEAT)
paint.shader = shade
canvas.drawCircle(200f, 200f,150f, paint)
image.png

水波紋按鈕效果

比較好理解巷怜,如下代碼:

init {
    setLayerType(LAYER_TYPE_SOFTWARE, null)
}

private var shade: RadialGradient? = null
val paint = Paint().apply {}
var currentX: Float = 0f
var currentY: Float = 0f
val DEFAULT_RADIUS = 80f
var radius = 0f
    set(value) {
        field = value
        if (value > 0) {
            shade = RadialGradient(currentX, currentY, value,
                    0x00ffffff, 0xFF58FAAC.toInt(), Shader.TileMode.CLAMP)
            paint.shader = shade
        }
        postInvalidate()
    }
var anim: Animator? = null

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.drawCircle(currentX, currentY, radius, paint)
}

override fun onTouchEvent(event: MotionEvent): Boolean {
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            currentX = event.x
            currentY = event.y
            radius = DEFAULT_RADIUS
            return true
        }
        MotionEvent.ACTION_UP -> {
            startAnim(400)
        }
    }
    return super.onTouchEvent(event)
}

private fun startAnim(duration: Long) {
    anim?.let { it.cancel() }
    anim = ObjectAnimator.ofFloat(this, "radius", DEFAULT_RADIUS, width * 1.0f).apply {
        setDuration(duration)
        interpolator = AccelerateInterpolator()
        addListener(object : Animator.AnimatorListener {
            override fun onAnimationRepeat(animation: Animator) {
            }
            override fun onAnimationEnd(animation: Animator) {
                radius = 0f     // 清除效果
            }
            override fun onAnimationCancel(animation: Animator) {
            }
            override fun onAnimationStart(animation: Animator) {
            }
        })
    }
    anim?.start()
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末陵叽,一起剝皮案震驚了整個濱河市狞尔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌巩掺,老刑警劉巖偏序,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異胖替,居然都是意外死亡研儒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門独令,熙熙樓的掌柜王于貴愁眉苦臉地迎上來端朵,“玉大人,你說我怎么就攤上這事燃箭〕迥兀” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵招狸,是天一觀的道長敬拓。 經(jīng)常有香客問我,道長裙戏,這世上最難降的妖魔是什么乘凸? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮累榜,結(jié)果婚禮上营勤,老公的妹妹穿的比我還像新娘。我一直安慰自己壹罚,他們只是感情好葛作,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著猖凛,像睡著了一般进鸠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上形病,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音霞幅,去河邊找鬼漠吻。 笑死,一個胖子當(dāng)著我的面吹牛司恳,可吹牛的內(nèi)容都是我干的途乃。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼扔傅,長吁一口氣:“原來是場噩夢啊……” “哼耍共!你這毒婦竟也來了烫饼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤试读,失蹤者是張志新(化名)和其女友劉穎杠纵,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钩骇,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡比藻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了倘屹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片银亲。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖纽匙,靈堂內(nèi)的尸體忽然破棺而出务蝠,到底是詐尸還是另有隱情,我是刑警寧澤烛缔,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布馏段,位于F島的核電站,受9級特大地震影響力穗,放射性物質(zhì)發(fā)生泄漏毅弧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一当窗、第九天 我趴在偏房一處隱蔽的房頂上張望够坐。 院中可真熱鬧,春花似錦崖面、人聲如沸元咙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽庶香。三九已至,卻和暖如春简识,著一層夾襖步出監(jiān)牢的瞬間赶掖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工七扰, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留奢赂,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓颈走,卻偏偏與公主長得像膳灶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

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