美文网首页
android 仿微博护眼模式

android 仿微博护眼模式

作者: 笑慢 | 来源:发表于2021-12-15 23:28 被阅读0次

一、需求描述

我们知道在微博APP的设置中有一个护眼模式的开关选项,说是护眼模式,当我们打开微博此功能时,就会发现“咦,这不是在页面上添加了一个透明度的view吗?”,接下来我们带着疑问开始对此功能进行实现。

二、实现方案

通过仔细观察发现确实就是在每个activity页面的上层添加了一个有透明度的view,且需要满以下要求:

1、需要在页面的最上层创建一个view,不能被其他view所遮挡。

2、不能影响下层view的触摸事件。

3、是一个全屏的透明度view。

4、打开和关闭后需要立即生效。

首先第一点我们知道activity的视图层级PhoneWindow是在最上层,所以我们可以在window中添加view,伪代码:
activity.windowManager.addView(eyeCareView, eyeCareViewParam)
这样就创建在了activity的最上层,那么不能影响下层view的触摸事件和全屏怎么设置呢?
伪代码:

val eyeCareViewParam = WindowManager.LayoutParams(
    WindowManager.LayoutParams.TYPE_APPLICATION,
    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
            or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
            or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
            or WindowManager.LayoutParams.FLAG_FULLSCREEN
            or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
    PixelFormat.TRANSPARENT
)

eyeCareViewParam.gravity = Gravity.TOP or Gravity.START

activity.windowManager.defaultDisplay.apply {
    val point = Point()
    this.getRealSize(point)
    eyeCareViewParam.width = point.x
    eyeCareViewParam.height = point.y
}

view的创建和添加的问题解决了,接下来就是什么打开和关闭立即生效的问题,首页在设置页打开和关闭就很好解决,我们只要在相对应的点击事件下添加view和移除view即可,伪代码:

findViewById<RelativeLayout>(R.id.rl_open_eye).setOnClickListener {
    openEye()
    Toast.makeText(this,"已开启护眼模式",Toast.LENGTH_SHORT).show()
}

findViewById<RelativeLayout>(R.id.rl_close_eye).setOnClickListener {
    closeEye()
    Toast.makeText(this,"已关闭护眼模式",Toast.LENGTH_SHORT).show()
}

那么,在设置页面返回上一页或者跳转到另一个页面怎么生效呢?也很简单,只要我们在设置开关时本地存储开关状态(这里的存储方法有很多例如SharedPreferences、jetpack的 DataStore等),在acivity(通常情况下我们会创建一个基类)的onStart()生命周期中根据开关状态进行添加和移除的操作,伪代码:

@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart(owner: LifecycleOwner?) {
    if (isOpenEye()) {
        //打开护眼模式
        openEye()
    } else {
        //关闭护眼模式
        closeEye()
    }
}

这样,功能实现基本就完成了。不过等一下,我们是不是可以增加一个在对应时间段自动切换的模式?,这样就不需要手动去打开和关闭了。那么,我们就需要在存储开关状态的基础上再增加一个自动切换的状态,伪代码:

/**
 * 护眼模式关闭状态
 */
const val eyeCloseType = 0

/**
 * 护眼模式打开状态
 */
const val eyeOpenType = 1

/**
 * 护眼模式自动切换状态
 */
const val eyeSwitchType = 2

当然,我们还需要判断当前时间是否在打开的时间段内,伪代码:

**
 * 判断当前系统时间是否在指定时间的范围内
 * [beginHour] 开始小时,例如22
 * [beginMin]  开始小时的分钟数,例如30
 * [endHour]   结束小时,例如 8
 * [endMin]    结束小时的分钟数,例如0
 * @return true表示在范围内, 否则false
 */
private fun isCurrentInTimeScope(): Boolean {
    var result: Boolean
    val aDayInMillis = (1000 * 60 * 60 * 24).toLong()
    val currentTimeMillis = System.currentTimeMillis()
    val now = Time()
    now.set(currentTimeMillis)
    val startTime = Time()
    startTime.set(currentTimeMillis)
    startTime.hour = beginHour
    startTime.minute = beginMin
    val endTime = Time()
    endTime.set(currentTimeMillis)
    endTime.hour = endHour
    endTime.minute = endMin
    // 跨天的特殊情况(比如22:00-8:00)
    if (!startTime.before(endTime)) {
        startTime.set(startTime.toMillis(true) - aDayInMillis)
        result = !now.before(startTime) && !now.after(endTime)
        val startTimeInThisDay = Time()
        startTimeInThisDay.set(startTime.toMillis(true) + aDayInMillis)
        if (!now.before(startTimeInThisDay)) {
            result = true
        }
    } else {
        //普通情况(比如 8:00 - 14:00)
        result = !now.before(startTime) && !now.after(endTime)
    }
    return result
}

这样,完整的功能就实现完了。

三、完整代码

这里view的创建以及管理是封装在了一个帮助类中,代码:

/**
 * 护眼模式帮助类
 * @author xiaoman
 */
class CreateEyeCareViewHelper(private val activity: Activity, lifecycle: Lifecycle? = null) :
    LifecycleObserver {
    private var eyeCareView: FrameLayout? = null

    companion object {
        /**
         * 护眼模式关闭状态
         */
        const val eyeCloseType = 0

        /**
         * 护眼模式打开状态
         */
        const val eyeOpenType = 1

        /**
         * 护眼模式自动切换状态
         */
        const val eyeSwitchType = 2

        /**
         * 自动切换模式开始小时
         */
        const val beginHour = 20

        /**
         * 自动切换模式开始分钟
         */
        const val beginMin = 0

        /**
         * 自动切换模式结束小时
         */
        const val endHour = 6

        /**
         * 自动切换模式结束分钟
         */
        const val endMin = 0
    }

    init {
        lifecycle?.addObserver(this)
    }

    /**
     * 添加护眼模式浮层
     */
    private fun createEyeView() {
        if (eyeCareView != null) {
            return
        }
        eyeCareView = FrameLayout(activity)
        if (!isOpenEye()) {
            closeEye()
            return
        }
        val eyeCareViewParam = WindowManager.LayoutParams(
            WindowManager.LayoutParams.TYPE_APPLICATION,
            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                    or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                    or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                    or WindowManager.LayoutParams.FLAG_FULLSCREEN
                    or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
            PixelFormat.TRANSPARENT
        )

        eyeCareViewParam.gravity = Gravity.TOP or Gravity.START

        activity.windowManager.defaultDisplay.apply {
            val point = Point()
            this.getRealSize(point)
            eyeCareViewParam.width = point.x
            eyeCareViewParam.height = point.y
        }
        activity.windowManager.addView(eyeCareView, eyeCareViewParam)
        openEye()
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onStart(owner: LifecycleOwner?) {
        if (isOpenEye()) {
            //打开护眼模式
            openEye()
        } else {
            //关闭护眼模式
            closeEye()
        }
    }

    /**
     * 开启护眼模式
     */
    fun openEye() {
        if (eyeCareView != null) {
            if (isOpenEye()) {
                eyeCareView!!.setBackgroundColor(Color.parseColor("#4D000000"))
            } else {
                closeEye()
            }
        } else {
            createEyeView()
        }
    }


    /**
     * 关闭护眼模式
     */
    fun closeEye() {
        eyeCareView?.let {
            it.setBackgroundColor(Color.TRANSPARENT)
            if (it.isAttachedToWindow) {
                activity.windowManager.removeView(it)
            }
            eyeCareView = null
        }
    }

    /**
     * 判断当前系统时间是否在指定时间的范围内
     * [beginHour] 开始小时,例如22
     * [beginMin]  开始小时的分钟数,例如30
     * [endHour]   结束小时,例如 8
     * [endMin]    结束小时的分钟数,例如0
     * @return true表示在范围内, 否则false
     */
    private fun isCurrentInTimeScope(): Boolean {
        var result: Boolean
        val aDayInMillis = (1000 * 60 * 60 * 24).toLong()
        val currentTimeMillis = System.currentTimeMillis()
        val now = Time()
        now.set(currentTimeMillis)
        val startTime = Time()
        startTime.set(currentTimeMillis)
        startTime.hour = beginHour
        startTime.minute = beginMin
        val endTime = Time()
        endTime.set(currentTimeMillis)
        endTime.hour = endHour
        endTime.minute = endMin
        // 跨天的特殊情况(比如22:00-8:00)
        if (!startTime.before(endTime)) {
            startTime.set(startTime.toMillis(true) - aDayInMillis)
            result = !now.before(startTime) && !now.after(endTime)
            val startTimeInThisDay = Time()
            startTimeInThisDay.set(startTime.toMillis(true) + aDayInMillis)
            if (!now.before(startTimeInThisDay)) {
                result = true
            }
        } else {
            //普通情况(比如 8:00 - 14:00)
            result = !now.before(startTime) && !now.after(endTime)
        }
        return result
    }

    /**
     * 护眼模式是否打开
     */
    private fun isOpenEye(): Boolean {
        return SpUtil.getInstance(activity)
            .getIntValue(SpUtil.EYE_CARE_STYLE) == eyeOpenType || isSwitchEye()
    }

    /**
     * 护眼模式是否关闭
     */
    private fun isCloseEye(): Boolean {
        return SpUtil.getInstance(activity)
            .getIntValue(SpUtil.EYE_CARE_STYLE) == eyeCloseType || !isSwitchEye()
    }

    /**
     * 护眼模式自动切换状态是否在打开时间阶段
     */
    private fun isSwitchEye(): Boolean {
        return SpUtil.getInstance(activity)
            .getIntValue(SpUtil.EYE_CARE_STYLE) == eyeSwitchType && isCurrentInTimeScope()
    }



}

然后只要在activity的基类中初始化帮助类,代码:

abstract class BaseActivity : AppCompatActivity(){

    protected var createEyeCareViewHelper: CreateEyeCareViewHelper? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setLayout()
        if (canOpenEye()) {
            createEyeCareViewHelper = CreateEyeCareViewHelper(this, lifecycle)
        }
        initView(savedInstanceState)

    }

    private fun setLayout() {
        setContentView(getLayoutId())
    }

    @LayoutRes
    abstract fun getLayoutId(): Int

    abstract fun initView(savedInstanceState: Bundle?)


    /**
     * @return 是否需要开启护眼模式 默认开启
     */
    protected fun canOpenEye(): Boolean {
        return true
    }

}

当然还有在设置页面进行对应状态的操作,代码:

class SettingActivity : BaseActivity() {
    private lateinit var ivOpenEye :ImageView
    private lateinit var ivCloseEye :ImageView
    private lateinit var ivSwitchEye :ImageView

    override fun getLayoutId(): Int = R.layout.activity_setting

    override fun initView(savedInstanceState: Bundle?) {
        ivOpenEye = findViewById(R.id.iv_open_eye)
        ivCloseEye = findViewById(R.id.iv_close_eye)
        ivSwitchEye = findViewById(R.id.iv_switch_eye)

        var type = SpUtil.getInstance(this).getIntValue(SpUtil.EYE_CARE_STYLE)
        when(type){
            //开启
            CreateEyeCareViewHelper.eyeOpenType ->{
                setOenStyle()
            }
            //关闭
            CreateEyeCareViewHelper.eyeCloseType ->{
                setCloseStyle()
            }
            //自动开启
            CreateEyeCareViewHelper.eyeSwitchType ->{
                setSwitchStyle()
            }

        }

        findViewById<ImageView>(R.id.ivBack).setOnClickListener {
            finish()
        }



        findViewById<RelativeLayout>(R.id.rl_open_eye).setOnClickListener {
            if (type == CreateEyeCareViewHelper.eyeOpenType){
                return@setOnClickListener
            }
            setOenStyle()
            SpUtil.getInstance(this).setIntValue(SpUtil.EYE_CARE_STYLE, CreateEyeCareViewHelper.eyeOpenType)
            type = CreateEyeCareViewHelper.eyeOpenType
            createEyeCareViewHelper?.openEye()
            Toast.makeText(this,"已开启护眼模式",Toast.LENGTH_SHORT).show()
        }

        findViewById<RelativeLayout>(R.id.rl_close_eye).setOnClickListener {
            if (type == CreateEyeCareViewHelper.eyeCloseType){
                return@setOnClickListener
            }
            setCloseStyle()
            SpUtil.getInstance(this).setIntValue(SpUtil.EYE_CARE_STYLE, CreateEyeCareViewHelper.eyeCloseType)
            type = CreateEyeCareViewHelper.eyeCloseType
            createEyeCareViewHelper?.closeEye()
            Toast.makeText(this,"已关闭护眼模式",Toast.LENGTH_SHORT).show()
        }

        findViewById<RelativeLayout>(R.id.rl_switch_eye).setOnClickListener {
            if (type == CreateEyeCareViewHelper.eyeSwitchType){
                return@setOnClickListener
            }
            setSwitchStyle()
            SpUtil.getInstance(this).setIntValue(SpUtil.EYE_CARE_STYLE, CreateEyeCareViewHelper.eyeSwitchType)
            type = CreateEyeCareViewHelper.eyeSwitchType
            createEyeCareViewHelper?.openEye()
            Toast.makeText(this,"已开启自动切换模式",Toast.LENGTH_SHORT).show()
        }
    }

    /**
     * 开启样式
     */
    private fun setOenStyle() {
        ivOpenEye.visibility = View.VISIBLE
        ivCloseEye.visibility = View.GONE
        ivSwitchEye.visibility = View.GONE
    }

    /**
     * 关闭样式
     */
    private fun setCloseStyle() {
        ivOpenEye.visibility = View.GONE
        ivCloseEye.visibility = View.VISIBLE
        ivSwitchEye.visibility = View.GONE
    }

    /**
     * 自动开启样式
     */
    private fun setSwitchStyle() {
        ivOpenEye.visibility = View.GONE
        ivCloseEye.visibility = View.GONE
        ivSwitchEye.visibility = View.VISIBLE
    }
}

四、实现效果

SM-G9500_20211214224222.gif

五、获取源码

github地址:https://github.com/still-soul/EyeProtectionMode

相关文章

网友评论

      本文标题:android 仿微博护眼模式

      本文链接:https://www.haomeiwen.com/subject/kcjmfrtx.html