29、综合实战(上):设计一个智能台灯控制App,根据环境光自动调节台灯亮度(模拟)
终于到了综合实战环节。
前面我们学了光线传感器的各种用法,从最基础的数值读取,到数据滤波,再到灵敏度调节。说实话,这些知识点单独拿出来都不难,但真正把它们组合成一个完整的应用,才是考验功底的时候。
这一章,我们就来做一个智能台灯控制App。它能根据环境光的强弱,自动调节台灯的亮度。当然,我们这里用手机屏幕的亮度来模拟台灯亮度——毕竟你手上没有真的台灯硬件,对吧?
项目需求分析
先理清楚我们要做什么。这个App的核心逻辑其实很简单:
- 输入:光线传感器读取的环境光强度(lux)
- 处理:根据光照值映射到对应的亮度百分比
- 输出:调整手机屏幕亮度(模拟台灯亮度)
听起来是不是有点像PID控制器的简化版?嗯,其实没那么复杂。我们只需要一个映射函数,把0~10000 lux的范围映射到0~255的屏幕亮度值。
核心设计思路:环境光越暗,台灯越亮;环境光越亮,台灯越暗。说白了就是补光逻辑——跟人眼的自然需求是一致的。
UI布局设计
我个人习惯先把UI搭好,再写逻辑。这样调试的时候能直观看到效果。
这个App的界面分三个区域:
- 状态显示区:显示当前环境光数值和台灯亮度百分比
- 模式切换区:手动模式 vs 自动模式
- 模拟台灯区:一个圆形区域,背景色随亮度变化
来看看布局代码:
<!-- activity_main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:background="#FFF8E1">
<!-- 环境光数值显示 -->
<TextView
android:id="@+id/tv_lux"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="环境光:0 lux"
android:textSize="20sp"
android:textColor="#333" />
<!-- 台灯亮度显示 -->
<TextView
android:id="@+id/tv_brightness"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="台灯亮度:0%"
android:textSize="18sp"
android:textColor="#555"
android:layout_marginTop="8dp" />
<!-- 模式切换 -->
<Switch
android:id="@+id/switch_mode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="自动模式"
android:checked="true"
android:layout_marginTop="16dp" />
<!-- 手动亮度滑块 -->
<SeekBar
android:id="@+id/seekbar_manual"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="50"
android:visibility="gone"
android:layout_marginTop="16dp" />
<!-- 模拟台灯区域 -->
<View
android:id="@+id/view_lamp"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center"
android:layout_marginTop="32dp"
android:background="@drawable/circle_lamp" />
</LinearLayout>
小技巧:手动模式下的SeekBar默认隐藏,只有切换到手动模式时才显示。这样UI更干净,用户不会困惑。
核心逻辑实现
接下来是重头戏——光线传感器监听和亮度映射。
我曾在项目里踩过一个坑:直接拿原始lux值去映射亮度,结果在弱光环境下屏幕疯狂闪烁。为什么呢?因为光线传感器在低光照下数值波动很大,哪怕只是稍微动一下手机,数值就跳几十个单位。
所以这里我们引入一个简单的平滑处理:取最近5次读数的平均值。
public class SmartLampActivity extends AppCompatActivity {
private SensorManager sensorManager;
private Sensor lightSensor;
private TextView tvLux, tvBrightness;
private Switch switchMode;
private SeekBar seekBarManual;
private View viewLamp;
private float currentLux = 0f;
private int currentBrightness = 0;
// 平滑缓冲区
private float[] luxBuffer = new float[5];
private int bufferIndex = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
initSensor();
}
private void initViews() {
tvLux = findViewById(R.id.tv_lux);
tvBrightness = findViewById(R.id.tv_brightness);
switchMode = findViewById(R.id.switch_mode);
seekBarManual = findViewById(R.id.seekbar_manual);
viewLamp = findViewById(R.id.view_lamp);
switchMode.setOnCheckedChangeListener((buttonView, isChecked) -> {
seekBarManual.setVisibility(isChecked ? View.GONE : View.VISIBLE);
if (isChecked) {
// 切回自动模式,立即更新一次
updateLampBrightness(currentLux);
}
});
seekBarManual.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (!switchMode.isChecked()) {
setScreenBrightness(progress);
updateLampUI(progress);
}
}
@Override public void onStartTrackingTouch(SeekBar seekBar) {}
@Override public void onStopTrackingTouch(SeekBar seekBar) {}
});
}
private void initSensor() {
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
if (lightSensor == null) {
tvLux.setText("设备不支持光线传感器");
return;
}
sensorManager.registerListener(lightSensorListener, lightSensor,
SensorManager.SENSOR_DELAY_NORMAL);
}
private final SensorEventListener lightSensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
float rawLux = event.values[0];
// 平滑处理
luxBuffer[bufferIndex % 5] = rawLux;
bufferIndex++;
float sum = 0;
int count = Math.min(bufferIndex, 5);
for (int i = 0; i < count; i++) {
sum += luxBuffer[i];
}
currentLux = sum / count;
runOnUiThread(() -> {
tvLux.setText(String.format("环境光:%.1f lux", currentLux));
if (switchMode.isChecked()) {
updateLampBrightness(currentLux);
}
});
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 精度变化时一般不需要处理
}
};
private void updateLampBrightness(float lux) {
// 映射逻辑:0 lux -> 255亮度,10000+ lux -> 0亮度
int brightness;
if (lux <= 0) {
brightness = 255;
} else if (lux >= 10000) {
brightness = 0;
} else {
// 线性映射,取反
brightness = (int) (255 - (lux / 10000f) * 255);
}
// 限制范围
brightness = Math.max(0, Math.min(255, brightness));
currentBrightness = brightness;
int percent = (int) (brightness / 255f * 100);
tvBrightness.setText("台灯亮度:" + percent + "%");
setScreenBrightness(brightness);
updateLampUI(brightness);
}
private void setScreenBrightness(int brightness) {
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.screenBrightness = brightness / 255f;
getWindow().setAttributes(lp);
}
private void updateLampUI(int brightness) {
// 亮度越高,背景色越接近白色;越低越接近暗黄色
int alpha = brightness;
int color = Color.argb(alpha, 255, 220, 100);
viewLamp.setBackgroundTintList(ColorStateList.valueOf(color));
}
@Override
protected void onDestroy() {
super.onDestroy();
if (sensorManager != null) {
sensorManager.unregisterListener(lightSensorListener);
}
}
}
映射逻辑详解
这段代码里最核心的就是updateLampBrightness()方法。我解释一下映射思路:
| 环境光(lux) | 台灯亮度(0~255) | 说明 |
|---|---|---|
| 0 | 255 | 全黑环境,台灯最亮 |
| 500 | ~242 | 微弱光,台灯依然很亮 |
| 2000 | ~204 | 室内正常光照,中等亮度 |
| 5000 | ~127 | 较亮环境,台灯半亮 |
| 10000+ | 0 | 强光环境,台灯关闭 |
注意:这个映射是线性的,实际产品中可能需要用曲线映射。比如人眼对暗部变化更敏感,所以低光照区域应该用更缓的斜率。不过作为入门教程,线性映射已经够用了。
知识体系结构图
下面这张图概括了整个智能台灯App的架构:
避坑指南
写这个Demo的时候,有几个地方特别容易翻车:
- 传感器注册时机:一定要在
onResume()里注册监听,在onPause()里取消注册。否则App切到后台还在耗电,用户会骂的。 - 屏幕亮度权限:修改系统屏幕亮度需要
WRITE_SETTINGS权限。不过我们这个Demo只修改当前Activity的亮度,不需要系统权限——用WindowManager.LayoutParams.screenBrightness就行。 - 数值溢出:我见过有人直接写
brightness = 255 - lux / 10000 * 255,结果因为整数除法,lux小于10000时结果永远是255。记得用浮点数运算。
扩展思路:如果你手头有Arduino或ESP32,完全可以把这套逻辑移植过去,用PWM控制真实的LED灯。手机端只做显示和控制,硬件端负责执行——这才是真正的智能台灯。
好了,这一章我们完成了智能台灯的核心逻辑。下一章我们会加入更多实用功能,比如亮度曲线优化、日志记录、以及更友好的交互反馈。嗯,先消化这些内容吧。
公众号:蓝海资料掘金营,微信deep3321