Android之调节屏幕亮度

1、需求分析

在使用微信或者支付宝的付款码支付时,如果你点击放大付款码,就会跳转到一个新的页面去显示大尺寸的付款码,而且你会发现屏幕变亮了,这样会便于扫码机识别你的付款码。当你付款成功退出付款码放大的界面后,屏幕就会恢复到原先的亮度。我很早就注意到了这点,所以当我自己的项目需要做二维码点击放大功能时,我也在放大的同时把屏幕的界面调亮一点。尽管我当时比较轻松地实现了这个功能,但是当我编写屏幕亮度工具类时,发现里面其实内有乾坤。现在就让我们来系统学习一下。

首先我们要明确“屏幕亮度”是什么。它其实包含了两种情况:

  1. 当前窗口的亮度。如果只改变当前窗口的亮度的话,当你退出该窗口(比如销毁了当前的Activity或者干脆退出了应用),那么屏幕就会恢复原先的亮度。也就是说,此处的改变只对当前的窗口有效。微信或支付宝在点击放大付款码后,改变的就是这个。
  2. 改变系统屏幕亮度。在下拉的手机设置面板中,有一个改变屏幕亮度的进度条(下图中的红框),这里改变的就是系统的屏幕亮度,适用于所有的窗口。

下来设置面板

2、准备工作

创建一个BrightnessActivity,然后在里面放置两个进度条,一个改变系统亮度,一个改变窗口亮度。为了便于以后使用,我们会把用到的方法都封装到一个工具类中。所以再创建一个名称为BrightnessUtil的Kotlin文件,但是不要创建类,因为我们会使用扩展成员的方式来编写工具类。

温馨提示:最近大半年都在使用Kotlin,这实在是一门很棒的语言,推荐大家学习。以后的博客我一般都会使用Kotlin了。

3、改变当前窗口亮度

首先来看看怎么改变窗口亮度。十分简单,只需改变窗口属性中的屏幕亮度(screenBrightness)一项。让我们直接来看代码:

/**
 * 当前窗口亮度
 * 范围为0~1.0,1.0时为最亮,-1为系统默认设置
 */
var Activity.windowBrightness
    get() = window.attributes.screenBrightness
    set(brightness) {
        //小于0或大于1.0默认为系统亮度
        window.attributes = window.attributes.apply {
            screenBrightness = if (brightness > 1.0 || brightness < 0) -1.0F else brightness
        }
    }

改变窗口亮度的上下文必须是Activity,所以我给Activity加了一个扩展属性windowBrightness,它的值就是当前的窗口亮度,改变它的值就可以改变窗口亮度。它的范围是0~1.0,从0到1.0亮度逐渐增大;如果赋值为-1,那就表示跟随系统的亮度。

使用起来也很简单:

        tvWindowBright.text = "当前窗口亮度=$windowBrightness"
        sbWindowBright.progress = if (windowBrightness > 0) (windowBrightness * 100).toInt() else 0
        sbWindowBright.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                windowBrightness = progress.toFloat() / 100F
                tvWindowBright.text = "当前窗口亮度=$windowBrightness"
            }

            override fun onStartTrackingTouch(seekBar: SeekBar?) {
            }

            override fun onStopTrackingTouch(seekBar: SeekBar?) {
            }

        })

没有接触过Kotlin的小伙伴们可能会不知道这属性在Java中怎么用?毕竟我们连类名都没有看到。其实Kotlin会默认为Java生成一个“类名+kt”的类,属性则会生成getter和setter静态方法。所以在Java代码中只需要 这么写:

        BrightnessUtilKt.getWindowBrightness(Activity);
        BrightnessUtilKt.setWindowBrightness(Activity,brightness);

4、改变系统亮度

比起改变窗口亮度,改变系统亮度就要麻烦一点了。作为个人,我们改变世界都是不容易的,那么一个应用想要改变系统自然也不会轻而易举。

4.1 清单文件申请权限

第一步,我们需要到AndroidManifest.xml中申请权限:

    <uses-permission android:name="android.permission.WRITE_SETTINGS"
        tools:ignore="ProtectedPermissions" />

之所以加上tools:ignore="ProtectedPermissions"是因为改变系统设置的权限一般只归系统App所有,所以编译器会报一个警告,加上这个可以忽略警告。

4.2 申请动态权限

如果你的手机系统是Android6.0以上的,那么还得动态申请权限。系统设置权限的动态申请有点特别,它需要跳转到系统的“可修改系统设置”界面,让用户决定是否允许当前应用修改系统设置,然后再在onActivityResult中处理回调结果。

我们在进入BrightnessActivity时就动态申请权限,代码如下:

        //修改系统屏幕亮度需要修改系统设置的权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //如果当前平台版本大于23平台
            if (!Settings.System.canWrite(mContext)) {
                val intent = with(Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)) {
                    data = Uri.parse("package:$packageName")
                    this
                }
                startActivityForResult(intent, RQ_WRITE_SETTINGS)
            } else {
                changeSystemBrightness()
            }
        } else {
            //Android6.0以下的系统则直接修改亮度
            changeSystemBrightness()
        }

首先调用Settings.System.canWrite(Context)判断手机系统,Android6.0以下的直接允许修改亮度的操作;Android6.0以上的则要进一步判断是否已经获得了修改系统设置的权限,没有的话就要打开如下界面去设置。

修改系统设置界面

无论用户是否授权,我们都需要一个回调,这时onActivityResult就可以派上用场了:

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            RQ_WRITE_SETTINGS -> {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    if (Settings.System.canWrite(mContext)) {
                        shortToast("已获取权限")
                        changeSystemBrightness()
                    } else {
                        shortToast("你拒绝了权限")
                    }
                }
            }
        }
    }

代码很简单,就不做过多解释了。

4.3 去除自动亮度

前面我们虽然解决了权限问题,但是还要考虑到一个实际情况,那就是用户可能会设置了自动亮度,在这个前提下是无法改变系统屏幕亮度的。所以这里要做两步处理:

  1. 判断用户是否开启了自动亮度;
  2. 如果当前开启了自动亮度,则需要将其关闭。

4.3.1 判断是否自动亮度

我们在工具类中添加isAutoBrightness属性,它只有Getter方法,返回一个布尔值。这里调用Settings.System.getInt()方法,第二个参数传入 Settings.System.SCREEN_BRIGHTNESS_MODE表示我们要获取系统屏幕亮度模式,如果是Settings.System.SCREEN_BRIGHTNESS_MODE,则表示当前自动亮度模式。

val isAutoBrightness:Boolean
    get() = try {
        Settings.System.getInt(
            AndUtil.appContext.contentResolver,
            Settings.System.SCREEN_BRIGHTNESS_MODE
        ) == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC
    } catch (e: Settings.SettingNotFoundException) {
        e.printStackTrace()
        false
    }

4.3.2 设置开启和关闭自动亮度

前面我们获取了系统亮度模式,通过设置它的值,我们就可以控制自动亮度模式的开关了。
在工具类中创建一个setAutoBrightness()函数,如果设置成功就返回true。这里用到的是Settings.System.putInt(),第二个参数即为我们要设置的亮度模式。当参数enable为true时就是自动模式了。

/**
 * 设置是否开启自动亮度
 * @param enable : 为true时开启,false时关闭
 * @return 设置成功返回true
 */
fun setAutoBrightness(enable: Boolean) = Settings.System.putInt(
    AndUtil.appContext.contentResolver, Settings.System.SCREEN_BRIGHTNESS_MODE,
    if (enable) Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC else Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL
)

4.4 封装改变系统屏幕亮度属性

现在我们总算可以编写修改系统亮度的代码了。跟修改窗口亮度一样,我们这里也使用了一个属性,命名为systemBrightness

/**
 * 系统屏幕亮度,需要WRITE_SETTINGS权限,并在代码中申请系统设置权限
 * 范围为0~255
 */
var systemBrightness
    get() = try {
        Settings.System.getInt(AndUtil.appContext.contentResolver, Settings.System.SCREEN_BRIGHTNESS)
    } catch (e: Settings.SettingNotFoundException) {
        e.printStackTrace()
        -1
    }
    @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
    set(@IntRange(from = 0, to = 255) brightness) {
        if (isAutoBrightness) {
            //如果当前是自动亮度,则关闭自动亮度
            setAutoBrightness(false)
        }
        val uri = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS)
        Settings.System.putInt(AndUtil.appContext.contentResolver, Settings.System.SCREEN_BRIGHTNESS, brightness)
        AndUtil.appContext.contentResolver.notifyChange(uri, null)
    }

这里我们重点来看设置系统亮度,也就是set()里面的代码。首先判断当前是否开启了自动亮度模式,如果是则将其关闭。后面的代码类似于setAutoBrightness(),都是在Settings.System.putInt()中赋值,不同的是还要调用Context.contentResolver.notifyChange()方法去通知系统我们已经修改了屏幕亮度,这样设置的值才会起作用。另外,要注意系统屏幕亮度的取值范围是0~255。

最后当然是设置SeekBar的监听了:

    private fun changeSystemBrightness() {
        sbSystemBright.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                systemBrightness = progress * 255 / 100
                tvSystemBright.text = "系统亮度=$systemBrightness"
            }


            override fun onStartTrackingTouch(seekBar: SeekBar?) {
            }


            override fun onStopTrackingTouch(seekBar: SeekBar?) {
            }

        })
    }

实现的效果如下:

效果

视频录制看不出亮度的变化,但真机上是没有问题的。

5、后记

本文分析了屏幕亮度的类型,并给出了设置的方法。其中,设置系统屏幕亮度时要格外注意动态权限申请和自动亮度模式的影响。

最后给一下主要的源码:

BrightnessUtil

BrightnessActivity

阅读 2.7k

推荐阅读