简介

大家好我是张鹏辉(道长)人如其名,我是天桥上算命的,转发这条博文,接下来一个月会有意想不到的惊喜发生。最近微博上的全景图火了,所以决定实现一下。工程里面图片资源来自网络,如有侵权请联系我,马上删除当然实现的方式很多比如OpenCV、u3d等。这里提供三种方式实现:

  1. OpenGL ES
  2. GoogleCardboard(Google VR)上面的一个集成模块,我们只使用里面展示全景图部分模块
  3. Three.js(利用前端姿势)WebView混合开发三种姿势孰强孰弱,根据需求你们自己判断!我会在结尾给出一些建议,多说无益开撸

先看下三种实现的效果:

1.OpenGL ES

实现后的效果故宫全景

2.Google VR(全景图模块)

第二种Google VR

3.Three.js(利用前端姿势)WebView混合开发

Three.js(利用前端姿势)WebView混合开发

第一种 OpenGL ES


第一种方式使用OpenGL来实现(上面gif图截取因为博客限制上传图片的大小,我压缩了,看起来有些卡其实很流畅的)可以看到支持旋转手机查看、或者拖动图片查看、可以看到右边中心部分有个指示器会随着角度变化而变化并且点击可以还原起始位置。

一.使用

有些小伙伴懒得看原理,直接就想拿来用所以我先说集成方式吧!

Step 1

在build.gradle 文件中添加库依赖:

allprojects {repositories {maven { url 'https://jitpack.io' }}}

Step 2.Add the dependency

在 build.gradle 文件中添加库依赖:

    dependencies {compile 'com.github.CN-ZPH:weibo360panorama:v1.0.1'}

build.gradle 完整代码:

apply plugin: 'com.android.application'android {compileSdkVersion 26buildToolsVersion "26.0.1"defaultConfig {applicationId "com.zph.three360panorama"minSdkVersion 19targetSdkVersion 26versionCode 1versionName "1.0"testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}allprojects {repositories {maven { url 'https://jitpack.io' }}}
}dependencies {compile fileTree(include: ['*.jar'], dir: 'libs')androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {exclude group: 'com.android.support', module: 'support-annotations'})compile 'com.android.support:appcompat-v7:26.+'compile 'com.android.support.constraint:constraint-layout:1.0.2'compile 'com.android.support:design:26.+'compile 'com.github.CN-ZPH:weibo360panorama:v1.0.1'compile 'com.google.vr:sdk-panowidget:1.80.0'testCompile 'junit:junit:4.12'compile files('libs/tbs_sdk_thirdapp_v3.3.0.1045_43300.jar')
}

Step 3.创建布局文件.XML

<com.zph.glpanorama.GLPanoramaandroid:id="@+id/mGLPanorama"android:layout_width="match_parent"android:layout_height="match_parent"></com.zph.glpanorama.GLPanorama>

Step 4.传入你的全景图

*R.drawable.imggugong 这张全景图传到控件里面

public class MainActivity extends AppCompatActivity {private GLPanorama mGLPanorama;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//初始化全景控件mGLPanorama= (GLPanorama) findViewById(R.id.mGLPanorama);//传入你的全景图mGLPanorama.setGLPanorama(R.drawable.imggugong);}
}

OK现在你可以测试玩玩了(别用模拟器玩咔咔报错就来找我)。

二.分析

首先我们需要了解全景图是什么东西,全景图是一种广角图。通过全景播放器可以让观看者身临其境地进入到全景图所记录的场景中去,通常标准的全景图是一张2:1的图像,其背后的实质就是等距圆柱投影。等距圆柱投影是一种将球体上的各个点投影到圆柱体的侧面上的一种投影方式,投影完之后再将它展开就是一张2:1的长方形的图像。比较常见的就是应用在地图上的投影。

引用慕课的一张介绍图

得到全景图后那我们就需要展示了,看到旁边地球了吗?

怎么展示呢简单来说就是把全景图片整个贴到一个球体上。
好了知道原理那我们就该考虑在android上怎么实现了,在android中绘制3d图形可以使用OpenGL (就不说OpenGL 基础了想看的自己百度一大堆资料)。

1.绘制球体:

引用tim_shadow大佬的关于全景图一篇文章介绍
在OpenGL ES中基本上所有的立体图像都是通过一个个的小三角形拼接而成我们知道球面上面的每一个点(P(x,y,z))都会满足方程组(球的极坐标方程):
x = r * sin(a) cos(b)
y = r * cos(a)
z = r * sin(a)
sin(b)
其中 r为球的半径,a为线段 OP与 z轴正方向所夹角,b为 OP在xoy平面的投影 OP‘ 与x的正方向所夹角

tim_shadow大佬的示意图

我们可以根据这个方程组,通过控制∠a和∠b的变化,从上到下,逆时针的取得我们需要用来组合称三角形的点,然后我们需要将全景图片上的点与我们在球上面选取的点一一对应起来(注意:球的坐标是3维坐标,图片的坐标是2维坐标)
球上面的点与图片上面的点一一对应起来。

纹理和图片绑定绘制到屏幕上

 int[] textures = new int[1];glGenTextures(1, textures, 0);int textureId = textures[0];glBindTexture(GL_TEXTURE_2D, textureId);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);InputStream is = context.getResources().openRawResource(drawableId);Bitmap bitmapTmp;try {bitmapTmp = BitmapFactory.decodeStream(is);} finally {try {is.close();} catch (IOException e) {e.printStackTrace();}}GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmapTmp, 0);bitmapTmp.recycle();

2.利用传感器让球随着手机转动而转动

第一想到的就是重力感应传感器,可是只能获得我们向那个位置偏移的方向,显然不可能满足我们旋转的需求,使用陀螺仪传感器。
陀螺仪就是内部有一个陀螺,它的轴由于陀螺效应始终与初始方向平行,这样就可以通过与初始方向的偏差计算出实际方向。
陀螺仪对设备旋转角度的检测是瞬时的而且是非常精确的。

1.注册陀螺仪传感器

首先注册陀螺仪传感器根据具体需要自己设置灵敏度,当然越灵敏,越耗电。

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

我这里为了测试设置了SENSOR_DELAY_FASTEST,实际使用建议用SENSOR_DELAY_GAME

    private void initSensor() {sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);gyroscopeSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);sensorManager.registerListener(this, gyroscopeSensor,SensorManager.SENSOR_DELAY_FASTEST);}

2.获得传感器数据

当传感器的值发生变化时,例如磁阻传感器方向改变时会调用OnSensorChanged(). 当传感器的精度发生变化时会调用OnAccuracyChanged()方法。

从 x、y、z 轴的正向位置观看处于原始方位的设备,如果设备逆时针旋转,将会收到正值;否则,为负值
得到两次检测到手机旋转的时间差(纳秒),并将其转化为秒
将手机在各个轴上的旋转角度相加,即可得到当前位置相对于初始位置的旋转弧度,将弧度转化为角度

   @Overridepublic void onSensorChanged(SensorEvent sensorEvent) {if (sensorEvent.sensor.getType() == Sensor.TYPE_GYROSCOPE) {if (timestamp != 0) {final float dT = (sensorEvent.timestamp - timestamp) * NS2S;angle[0] += sensorEvent.values[0] * dT;angle[1] += sensorEvent.values[1] * dT;angle[2] += sensorEvent.values[2] * dT;float anglex = (float) Math.toDegrees(angle[0]);float angley = (float) Math.toDegrees(angle[1]);float anglez = (float) Math.toDegrees(angle[2]);Sensordt info = new Sensordt();info.setSensorX(angley);info.setSensorY(anglex);info.setSensorZ(anglez);Message msg = new Message();msg.what = 101;msg.obj = info;mHandler.sendMessage(msg);}timestamp = sensorEvent.timestamp;}}

3.设置填充球的Y,X的角度

每次获得角度数据后只需要y,x的值计算位移的值

因为全景图上下旋转会翻转整个图所以我这里设置了上下只能偏移50f,如果不限制你可以去掉

mBall.yAngle += dx* 2.0f;这里*2.0也就是陀螺仪传过来的值乘以得出偏移的角度,数值越大,每次偏移更快!

   Sensordt info = (Sensordt) msg.obj;float y = info.getSensorY();float x = info.getSensorX();float dy = y - mPreviousY;// 计算触控笔Y位移float dx = x - mPreviousX;// 计算触控笔X位移mBall.yAngle += dx * 2.0f;// 设置填充椭圆绕y轴旋转的角度mBall.xAngle += dy * 0.5f;// 设置填充椭圆绕x轴旋转的角度if (mBall.xAngle < -50f) {mBall.xAngle = -50f;} else if (mBall.xAngle > 50f) {mBall.xAngle = 50f;}mPreviousY = y;mPreviousX = x;

3.加入手势操控,拖动图片转动

加入手势这里没什么好说的了,就是重写onTouchEvent()方法。
这里唯一要注意的就是,当手指点击屏幕的时候要关闭陀螺仪传感器的监听不然会引起冲突。当手指离开屏幕,重新监听陀螺仪传感器。
和上面也一样只是这里换成获取手指偏移角度,而不是传感器的数值,直接看代码。

  public boolean onTouchEvent(MotionEvent e) {sensorManager.unregisterListener(this);float y = e.getY();float x = e.getX();switch (e.getAction()) {case MotionEvent.ACTION_MOVE:float dy = y - mPreviousYs;// 计算触控笔Y位移float dx = x - mPreviousXs;// 计算触控笔X位移mBall.yAngle += dx * 0.3f;// 设置填充椭圆绕y轴旋转的角度mBall.xAngle += dy * 0.3f;// 设置填充椭圆绕x轴旋转的角度if (mBall.xAngle < -50f) {mBall.xAngle = -50f;} else if (mBall.xAngle > 50f) {mBall.xAngle = 50f;}Log.i("zphsas", "mHandler ***  mPreviousY" + mBall.yAngle);Log.i("zphsas", "mHandler ***  mPreviousx" + mBall.xAngle);rotate();break;case MotionEvent.ACTION_UP:sensorManager.registerListener(this, gyroscopeSensor,SensorManager.SENSOR_DELAY_FASTEST);break;}mPreviousYs = y;// 记录触控笔位置mPreviousXs = x;// 记录触控笔位置return true;}

4.加入指示器

指示器这里弄了一个角标指示当前在全景图的角度,并且点击还原起始角度。
可以想象同样是获取角度,我们直接放在全景图改变的地方,让指示器一起改变,而我们改变的地方只有2个陀螺仪和拖动屏幕。
我这里指示器放了一张图也就是一个 ImageView 控件

指示器img

1.为指示器加入动画跟随全景图一起转

  private void rotate() {RotateAnimation anim = new RotateAnimation(predegrees, -mBall.yAngle,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);anim.setDuration(200);img.startAnimation(anim);predegrees = -mBall.yAngle;//记录这一次的起始角度作为下次旋转的初始角度}

2.点击指示器还原起始位置

当点击还原的时候,我一开始是直接恢复起始位置可是太生硬了,通过获取当前旋转的角度,逆向旋转,慢慢还原,让其有个过渡的效果。
Y轴=旋转的角度-90f(起始角度)/10f(每次偏移多少,经过我多次尝试10f在我的手机上刚刚好);
得到我们总共偏移几次可以复位;
X轴同理,因为我上面限制了X轴的最大偏移,这里就不就算X轴了,不过在完成的同时直接复位X轴。(只是没有过渡的效果),你可以加上。
我设置的起始角度是90f和0f,也就是X,Y轴的起始点
mHandlers.postDelayed(this, 16);
这行代码就是多少毫秒复位一次。

看代码:

private void zero() {yy = (int) ((mBall.yAngle - 90f) / 10f);mHandlers.post(new Runnable() {@Overridepublic void run() {if (yy != 0) {if (yy > 0) {mBall.yAngle = mBall.yAngle - 10f;mHandlers.postDelayed(this, 16);yy--;}if (yy < 0) {mBall.yAngle = mBall.yAngle + 10f;mHandlers.postDelayed(this, 16);yy++;}} else {mBall.yAngle = 90f;}mBall.xAngle = 0f;}});}

至此第一种OpenGL ES方式核心代码分析完毕,有不明白的地方可以找我

第二种 Google VR


第二种也就是谷歌官方为移动平台下VR解决方案,有兴趣的可以点开下面链接玩玩,我们只使用其中全景图模块。

Google VR主页:https://developers.google.com/vr/
Google VR for Android github地址:https://github.com/googlevr/gvr-android-sdk

一.使用

Step 1.Add the dependency

目前GitHub上最新版本号为1.8.0,我这里也用最新的了。
最低支持到 minSdkVersion 19 也就是Android 4.4.0
在 build.gradle 文件中添加库依赖:

    dependencies {compile 'com.google.vr:sdk-panowidget:1.80.0'}

Step 2.创建布局文件.XML

      <com.google.vr.sdk.widgets.pano.VrPanoramaViewandroid:id="@+id/mVrPanoramaView"android:layout_width="match_parent"android:layout_height="250dip"/>

Step 3.AndroidManifest中添加权限

 <!-- These permissions are used by Google VR SDK to get the best Google VR headset profiles. !--><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />//因为全景图较大,所以在application下申请更多空间,但是作为一个有节操的码农建议你不要这么干。<application   android:largeHeap="true"   </application>

Step 4.Activity中初始化组件

//初始化VR图片private void initVrPaNormalView() {mVrPanoramaView = (VrPanoramaView) findViewById(R.id.mVrPanoramaView);paNormalOptions = new VrPanoramaView.Options();paNormalOptions.inputType = VrPanoramaView.Options.TYPE_STEREO_OVER_UNDER;
//      mVrPanoramaView.setFullscreenButtonEnabled (false); //隐藏全屏模式按钮mVrPanoramaView.setInfoButtonEnabled(false); //设置隐藏最左边信息的按钮mVrPanoramaView.setStereoModeButtonEnabled(false); //设置隐藏立体模型的按钮mVrPanoramaView.setEventListener(new ActivityEventListener()); //设置监听//加载本地的图片源mVrPanoramaView.loadImageFromBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.andes), paNormalOptions);//设置网络图片源
//        panoWidgetView.loadImageFromByteArray();}private class ActivityEventListener extends VrPanoramaEventListener {@Overridepublic void onLoadSuccess() {//图片加载成功}@Overridepublic void onLoadError(String errorMessage) {//图片加载失败}@Overridepublic void onClick() {//当我们点击了VrPanoramaView 时候触发            super.onClick();}@Overridepublic void onDisplayModeChanged(int newDisplayMode) {super.onDisplayModeChanged(newDisplayMode);}}

第二种到这里已经可以显示玩玩了,也没什么可分析的,都是官方提供的sdk,会调用相关的方法就好了,具体都有那些方法接口,最好的文档永远都是官方提供的,上面已经给出了链接,最好自己把GitHub上的官方提供的demo拿下来跑一遍,我就不多介绍了。

第三种 Three.js(利用前端姿势)WebView混合开发


Three.js是JavaScript编写的WebGL第三方库。提供了非常多的3D显示功能。
Android下相信很多人都多少做过前端开发,现在很多应用程序都是基于前端H5/RN/小程序等来玩的。
当然我们全景图也可以放到前端来实现,套个WebView利用JavaScript与Android交互来实现一部分功能。

考虑到在多种机型兼容性,还有原生WebView的一些坑,我这里使用腾讯的X5内核的WebView。

一.使用

Step 1.添加x5 SDK

到x5官网下载最新的sdk得到一个jar包
我在这的是3.3.0版本的。
将下载好的jar包放到你的工程libs目录下
在 build.gradle 文件中添加库依赖:

    dependencies {compile files('libs/tbs_sdk_thirdapp_v3.3.0.1045_43300_sharewithdownload_withoutGame_obfs_20170605_170212.jar')}

Step 2.AndroidManifest.xml里加入权限声明

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.READ_PHONE_STATE" />

Step 3.APPAplication中X5内核初始化


public class APPAplication extends Application {@Overridepublic void onCreate() {// TODO Auto-generated method stubsuper.onCreate();//搜集本地tbs内核信息并上报服务器,服务器返回结果决定使用哪个内核。QbSdk.PreInitCallback cb = new QbSdk.PreInitCallback() {@Overridepublic void onViewInitFinished(boolean arg0) {// TODO Auto-generated method stub//x5內核初始化完成的回调,为true表示x5内核加载成功,否则表示x5内核加载失败,会自动切换到系统内核。}@Overridepublic void onCoreInitFinished() {// TODO Auto-generated method stub}};//x5内核初始化接口QbSdk.initX5Environment(getApplicationContext(),  cb);}}

Step 4..创建布局文件.XML

 <com.tencent.smtt.sdk.WebViewandroid:id="@+id/web"android:layout_width="match_parent"android:layout_height="match_parent"></com.tencent.smtt.sdk.WebView>

Step 5.下载Three.js

下载地址:https://threejs.org/
或者去GitHub从我的项目中找今天代码都会放到GitHub上

   <script src="js/three.min.js"></script><script src="js/photo-sphere-viewer.min.js"></script>

Step 6.编写HTML文件

在 assets 目录下创建一个html文件展示全景图
引入Threejs
panorama:'https://gw.alicdn.com/tfs/TB1WSInRFXXXXXlXpXXXXXXXXXX-1200-600.jpg', 这行就是你的全景图地址

你可以使用js交互将你的地址传到HTML上

直接上代码了:

<!DOCTYPE html>
<html><head><meta charset="utf-8" /><title>Photo Sphere Viewer</title><meta name="viewport" content="initial-scale=1.0" /><script src="js/three.min.js"></script><script src="js/photo-sphere-viewer.min.js"></script><style>html, body {margin: 0;width: 100%;height: 100%;overflow: hidden;}#container {width: 100%;height: 100%;}</style></head><body><div id="container"></div><script>var div = document.getElementById('container');var PSV = new PhotoSphereViewer({panorama: 'https://gw.alicdn.com/tfs/TB1WSInRFXXXXXlXpXXXXXXXXXX-1200-600.jpg',container: div,time_anim: false,navbar: true,navbar_style: {backgroundColor: 'rgba(58, 67, 77, 0.7)'},});</script></body>
</html>

Step 7.Activity调用HTML

很简单就是把系统的WebView换成Tencent_Webview其他类似

public class WebViewActivity extends AppCompatActivity {private com.tencent.smtt.sdk.WebView tencent_webview;private String url = "file:///android_asset/admin.html";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_web_view);initView();}@SuppressLint("SetJavaScriptEnabled")private void initView() {tencent_webview = (WebView) findViewById(R.id.web);tencent_webview.loadUrl(url);WebSettings webSettings = tencent_webview.getSettings();webSettings.setJavaScriptEnabled(true);tencent_webview.setWebViewClient(new WebViewClient() {@Overridepublic boolean shouldOverrideUrlLoading(WebView view, String url) {return true;}});}}

最后附上插件的可配置参数:

panorama:必填参数,全景图的路径。
container:必填参数,放置全景图的div元素。
autoload:可选,默认值为true,true为自动调用全景图,false为在后面加载全景图(通过.load()方法)。
usexmpdata:可选,默认值为true,如果Photo Sphere Viewer必须读入XMP数据则为true。
default_position:可选,默认值为{},定义默认的位置,及用户看见的第一个点,例如:{long: Math.PI, lat: Math.PI/2}。
min_fov:可选,默认值为30,观察的最小区域,单位degrees,在1-179之间。
max_fov:可选,默认值为90,观察的最大区域,单位degrees,在1-179之间。
allow_user_interactions:可选,默认值为true,设置为false则禁止用户和全景图交互(导航条不可用)。
tilt_up_max:可选,默认值为Math.PI/2,向上倾斜的最大角度,单位radians。
tilt_down_max:可选,默认值为Math.PI/2,向下倾斜的最大角度,单位radians。
zoom_level:可选,默认值为0,默认的缩放级别,值在0-100之间。
long_offset:可选,默认值为PI/360,mouse/touch移动时每像素经过的经度值。
lat_offset:可选,默认值为PI/180,mouse/touch移动时每像素经过的纬度值。
time_anim:可选,默认值为2000,全景图在time_anim毫秒后会自动进行动画。(设置为false禁用它)
theta_offset:过时的选项,可选,默认值为1440,自动动画时水平方向的速度。
anim_speed:可选,默认值为2rpm,动画的速度,每秒/分钟多少radians/degrees/revolutions。
navbar:可选值,默认为false。显示导航条。
navbar_style:可选值,默认为{}。导航条的自定义样式。下面是可用的样式列表:
backgroundColor:导航条的背景颜色,默认值为rgba(61, 61, 61, 0.5)。
buttonsColor:按钮的前景颜色,默认值为transparent。
activeButtonsBackgroundColor:按钮激活状态的背景颜色,默认值为rgba(255, 255, 255, 0.1)。
buttonsHeight:按钮的高度,单位像素,默认值为20。
autorotateThickness:autorotate图标的厚度,单位像素,默认值为1。
zoomRangeWidth:缩放的范围,单位显示,默认值50。
zoomRangeThickness:缩放的范围的厚度,单位像素,默认值1。
zoomRangeDisk:缩放范围的圆盘直径,单位像素,默认值为7。
fullscreenRatio:全屏图标的比例,默认值为3/4。
fullscreenThickness:全屏图标的厚度,单位像素,默认值为2。
loading_msg:可选,默认值为Loading…,图片加载时的提示文字。
loading_img:可选,默认值为null,在加载时显示的图片的路径。
size:可选,默认值null,全景图容器的最终尺寸。例如:{width: 500, height: 300}。
onready:可选值,默认值为null。当全景图准备就绪并且第一张图片显示时的回调函数。

总结

三种方式都实现完了,不用担心今天所有代码都会放在GitHub上。
三种方式具体你使用哪种我还是没有推荐的
这里只是一张图,你可以多张图实现来完成简单的全景街景功能!点击图片某个区域,跳转到下一个街景的图,包括百度地图里面也是一张张全景图拼接而成。

  • 第一种我会在后续继续完善加入更多的可选参数,你们有兴趣也可以自己优化。
  • 第二种是谷歌VR模块的没什么好说的,毕竟官方俩字就够了。
  • 第三种跨平台最好的,毕竟是个网页。而我们第三种使用了腾讯X5内核来玩,但是还可以在优化,消耗不小,我建议你单独给WebView分配一个进程和你的业务分离。
  • 拿着我的保温杯,泡一杯枸杞,我们下篇文章再会

项目源码下载地址:

https://github.com/CN-ZPH/
觉得不错请点一个star蛤!
有问题下面留言评论,我看到会回复。


作者:张鹏辉_道长链接:http://www.jianshu.com/p/adfab8201660來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Android三种姿势带你玩转360度全景图功能相关推荐

  1. android 手机 拍 全景 java_Android 4.2系统360度全景图拍摄试玩

    上周,谷歌开始销售第一批采用Android 4.2系统的Nexus4手机和Nexus7平板电脑.在全新代号为JellyBean的Android系统中,包括了一个全新的Photo Sphere的拍摄功能 ...

  2. Android App 开发的三种姿势 ~

    点击上方"码农的后花园",选择"星标" 公众号 精选文章,第一时间送达 现在市场上各式各样的App越来越多,其实设计一个App也没有那么难,这期就讲解一下安卓开 ...

  3. Spring中部署Activiti流程定义的三种姿势

    摘要:本文对工作流Activiti框架中流程定义的部署进行了详细说明介绍. 本文分享自华为云社区<项目中工作流部署详细解析!Spring中部署Activiti流程定义的三种姿势>,作者:攻 ...

  4. android圆形点击效果,Android 三种方式实现自定义圆形页面加载中效果的进度条

    [实例简介] Android 三种方式实现自定义圆形页面加载中效果的进度条 [实例截图] [核心代码] ad376a86-a9aa-49bc-8cea-321bcff2c0c3 └── AnimRou ...

  5. 全景效果图html5,利用html5实现的360度全景图浏览(带天地)

    [实例简介]利用html5实现的360度全景图浏览(带天地) [实例截图] [核心代码] var camera, scene, renderer; var texture_placeholder, i ...

  6. Android -- 三种动画(帧动画、View动画、属性动画)

    Android的动画分为了三种, 分别是 帧动画.View动画.属性动画 一:帧动画 帧动画就是顺序播放一组预先定义好的图片,就类似于我们观看视频,就是一张一张的图片连续播放. 帧动画的使用很简单,总 ...

  7. android 三种定位方式

    最近在看android关于定位的方式,查了很多资料,也做了相关实验,在手机上做了测试,下面总结: 一共有三种定位方式,一种是GPS,一种是通过网络的方式,一种则是在基于基站的方式,但是,不管哪种方式, ...

  8. Android三种模拟器介绍

    我的新书<Android App开发入门与实战>已于2020年8月由人民邮电出版社出版,欢迎购买.点击进入详情 文章目录 AVD genymotion MuMu模拟器 AVD AVD也就是 ...

  9. Android三种方式截取任意界面屏幕

    一.使用MediaProjectionManager Android5.0之后,开放截取屏幕的API,也就是利用MediaProjectionManager创建VirtualDisplay,传入与Im ...

最新文章

  1. 秩、标量、矢量、矩阵
  2. 创建MySql用户并给用户授权,设置mariadb默认编码,修改mariadb数据存储目录datadir...
  3. Docker创建虚机和swarm
  4. 【联盛德W806上手笔记】七、I2C
  5. java中Action层、Service层和Dao层的功能区分
  6. 使用listen()和accept()函数
  7. Java思维导图(七)
  8. 【KITTI】KITTI数据集简介(一) — 激光雷达数据
  9. 复杂性,科学,方法论?
  10. Windows 进程激活服务
  11. 听见丨三星Bixby中文(普通话)版正式发布 云端服务商Scalyr获2000万美元A轮融资
  12. 解决:log4j警告:WARN Please initialize the log4j system properly
  13. java implode函数_PHP警告:implode():传递的参数无效
  14. excel建立层级_利用Excel升职加薪——数据分析报告
  15. VueFastDev - 前端快速开发工具 (更新树形选择器)
  16. awk NR详解!awk 的内置变量 NF、NR、FNR、FS、OFS、RS、ORS
  17. 漫画人脸检测 | 全局和局部信息融合的深度神经网络(文末源码)
  18. 杨昕立计算机学院,写在告别之前——那些来自辅导员们的悄悄话
  19. SAP SD VL31N BBP_INB_DELIVERY_CREATE 根据采购订单创建内向交货单
  20. 常用神经网络结构图绘图工具推荐

热门文章

  1. cocos2dx 写的泡泡龙2014
  2. 淘宝店群的失败,标志另一代店群平台的崛起,店群“时代在发展”
  3. 虹膜识别1.opencv3同心圆的提取
  4. 价值4500的国际版多语言点赞抖音分享点赞任务平台源码(十二种语言)
  5. HTML怎么设置自动滚动的图片,转:HTML中让图片滚动的marquee标签的使用方法
  6. [斜率优化] 特别行动队 commando
  7. Pantoea(泛菌属)——肠道内善恶兼备的神秘细菌
  8. HIP HOP 街舞文化
  9. pc端和移动端两套样式在vue中的切换
  10. IDcard 验证代码----新身份证合法性验证