当前位置:   article > 正文

Android自定义View(二)——亮度条_android 设置亮度条

android 设置亮度条

前言

昨天凌晨在B站抽盲盒抽上瘾,花了六百多块,就中了一个九十多块的miku fufu,还不是世嘉版的,标注的8%概率,假得很。

前天老板问我什么时候能做完,答下周五,毕竟不能留着跨年,然后他让我下周三之前做完,周末又不想跑这么远去公司,没办法,只能把项目copy回家了(没有远程代码仓库,就我一个android开发的小公司,保密协议现在都没给我签…)

亮度条

class LightnessBar @JvmOverloads constructor(
    context: Context,
    attributeSet: AttributeSet? = null,
    defStyleAttr: Int = 0,
    defStyleRes: Int = 0
): View(context, attributeSet, defStyleAttr, defStyleRes) {
    companion object {
        private const val TAG = "LightnessBar"
    }

    /**
     * 前景画笔,默认绘制白色的条
     */
    private val mPaint: Paint = Paint()

    /**
     * 背景RectF,负责描述背景条的位置信息
     */
    private val mBackgroundRectF = RectF()

    /**
     * 亮度条RectF,负责描述亮度条的位置信息
     */
    private val mLightnessBarRectF = RectF()

    /**
     * 背景和前景条的切角
     */
    private val mCornerRadius: Float

    /**
     * 当前亮度
     */
    private var mCurrentBrightness: Int

    /**
     * 仿seekbar做个maxWidth
     */
    private val mMaxWidth: Int

    /**
     * 缓存背景bitmap
     */
    private var mBackgroundBitmap: Bitmap? = null

    /**
     * draw()不宜新建对象
     */
    private val mMode = PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)

    init {
        context.theme.obtainStyledAttributes(
            attributeSet,
            R.styleable.LightBar,
            defStyleAttr, defStyleRes
        ).apply {
            mCornerRadius = getDimension(R.styleable.LightBar_cornerRadius, 0f)

            val contentResolver = context.contentResolver
            val mode = Settings.System.getInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS_MODE)
            if (mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) {
                Settings.System.putInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS_MODE,
                    Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)
            }

            mCurrentBrightness = Settings.System.getInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS)
            mMaxWidth = getDimensionPixelOffset(R.styleable.LightBar_maxWidth, DensityUtils.dip2px(40))
            recycle()
        }
        initPaint()
    }

    private fun initPaint() {
        mPaint.style = Paint.Style.FILL
        mPaint.color = Color.parseColor("#F2FFFFFF")
        mPaint.isAntiAlias = true
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val paddingVertical = (measuredWidth - min(measuredWidth, mMaxWidth)) * 0.5f
        mBackgroundRectF.top = 0f
        mBackgroundRectF.left = paddingVertical
        mBackgroundRectF.right = measuredWidth.toFloat() - paddingVertical
        mBackgroundRectF.bottom = measuredHeight.toFloat()
        mBackgroundBitmap = makeBackground(measuredWidth, measuredHeight)

        mLightnessBarRectF.bottom = mBackgroundRectF.bottom
        mLightnessBarRectF.left = mBackgroundRectF.left
        mLightnessBarRectF.right = mBackgroundRectF.right
        mLightnessBarRectF.top = measuredHeight * (1 - mCurrentBrightness / 255f)
    }

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

        if (canvas == null) return
        val sc = canvas.saveLayer(mBackgroundRectF, mPaint)
        drawBackground(canvas)
        mPaint.xfermode = mMode
        drawForeground(canvas)
        mPaint.xfermode = null
        canvas.restoreToCount(sc)
    }

    private fun drawBackground(canvas: Canvas?) {
        if (mBackgroundBitmap != null) {
            canvas?.drawBitmap(mBackgroundBitmap!!, 0f, 0f, mPaint)
        }
    }

    private fun drawForeground(canvas: Canvas?) {
        canvas?.drawRoundRect(mLightnessBarRectF, mCornerRadius, mCornerRadius, mPaint)
    }

    /**
     * 记录上一次亮度变化时的纵坐标y
     */
    private var mLastLightnessY = 0f

    /**
     * 记录指针移动时动态变化的纵坐标y
     */
    private var mPointerMoveY: Float = 0f

    override fun onTouchEvent(event: MotionEvent?): Boolean {
//        LogUtil.i(TAG, "onTouchEvent: $event")
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                mPointerMoveY = event.y
                mLastLightnessY = event.y
                parent.requestDisallowInterceptTouchEvent(true)
            }
            MotionEvent.ACTION_MOVE -> trackTouchEvent(event)
            MotionEvent.ACTION_UP -> {
                parent.requestDisallowInterceptTouchEvent(false)
            }
            MotionEvent.ACTION_CANCEL -> {
                parent.requestDisallowInterceptTouchEvent(false)
            }
        }
        return true
    }

    private fun trackTouchEvent(event: MotionEvent) {
        val newTop = mLightnessBarRectF.top - (mPointerMoveY - event.y)
        if (mPointerMoveY == event.y) {
            LogUtil.e(TAG, "mPointerMoveY: $mPointerMoveY event.y: ${event.y}")
        }
        mPointerMoveY = event.y

        mLightnessBarRectF.top = when {
            newTop <= mBackgroundRectF.top -> {
                if (mCurrentBrightness >= 255) return
                mBackgroundRectF.top
            }
            newTop >= mLightnessBarRectF.bottom -> {
                if (mCurrentBrightness <= 0) return
                mLightnessBarRectF.bottom
            }
            else -> newTop
        }

        LogUtil.i(TAG, """top: ${mLightnessBarRectF.top} newTop: $newTop""")
        if (mCurrentBrightness > 255 || mCurrentBrightness < 0) {
            LogUtil.i(TAG, """mCurrentBrightness: $mCurrentBrightness""")
        }

        val newLightness: Int = mCurrentBrightness + (((mLastLightnessY - mPointerMoveY) / measuredHeight) * 255).toInt()

        if (newLightness < mCurrentBrightness && mCurrentBrightness > 0) {
            mCurrentBrightness = if (newLightness > 0) {
                newLightness
            } else {
                0
            }
            adjustLightness()
        }

        if (newLightness > mCurrentBrightness && mCurrentBrightness < 255) {
            mCurrentBrightness = if (newLightness < 255) {
                newLightness
            } else {
                255
            }
            adjustLightness()
        }

        invalidate()
    }

    /**
     * 限制每次调节系统亮度的协程数量
     */
    private val mMutex = Mutex()

    /**
     * 调节亮度
     */
    private fun adjustLightness() {
        mLastLightnessY = mPointerMoveY
        CoroutineScope(Dispatchers.IO).launch {
            mMutex.tryLock()
            if (mCurrentBrightness in 0..255) {
                Settings.System.putInt(
                    context.contentResolver,
                    Settings.System.SCREEN_BRIGHTNESS,
                    mCurrentBrightness
                )
            } else {
                throw IllegalArgumentException("mCurrentBrightness超出了界限")
            }
            mMutex.unlock()
        }
    }

    /**
     * 绘制背景bitmap
     */
    private fun makeBackground(w: Int, h: Int): Bitmap {
        val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        val paint = Paint(Paint.ANTI_ALIAS_FLAG)
        paint.color = Color.parseColor("#CC050603")
        canvas.drawRoundRect(mBackgroundRectF, mCornerRadius, mCornerRadius, paint)
        ContextCompat.getDrawable(context, R.drawable.icon_light_mode)?.let {
            it.setBounds(
                (DensityUtils.dip2px(10) + mBackgroundRectF.left).toInt(),
                (mBackgroundRectF.bottom - DensityUtils.dip2px(32)).toInt(),
                (mBackgroundRectF.right - DensityUtils.dip2px(10)).toInt(),
                (mBackgroundRectF.bottom - DensityUtils.dip2px(12)).toInt()
            )
            it.draw(canvas)
        }
        return bitmap
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238

详解

就像手机上的亮度条、音量条一样,看起来是两个叠加上去的(大佬或许可以一次绘制完毕),背景我使用bitmap变量存下来了

/**
 * 绘制背景bitmap
 */
private fun makeBackground(w: Int, h: Int): Bitmap {
    val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    paint.color = Color.parseColor("#CC050603")
    canvas.drawRoundRect(mBackgroundRectF, mCornerRadius, mCornerRadius, paint)
    ContextCompat.getDrawable(context, R.drawable.icon_light_mode)?.let {
        it.setBounds(
            (DensityUtils.dip2px(10) + mBackgroundRectF.left).toInt(),
            (mBackgroundRectF.bottom - DensityUtils.dip2px(32)).toInt(),
            (mBackgroundRectF.right - DensityUtils.dip2px(10)).toInt(),
            (mBackgroundRectF.bottom - DensityUtils.dip2px(12)).toInt()
        )
        it.draw(canvas)
    }
    return bitmap
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

上面的R.drawable.icon_light_mode是我找的小太阳图标,标注为亮度条;DensityUtils是我的工具类,用来做屏幕适配和在代码中使用dp,这种写法还是麻烦了些,后面有时间优化一下;

前景也类似

canvas?.drawRoundRect(mLightnessBarRectF, mCornerRadius, mCornerRadius, mPaint)

  • 1
  • 2

但是要注意的是,我们对于前景表示进度的矩形条可能有不同的需求,比如上平下圆、上圆下圆等;这里我的需求是上圆下圆,但是也可以很简单过渡到上平下圆,关键代码就是这里:

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

    if (canvas == null) return
    val sc = canvas.saveLayer(mBackgroundRectF, mPaint)
    drawBackground(canvas)
    mPaint.xfermode = mMode
    drawForeground(canvas)
    mPaint.xfermode = null
    canvas.restoreToCount(sc)
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

结尾

如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料

1、《Android性能优化实战篇》
2、《音视频精编源码解析》
3、24种设计模式介绍与6大设计原则
4、360°全方面性能调优
5、2021最新版数据结构与算法面试题手册 1
6、2023年Android中高级最全面试真题答案解析
7、Android Compose 强化实战
8、Android Framework 源码开发揭秘(2)
9、Android Jetpack Compose开发应用指南第三版
10、Android 音视频开发进阶指南-无水印(1)
11、Android车载操作系统开发揭秘
12、Android车载系统应用指南(1)
13、Android多媒体应用开发实战详解:图像、音频、视频、2D和3D-2
14、Android高级UI开源框架进阶解密(1)无水印版
15、Android源码解析(1)
16、Flutter技术解析与实战
17、Flutter技术进阶
18、Flutter入门与实战 无水印
19、Flutter完整开发实战详解
20、Jetpack架构组件从入门到精通
21、KMM跨平台框架入门教程无水印
22、Kotlin 入门教程指南(1)
23、kotlin从入门到精通
24、高级Android插件化强化实战(附源码)
25、高级Android组件化强化实战(附源码)
26、高级Jetpack强化实战
27、高级Kotlin强化实战(附Demo)
28、鸿蒙零基础入门学习指南
29、史上最详android版kotlin协程入门进阶实战指南
30、音视频开发教程(附面试题)
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/643098
推荐阅读
相关标签
  

闽ICP备14008679号