高仿贝壳找房之使用陀螺仪移动图片Drawable

code小生,一个专注 Android 领域的技术分享平台

作者:r17171709
地址:https://www.jianshu.com/p/206713510003
声明:本文来自 r17171709 投稿,转发等请联系原作者授权


今年地产行业兴起VR看房这种模式以提升购房人在带看过程中的体验。近期没有打算购房的朋友可能不知道什么叫VR看房,这里简单做个普及:
这个技术主要有三项核心功能:VR看房、VR讲房、VR带看
VR看房是一种沉浸式看房体验,购房人点击APP上的VR房源,轻触屏幕任意处即可获得包括房屋真实空间的尺寸、朝向、远近等深度信息
VR讲房是在VR看房的基础上,增加语音讲解服务功能。房产经纪人会将房屋最为核心的优势、特色还有一些不足进行主动讲解并录入到系统中。这些录音会随着购房人浏览房源时到达相应位置而进行播放。这个感觉就类似于参观博物馆时配发的讲解器一样,每到一个地点就自动触发语音播放
VR带看则是一种全新的交互场景体验,打破了传统线上看房的固定限制。与伪全息或3D看房不同的是,VR带看可以随意调整自己的位置和视角,获得不同视觉和看房体验,以求达到用户最大的满意度。购房人可与经纪人提前预约看房时间,并实时连线进行交互,改变了传统线上看房“异步传输”的现状(即拍摄后完全没有任何同步互动和反馈)


以上只是一个新概念的普及。本文不会教你如何实现VR看房的功能,因为,我也不会。。。那能教你什么呢?使用陀螺仪移动图片Drawable。打开贝壳找房App就能看到文中所示的效果。

640

VR看房入口效果


同为地产企业,最近有同事也在开发这个功能,我也顺带学习下这个效果是如何实现的。两年多前已经有大神在github上分享了利用陀螺仪让ImageView自动滚动的功能,功能相对比较单一,只能横或纵方向滚动,链接地址为PanoramaImageView。随后偶然看到贝壳的小伙也基于那个项目对其进行功能扩展,实现了多方向自动滚动的功能,链接地址为GyroscopeImageDemo。两个项目都很优秀,那我们就跟随大神们的脚步,来一步一步分析这个功能是如何实现的吧
本文涉及到的代码都在github上,欢迎star、fork
https://github.com/r17171709/android_demo/tree/master/GyroscopeImageDemo


预备知识

Android系统内置很多传感器组件,陀螺仪就是其中一个。陀螺仪的轴由于陀螺效应始终与初始方向平行,这样就可以通过与初始方向的偏差计算来出实际方向。角度需要角速度与时间积分计算,得到的角度变化量与初始角度相加,就得到目标角度,其中积分时间Dt越小,输出角度越准。
看起来好像很复杂,但实际上到Android Api已经为你封装的好好的。你只要继承SensorEventListener,并将其绑定在SensorManager上,即可让你妥妥的可以实现角度计算功能

SensorManager sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME)

精度与耗电量成正比,所以我们也要选择适合的传感器精度才行。内置四种精度分别为

SensorManager.SENSOR_DELAY_FASTEST(0微秒):最快。最低延迟,一般不是特别敏感的处理不推荐使用,该模式可能在成手机电力大量消耗,由于传递的为原始数据,诉法不处理好会影响游戏逻辑和UI的性能
SensorManager.SENSOR_DELAY_GAME(20000微秒):游戏。游戏延迟,一般绝大多数的实时性较高的游戏都是用该级别
SensorManager.SENSOR_DELAY_NORMAL(200000微秒):普通。标准延时,对于一般的益智类或EASY级别的游戏可以使用,但过低的采样率可能对一些赛车类游戏有跳帧现象
SensorManager.SENSOR_DELAY_UI(60000微秒):用户界面。一般对于屏幕方向自动旋转使用,相对节省电能和逻辑处理,一般游戏开发中不使用

最后就是监听回调。通过两次检测到手机旋转的时间差进行角度累加计算

class GyroscopeAngel : SensorEventListener {
    private var timestamp = 0.toLong()

    private val NS2S = 1.0f / 1000000000.0f

    private val angle = longArrayOf(0.toLong(), 0.toLong(), 0.toLong())

    override fun onAccuracyChanged(p0: Sensor?, p1: Int) {

    }

    override fun onSensorChanged(p0: SensorEvent?) {
        if (p0?.sensor?.type == Sensor.TYPE_GYROSCOPE) {
            if (timestamp == 0L) {
                // 从 x、y、z 轴的正向位置观看处于原始方位的设备,如果设备逆时针旋转,将会收到正值;否则,为负值
                timestamp = p0.timestamp
                return
            }
            // 得到两次检测到手机旋转的时间差(纳秒),并将其转化为秒
            val dT = (p0.timestamp -timestamp) * NS2S
            // 将手机在各个轴上的旋转角度相加,即可得到当前位置相对于初始位置的旋转弧度
            angle[0] += (p0.values[0] * dT).toLong()
            angle[1] += (p0.values[1] * dT).toLong()
            angle[2] += (p0.values[2] * dT).toLong()
            // 将弧度转化为角度
            val anglex = Math.toDegrees(angle[0].toDouble()).toFloat()
            val angley = Math.toDegrees(angle[1].toDouble()).toFloat()
            val anglez = Math.toDegrees(angle[2].toDouble()).toFloat()
            timestamp = p0.timestamp
        }
    }
}

以上代码就是我们今天传感器部分代码的原型,我们在此基础上将它与ImageView关联起来

可移动的Drawable

Drawable在ImageView怎么移动呢?其实很简单,就在onDraw的时候直接移动canvas即可

canvas?.save()
canvas?.translate(currentOffsetX, currentOffsetY)
super.onDraw(canvas)
canvas?.restore()

这里面还有一个别别窍的地方。我们要注意一下ScaleType。ImageView的ScaleType类别很多,但是只有ScaleType.Center是以原图的几何中心点和ImageView的几何中心点为基准,按图片原来的尺寸居中显示并不剪裁。这样的话canvas在移动过程中,之前未显示部分的图片才能被展现出来。如果你选择ScaleType.CENTER_CROP,那多余的部分被剪裁掉,移动过程中就是背景色来占位了
当然这个移动也要有范围约束的,不是随便移的。这里移动范围就是超出控件可视区域部分图片的长与宽

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)

    if (drawable != null) {
        mWidth = MeasureSpec.getSize(widthMeasureSpec) - paddingLeft - paddingRight
        mHeight = MeasureSpec.getSize(heightMeasureSpec) - paddingTop - paddingBottom

        mDrawableWidth = drawable.intrinsicWidth
        mDrawableHeight = drawable.intrinsicHeight

        mMaxOffsetX = Math.abs((mDrawableWidth - mWidth) * 0.5f)
        mMaxOffsetY = Math.abs((mDrawableHeight - mHeight) * 0.5f)
    }
}

我们通过接口,将传感器回调过来的X轴Y轴角度比例传到ImageView中,对视图进行刷新

interface GyroscopeImpl {
    fun updateProgress(progressX: Float, progressY: Float)
}

override fun updateProgress(progressX: Float, progressY: Float) 
{
    this.progressX = progressX
    this.progressY = progressY
    invalidate()
}

最后不要忘记在视图创建和销毁的时候将其在传感器类中进行添加和删除,以免浪费资源

传感器控制类

刚才的预备知识里面已经完成我们大部分的工作了,下面我们就来将这个偏移范围调整一下即可

val dt = (p0.timestamp - mLastTimestamp) * NS2S * 2.0f
views.forEach {
    it.mRotateRadianY += p0.values[1] * dt
    it.mRotateRadianX += p0.values[0] * dt
    if (it.mRotateRadianY > mMaxRotateRadian) 
{
        it.mRotateRadianY = mMaxRotateRadian.toFloat()
    }
    else if (it.mRotateRadianY < -mMaxRotateRadian) {
        it.mRotateRadianY = -mMaxRotateRadian.toFloat()
    }
    if (it.mRotateRadianX > mMaxRotateRadian) {
        it.mRotateRadianX = mMaxRotateRadian.toFloat()
    }
    else if (it.mRotateRadianX < -mMaxRotateRadian) {
        it.mRotateRadianX = -mMaxRotateRadian.toFloat()
    }
    // 注意此处,X与Y方向是反过来的
    it.updateProgress((it.mRotateRadianY / mMaxRotateRadian).toFloat(), (it.mRotateRadianX / mMaxRotateRadian).toFloat())
}

最大弧度区间为(0, π/2],自己转转手机就知道了,移动距离可以自行修改
最后将偏移的比例传到ImageView中,进行刷新视图即可

额外的知识

  1. 网络加载图片框架
    框架很多,对于一般情况下的使用都问题不大,但在这个场景下坑还是蛮多的。首先是Fresco,这玩意的ScaleType.Center与ImageView的不同,图片被剪裁了,玩不了。Glide4也不行,但是Glide3是可以的。Picasso没问题

  2. RecyclerView上的使用
    在RecyclerView上使用是没有问题的,如果你想横向当Banner展示而不用ViewPager的话,建议你使用DiscreteScrollView,它将RecyclerView包装成ViewPager,并且提供诸如scrollToPosition、smoothScrollToPosition、getCurrentItem这种ViewPager中的方法
    这是无限循环banner的效果

images.add("http://wx2.sinaimg.cn/large/6e9ad2bdly1fnih8uqgkuj2140140b2b.jpg")
images.add("http://vrlab-public.ljcdn.com//release//vradmin//1000000020129136//images//FF41C450.png")
images.add("http://wx2.sinaimg.cn/large/6e9ad2bdly1fnih8s6on4j21401401kz.jpg")
images.add("http://wx2.sinaimg.cn/large/6e9ad2bdly1fnih8uqgkuj2140140b2b.jpg")
images.add("http://vrlab-public.ljcdn.com//release//vradmin//1000000020129136//images//FF41C450.png")

rv_image.setHasFixedSize(true)
rv_image.scrollToPosition(1)
rv_image.addOnItemChangedListener { _, adapterPosition ->
    if (adapterPosition == 0) {
        rv_image.scrollToPosition(3)
    }
    if (adapterPosition == 4) {
        rv_image.scrollToPosition(1)
    }
}
val adapter = MainAdapter(requireContext(), images)
rv_image.adapter = adapter

来看看我们demo的效果

640

demo


640


  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首家百分百开源房少房产系统网站,帮您一站式快速搭建类似58.链家,安居客网站平台 房少房产系统,全网唯一拥有pc端+手机端内外网erp和外网运营的中介系统,适用于房产门户和联盟的多个场景, 拥有新房、二手房、出租房、小区、问答等多套系统满足各类型房产企业电商化发展需求 ; 产品特色: 网站所有信息和软件系统同步,技术方案成熟稳定,支持各类房产中介业务场景,可以方便的进行网站的管理,实现高度的信息化, 帮您一站式快速搭建类似于链家,58,我爱我家,房多多,Q房网, 房天下,等网站平台,提升了企业品牌形象和服务范围,客户可实现在线的房源查询和发布。 建立自己的运营平台,拥有自主的房产电商平台,不仅能够便捷地接收网络订单,还能挖掘更多的潜在客户; 多区域分站平台,系统后台管理员对某区域绑定相对应子域名就能迅速形成一个新的分站系统,快速跨区域扩张自已的业务。 开拓连锁加盟事业,运用系统超强的会员分配权限,可以提供自主运营与加盟运营相结合模式。加盟商的管理权限可以自由分配。 二次开发成其它电商产品,系统源码提供,丰富优秀的源码能支持您迅速二次开发成您所需求的其它产品平台。 房产电商细分门户,根据运营者的需求,可对家新房,二手房、出租、商业地产(商铺与写字档)、楼盘分销等功能模板单独或组合运营。 平台植入广告,吸引客源,增加企业知名度,树立品牌形象,达到互利共赢,专业的网络维护交给我们,您只管专心做好平台运营。
在Android中,Drawable是一种可绘制的图像对象,可以用于绘制各种形状的图像,如位图、矢量图等。下面是使用Drawable图片的教程: 1. 创建Drawable资源文件 在Android Studio中,可在res/drawable目录下创建Drawable资源文件,支持的文件格式包括PNG、JPG、GIF等。例如,创建一个名为ic_launcher的PNG图片文件,可在drawable目录下创建ic_launcher.png文件。 2. 在布局文件中引用Drawable资源 在布局文件中,可通过ImageView控件引用Drawable资源,例如: ``` <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher"/> ``` 其中,@drawable/ic_launcher表示引用名为ic_launcher的Drawable资源。 3. 在代码中引用Drawable资源 在代码中,可通过Context的getDrawable()方法获取Drawable资源,例如: ``` Drawable drawable = context.getDrawable(R.drawable.ic_launcher); ``` 其中,R.drawable.ic_launcher表示引用名为ic_launcher的Drawable资源的ID。 4. 设置Drawable图片的属性 Drawable图片可以设置不同的属性,例如透明度、边框、大小等。可通过代码方式设置Drawable图片的属性,例如: ``` Drawable drawable = context.getDrawable(R.drawable.ic_launcher); // 设置透明度为50% drawable.setAlpha(128); // 设置边框为红色,宽度为2dp drawable.setStroke(2, Color.RED); // 设置大小为50dp * 50dp drawable.setBounds(0, 0, 50, 50); ``` 以上是使用Drawable图片的教程,希望对您有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值