锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

Android折叠屏开发学习(一)---通过传感器获取铰链角度

时间:2023-09-03 20:37:02 传感器zh30104

学习更好的别人,

做更好的自己。

——微卡智享

df8c47d9872c1bbb1c4c032879f4149b.png

本文长度为5289,预计阅读8分钟

前言

Vivo4月11日发布X Fold折叠屏幕手机,也抢了几个星期,终于得到了,因为有折叠屏幕手机,作为一个开发者,当然,也想研究折叠屏幕的开发,本文将简要介绍折叠屏幕的开发,通过传感器获得折叠角度,适合折叠屏幕,Android官方推出了Jetpack WindowManager,下一篇文章将特别介绍WindowManager适合开发的折叠屏。

实现效果

折叠屏开发

微卡智享

2018年8月Google发布Android 9.首次支持折叠屏功能并推出Android官方折叠屏适应指南。适应主要从以下几个方面:

  1. 应用连续性:处理配置变更

  2. 屏幕兼容性:resizeableActivity 与 maxAspectRatio

  3. 多窗口适应:多项恢复和独家资源访问

01

关于应用程序的连续性

对于应用的连续性,最核心的两点是配置界面状态及处理配置变更

配置界面状态,现在Android APP只要使用开发ViewModel,其实就不是问题,Activity重建时,系统会将上次销毁Activity的内部ViewModelStore中的所有ViewModel传给新的Activity的ViewModelStore,新的Activity中可以从ViewModelStore获取前的ViewModel,流程图如下:

从上图可以看出Activity最终finished会通过的ViewModelStore的onCleared清空当前的ViewModel,当然,这种情况是水平和垂直屏幕切换Activity在finished志之前已经将就了ViewModel传递给新的Activity的ViewModelStore没有问题。

如果在特定配置变更期间不需要更新资源,则自行配置处理变更时,并且由于性能限制,你需要尽量避免 Activity 重启,可以声明 Activity 自行处理配置变更,防止系统重启 Activity,例如,文件代码声明 Activity 可同时处理屏幕方向变更和键盘可用性变更:

即使其中一种配置发生了变化,MyActivity也不会重启。但MyActivity会接收到对onConfigurationChanged()调用消息。这种方法将被传递Configuration对象,从而指定新设备配置。你可以通过阅读Configuration通过更新界面使用的资源,确定中间字段的新配置。调用此方法时,Activity 的Resources对象将相应更新,并根据新配置返回资源,以便您不重启系统 Activity 界面元素很容易重置。

通过onConfigurationChanged()用于检查当前设备的方向

override fun onConfigurationChanged(newConfig: Configuration) {     super.onConfigurationChanged(newConfig)       // Checks the orientation of the screen     if (newConfig.orientation === Configuration.ORIENTATION_LANDSCAPE) {         Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show()     } else if (newConfig.orientation === Configuration.ORIENTATION_PORTRAIT) {         Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show()     } }

02

resizeableActivity 与 maxAspectRatio

resizeableActivity可以调整应用程序的大小,设置resizeableActivity=true,这个在Android的7.0默认为true因此,这可以最大限度地兼容序可能遇到的任何设备类型和环境(如可折叠设备、桌面模式或免费窗口)。请使用可折叠模拟器在分屏模式下测试应用行为。

若应用设置resizeableActivity=false,它将平台不支持多窗口模式。系统可能仍然会调整应用程序的大小或将其放置在多窗口模式中;然而,为了实现兼容性,应用程序中的所有组件(包括应用程序中的所有组件) Activity、Service 等)应用相同的配置。在某些情况下,重大变化配置的情况下,重大变能会在不改变配置的情况下重启过程。

例如,以下 Activity 设置了resizableActivity=false以及maxAspectRatio。当设备展开时,系统将应用程序放置在兼容模式中以保持 Activity 配置、尺寸和宽高比。

如果未设置resizeableActivity,或将其设置为 true,该系统假设该应用程序完全支持多窗口,并且可以调整大小。

微卡智享

获取铰链角度

关于折叠屏,在Android Q 版本已经开始官方支持,本文仅针对Android 简单介绍一下11版折叠屏的一些扩展支持。

根据官网提供Android 在11个新特性介绍文档中,对折叠屏幕的支持和适应指导如下:

使用Android11.合页角度可通过以下方法确定运行在合页屏幕配置设备上的应用: 提供具有TYPE_HINGE_ANGLE新传感器和新传感器SensorEvent, 后者可以监控合页角度,并提供设备两部分之间的角度测量值。 在用户操作设备时,可以使用这些原始测量值进行精细的动画显示。 在用户操作设备时,可以使用这些原始测量值进行精细的动画显示。 尽管对于某些类型的应用(如启动器和壁纸), 知道确切的合页角度会很有用,但大多数应用都应该使用 Jetpack 窗口管理器库, 通过调用DeviceState.getPosture()检索设备状态。 或者,您的应用程序也可以调用 registerDeviceStateChangeCallback(), 以在 DeviceState 变更时收到通知,并在状态变化时做出反应。

当然,在新版本中DeviceState不再公开API下一篇文章将说,今天我们主要看铰链的折叠角度,TYPE_HINGE_ANGLE为 Android 新的传感器类型,专门用于处理折叠屏幕两部分之间的角度相关性。

代码展示

微卡智享

我们直接用上一个代码《Android MVI架构初探Demo,因为直接使用ViewModel,在屏幕Destory和Create中数据一直保持着。

在MainActivity中加入了SensorManager和Sensor并写了定义SensorEventListener的监听事件

通过onResume和onPause注册并取消监控。

在onCreate中获取到SensorManager和Sensor,其中Sensor记得要改Sensor.TYPE_HINGE_ANGLE。

package pers.vaccae.mvidemo.ui.view   import android.content.Context import android.content.res.Configuration import andrid.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import pers.vaccae.mvidemo.R
import pers.vaccae.mvidemo.bean.CDrugs
import pers.vaccae.mvidemo.ui.adapter.DrugsAdapter
import pers.vaccae.mvidemo.ui.intent.ActionIntent
import pers.vaccae.mvidemo.ui.intent.ActionState
import pers.vaccae.mvidemo.ui.viewmodel.MainViewModel


class MainActivity : AppCompatActivity() {


    private val TAG = "X Fold"


    private val recyclerView: RecyclerView by lazy { findViewById(R.id.recycler_view) }
    private val btncreate: Button by lazy { findViewById(R.id.btncreate) }
    private val btnadd: Button by lazy { findViewById(R.id.btnadd) }
    private val btndel: Button by lazy { findViewById(R.id.btndel) }


    private lateinit var mainViewModel: MainViewModel
    private lateinit var drugsAdapter: DrugsAdapter


    //adapter的位置
    private var adapterpos = -1


    private var mSensorManager: SensorManager? = null
    private var mSensor: Sensor? = null
    private val mSensorEventListener = object : SensorEventListener {
        override fun onSensorChanged(p0: SensorEvent?) {
            //p0.values[0]: 测量的铰链角度,其值范围在0到360度之间
            p0?.let {
                Log.i(TAG, "当前铰链角度为:${it.values[0]}")
            }
        }


        // 当传感器精度发生改变时回调该方法
        override fun onAccuracyChanged(p0: Sensor?, p1: Int) {
            p0?.let {
                Log.i(TAG, "Sensor:${it.name}, value:$p1")
            }
        }
    }




    override fun onDestroy() {
        super.onDestroy()
        Log.i(TAG, "onDestroy")
    }


    override fun onResume() {
        super.onResume()
        Log.i(TAG, "onResume")
        //开启监听
        mSensorManager?.let {
            it.registerListener(mSensorEventListener, mSensor!!,
                SensorManager.SENSOR_DELAY_NORMAL)
        }
    }


    override fun onPause() {
        super.onPause()
        Log.i(TAG, "onPause")
        // 取消监听
        mSensorManager?.let {
            it.unregisterListener(mSensorEventListener);
        }
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        Log.i(TAG, "onCreate")


        // 获取传感器管理对象
        mSensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
        // 获取传感器的类型(TYPE_HINGE_ANGLE:铰链角度传感器)
        mSensorManager?.let {
            mSensor = it.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE);
        }


        mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)


        drugsAdapter = DrugsAdapter(R.layout.rcl_item, mainViewModel.listDrugs)
        drugsAdapter.setOnItemClickListener { baseQuickAdapter, view, i ->
            adapterpos = i
        }


        val gridLayoutManager = GridLayoutManager(this, 3)
        recyclerView.layoutManager = gridLayoutManager
        recyclerView.adapter = drugsAdapter


        //初始化ViewModel监听
        observeViewModel()


        btncreate.setOnClickListener {
            lifecycleScope.launch {
                mainViewModel.actionIntent.send(ActionIntent.LoadDrugs)
            }
        }


        btnadd.setOnClickListener {
            lifecycleScope.launch {
                mainViewModel.actionIntent.send(ActionIntent.InsDrugs)
            }
        }


        btndel.setOnClickListener {
            lifecycleScope.launch {
                Log.i("status", "$adapterpos")
                val item = try {
                    drugsAdapter.getItem(adapterpos)
                } catch (e: Exception) {
                    CDrugs()
                }
                mainViewModel.actionIntent.send(ActionIntent.DelDrugs(adapterpos, item))
            }
        }
    }


    private fun observeViewModel() {
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                mainViewModel.state.collect {
                    when (it) {
                        is ActionState.Normal -> {
                            btncreate.isEnabled = true
                            btnadd.isEnabled = true
                            btndel.isEnabled = true
                        }
                        is ActionState.Loading -> {
                            btncreate.isEnabled = false
                            btncreate.isEnabled = false
                            btncreate.isEnabled = false
                        }
                        is ActionState.Drugs -> {
                            drugsAdapter.setList(it.drugs)
//                            drugsAdapter.setNewInstance(it.drugs)
                        }
                        is ActionState.Error -> {
                            Toast.makeText(this@MainActivity, it.msg, Toast.LENGTH_SHORT).show()
                        }
                    }
                }
            }
        }
    }
}

上面是MainActivity的全部代码,等下一章都完成后整个Demo会更新到Github和Gitee上,这样通过传感器获取折叠屏折叠角度的代码就完成了。

参考文章

https://developer.android.google.cn/guide/topics/ui/foldables?hl=zh-cn

https://blog.csdn.net/vitaviva/article/details/105246639

https://juejin.cn/post/7049705037525680164

https://blog.csdn.net/luzhenrong45/article/details/109632140

往期精彩回顾

Android MVI架构初探


Android Kotlin协程间的通信Channel介绍


Android内存篇(三)----自动重启APP实现内存兜底策略


锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章