使用传感器和Matrix实现图片随手机晃动而变换

背景

接上篇文章用Kotlin+Retrofit+RxKotlin+MVP撸了一个小项目

这个效果是在逛今日头条时候看到一条宝马车的广告有这样的效果。觉得很有意思,网上应该也有很多实现的例子,但是就想着自己思考实现一下。emmm,模拟器不能模拟出传感器的效果,然后不知道怎么录真机的视频上传。。。所以效果脑补一下吧。(一张很大的图片,手机往左边偏,图片往右边移动。。。)

技术探索
  • 传感器技术
  • Matrix

要想监听到手机晃动,肯定只有用传感器啦,由于Android系统的强大,我们使用各种传感器也是很方便的啦。

另外图片的左右移动之类的效果,之前想着用位移动画。。然后想到动画是让整个 View 在移动,而不是图片在移动,达不到效果。然后就决定用Matrix来做。事实证明是真的简单,就几行代码。当然要闹懂原理还是需要深入学习的。

代码
布局

布局很简单,就是一个固定大小的ImaveView。 这里有很重要的一点就是scaleType一定要设置成 matrix不然是没有效果的,原因我们后面讲。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/sensor_iv"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dimen_200"
        android:layout_marginTop="@dimen/dimen_100"
        android:contentDescription="@string/app_name"
        android:scaleType="matrix"
        android:src="@drawable/aaaa" />


</FrameLayout>
复制代码
传感器

首先来看一下传感器世界的坐标系是怎样的:

手机平放在桌面上,横向的是X轴,纵向是Y轴,与屏幕垂直的方向是Z轴。

然后传感器的使用也很简单 Android sensor framework 为我们提供了丰富我类和接口。 常用的类如下:

  • SensorManager 你能使用这个类来创建传感器服务的实例。这个类提供了各种方法类访问和列举传感器,注册和注销传感器的事件监听,获取相应的信息。这个类也提供了一些传感器的常量:报告传感器的精确度,设置数据获取频率,和校准传感器。

  • Sensor 通过这个类能够创建一个特定传感器的视力,这个类提过了各种检测传感器能力的方法。

  • SensorEvent 系统使用这个类来创建一个传感器的事件对象,它提供传感器事件的信息。传感器事件包含以下信息:原始传感器数据,产生这个事件的传感器类型,数据的准确性,以及事件的时间戳。

获取传感器

    private var manager: SensorManager? = null
    private var sensor: Sensor? = null

    ....
    //获取传感器管理对象
    manager = activity.getSystemService(Context.SENSOR_SERVICE) as SensorManager
    sensor = manager!!.getDefaultSensor(Sensor.TYPE_ORIENTATION) //获取向传感器
    
复制代码

由于我们要实现的功能只需要方向传感器就可以了,所以我们获取了方向传感器对象,当然系统还有更多的传感器对象,这里我就不一一介绍了,大家可以阅读文档或者百度一下。另外由于手机硬件的区别,请判断传感器对象是否可用。(方向传感器大部分手机应该都有吧哈哈)

定义监听

private val mListener = object : SensorEventListener {
        override fun onSensorChanged(event: SensorEvent) {
            //传感器值发生改变
            val values = event.values
            //Log.e(TAG, "onSensorChanged:  Z轴 = "+values[0]);
            //Log.e(TAG, "onSensorChanged:  X轴 = " + values[1]);
            //Log.e(TAG, "onSensorChanged:  Y轴 = " + values[2]);
        }

        override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
            //传感器精度发生改变
        }
    }
复制代码

注册和解除监听

override fun onResume() {
        super.onResume()
        Log.e(TAG, "onResume: ")
        manager!!.registerListener(mListener, sensor, SensorManager.SENSOR_DELAY_UI)
    }


    override fun onDestroy() {
        super.onDestroy()
        Log.e(TAG, "onDestroy: ")
        manager!!.unregisterListener(mListener)
    }
复制代码

使用SensorManagerregisterListener方法可以为我们注册所需要的传感器监听。 其中最后一个参数是控制回调频率的,有如下几个值:

  • SensorManager.SENSOR_DELAY_FASTEST:最快,延迟最小,同时也最消耗资源,一般只有特别依赖传感器的应用使用该频率,否则不推荐。
  • SensorManager.SENSOR_DELAY_GAME:适合游戏的频率,一般有实时性要求的应用适合使用这种频率。
  • SensorManager.SENSOR_DELAY_NORMAL:正常频率,一般对实时性要求不高的应用适合使用这种频率。
  • SensorManager.SENSOR_DELAY_UI:适合普通应用的频率,这种模式比较省电,而且系统开销小,但延迟大,因此只适合普通小程序使用。
图片移动

传感器搞定了,回调值拿到了,接下来就应该让图片动起来了。前面说过代码很少,是真的很少,

private val mListener = object : SensorEventListener {
        override fun onSensorChanged(event: SensorEvent) {
            //传感器值发生改变
            val values = event.values
            //Log.e(TAG, "onSensorChanged:  绕Z轴旋转 = "+values[0]);
            //Log.e(TAG, "onSensorChanged:  绕X轴翘起 = " + values[1]);
            //Log.e(TAG, "onSensorChanged:  绕Y轴翘起 = " + values[2]);
            val x = values[1]
            val y = values[2]
            //TODO 边界控制
            
            val imageMatrix = Matrix()
            imageMatrix.preTranslate(-y * scale, -x * scale)
            imageView!!.imageMatrix = imageMatrix
        }

        override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
            //传感器精度发生改变
        }
    }
复制代码

让图片移动起来的代码就三句:

            val imageMatrix = Matrix()
            imageMatrix.preTranslate(-y * scale, -x * scale)
            imageView!!.imageMatrix = imageMatrix
复制代码

这三句是怎么起作用的呢?我们一一来分析

首先创建了一个新的 Matrix 对象,那么 Matrix(矩阵)是怎么作用于图像的呢? 这里简单说一下我的理解 (这方面涉及到矩阵的乘法运算)可以参考:矩阵的数学知识--维基百科

图形处理所用到的是一个 3*3 的矩阵:

矩阵的这些参数分别控制的效果如下:

我们知道一张图片是由像素点组成的,我们对每个像素点进行位移,那么图片也就会跟着位移了,所以看一下矩阵怎么对一个像素点进行作用的。前面讲到屏幕的坐标系中有Z轴的概念,也就是空间的概念,一个点就是(x,y,z) 用矩阵表示就是

然后看平移操作的图例

如图,A点移动到B点,移动的距离分别是 △x,△y 。那么
x=x0+△x

y=y0+△y

用矩阵表示就是:

也就是用我们设定的矩阵去乘以A点,就得到了B点。等号右边最终运算的结果就是

x0+△x

y0+△y

1

就得到了上面的数学式子了。(这里有一个前乘和后乘的概念,我才疏学浅,这里就不讲了,大家可以自己去了解一下)

代码表示

当然这些运算系统已经为我们封装好了

val imageMatrix = Matrix()构建一个单位矩阵。单位矩阵如下

开始我是直接获得Imageview内部的矩阵,发现并不行,后来阅读源码后发现做了判断。

preTranslate方法就是矩阵的前乘。参数分别是 x,y 移动的距离。

这里注意:传感器返回的值是 绕某一轴翘起的角度

比如 x的值表示手机顶部或尾部翘起的高度。当手机绕着X轴倾斜时,该角度值发生变化,该角度的取值范围是-180~180度。假设手机屏幕朝上水平放在桌子上,如果桌子是完全水平的,该角度值应该是0度。假如从手机顶部开始抬起,直到将手机沿X轴旋转180度(屏幕向下水平放在桌子上),在这个旋转的过程中,该角度值会从0度变化到-180度。也就是说,从手机顶部抬起时,该角度的值会逐渐减少,直到等于-180度;如果从手机底部开始抬起,直到将手机沿X轴旋转180度(屏幕向下水平放在桌子上),该角度的值会从0度变化到180度,也就是说,从手机底部抬起时,该角度的值会逐渐增大,直到等于180度。

那么想象一下,手机底部翘起了,我们想要的则是图片往下移动则得以看到图片上面隐藏的内容。所以传入的参数则是相反的,数值也应该是相反的。 imageMatrix.preTranslate(-y * scale, -x * scale) 这个 scale则是为了增大移动的幅度而乘的。

imageView!!.imageMatrix = imageMatrix 这里是调用了 ImageView的setImageMatrix方法。改变内部的矩阵,达到我们想要的效果。

接下来就是填坑了

第一个是布局文件中android:scaleType="matrix" 看Imageview源码:

源码中针对我们设置的scaleType都有分别的处理,我们设置了matrix的逻辑中 mDrawMatrix = mMatrix; 把我们设置的矩阵赋给了用于绘制的矩阵且没有做其他处理。

而我们调用的setImageMatrix内部是这样的

里面有对参数传入的矩阵与内部的矩阵进行比较和单位矩阵的校验。

最后有一点是边界控制没有做到,有木有大神评论指导一下下。。嘤嘤嘤

参考与感谢

安卓自定义View进阶-Matrix原理

android matrix 最全方法详解与进阶(完整篇)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用Java时,可以使用`SimpleMatrix`库来进行矩阵操作。下面是使用Java和`SimpleMatrix`库实现上述两个问题的示例代码: ```java import org.ejml.simple.SimpleMatrix; public class MatrixTransformation { public static void main(String[] args) { // 问题1:将结果点坐标(1269.726, 183.1904)转换为原始坐标(1269, 183) SimpleMatrix matrix = new SimpleMatrix(new double[][]{{0.999, 0.0208, 1177.7572}, {-0.0208, 0.999, 0.9186}}); SimpleMatrix resultPoint = new SimpleMatrix(new double[][]{{1269.726}, {183.1904}, {1}}); SimpleMatrix inverseMatrix = matrix.invert(); SimpleMatrix originalPoint = inverseMatrix.mult(resultPoint); System.out.println("原始坐标:"); System.out.println(originalPoint.get(0, 0)); System.out.println(originalPoint.get(1, 0)); // 问题2:将点坐标(1269, 183)乘以矩阵得到的结果 SimpleMatrix point = new SimpleMatrix(new double[][]{{1269}, {183}, {1}}); SimpleMatrix transformedPoint = matrix.mult(point); System.out.println("变换后的点坐标:"); System.out.println(transformedPoint.get(0, 0)); System.out.println(transformedPoint.get(1, 0)); } } ``` 这段代码使用了`SimpleMatrix`库来进行矩阵的求逆和乘法运算。在第一个问题中,我们将结果点坐标和矩阵进行逆矩阵乘法运算,得到原始坐标。在第二个问题中,我们将点坐标与矩阵进行乘法运算,得到变换后的点坐标。 请确保在运行代码之前,已经安装了`SimpleMatrix`库,并将其添加到Java项目的类路径中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值