赞
踩
昨天凌晨在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 } }
就像手机上的亮度条、音量条一样,看起来是两个叠加上去的(大佬或许可以一次绘制完毕),背景我使用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 }
上面的R.drawable.icon_light_mode是我找的小太阳图标,标注为亮度条;DensityUtils是我的工具类,用来做屏幕适配和在代码中使用dp,这种写法还是麻烦了些,后面有时间优化一下;
前景也类似
canvas?.drawRoundRect(mLightnessBarRectF, mCornerRadius, mCornerRadius, mPaint)
但是要注意的是,我们对于前景表示进度的矩形条可能有不同的需求,比如上平下圆、上圆下圆等;这里我的需求是上圆下圆,但是也可以很简单过渡到上平下圆,关键代码就是这里:
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)
}
如果你看到了这里,觉得文章写得不错就给个赞呗?
更多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、音视频开发教程(附面试题)
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。