本来之前还想实现切换前后摄像头功能的,但ARcore有些功能只能用后置摄像头,于是作罢,来实现相册功能。
因为要调用安卓的SDK给Unity使用,需要AndroidStudio导出arr包。这一过程极其折磨,各种莫名其妙的错误。
首先创建一个Android空白工程,注意因为是要把编译出的arr包导入Unity工程,所以工程的包名要一致。
红框里是包名,要和Unity工程的一致。
(1)把Unity引擎目录下的classes.jar文件拷贝至Android Studio工程中的libs目录
版本不同有所区别
Unity版本为5.0之前时,classes.jar的路径:
unity的安装路径\Editor\Data\PlaybackEngines\androidplayer\bin下的classes.jar
Unity版本为5.0及其之后时,classes.jar的路径:
unity的安装路径\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono
\Release\Classes
(2)右击"Project"视图,打开"Open Module Settings", 添加“classes.jar”依赖
注意:红框选择的是Compile only, 而不是Implemetation,这样选择的原因是最后我们打包出来的aar文件将不会包含这个jar包,如果使用Implemetation 将会把这个jar放到最后打出来的aar包中,我们得手动删除掉,不然我们打Apk的时候会出错,因为unity会使用自己的这个jar包。
1.将com.android.application 改为 com.android.library
2.将applicationId “com.qxlz.ARMuseum” 删除掉
然后修改完后编译Gradle
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.qxlz.ARMeseum">
<application
android:icon="@drawable/ic_launcher_background"
android:label="@string/app_name">
<meta-data
android:name="com.google.android.actions"
android:resource="@xml/provider_paths" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 连接互联网的权限 -->
</application>
<uses-permission android:name="android.permission.INTERNET" /> <!-- SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import android.util.Log;
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
public class MainActivity extends UnityPlayerActivity {
private static final int TAKE_PHOTO = 1;
private static final int OPEN_GALLERY = 2;
private static final int CROP_PHOTO = 3;
private Uri mPhotoUri;
private Uri mCropPhotoUri;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
public void TakePhoto(){
mPhotoUri = GetUri(CreateFile("temp.png"));
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mPhotoUri);
startActivityForResult(intent, TAKE_PHOTO);
}
//调用相册
public void OpenGallery()
{
Intent intent = new Intent(Intent.ACTION_PICK,null);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*");
startActivityForResult(intent, OPEN_GALLERY);
}
private Uri GetUri(File file)
{
Uri uri;
if(Build.VERSION.SDK_INT >= 24)
{
uri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", file);
}
else
{
uri = Uri.fromFile(file);
}
return uri;
}
private File CreateFile(String name)
{
File file = new File(Environment.getExternalStorageDirectory(), name);
try
{
if(file.exists())
{
file.delete();
}
file.createNewFile();
}catch(IOException e)
{
e.printStackTrace();
}
return file;
}
private void StartCrop(Uri inputUri)
{
mCropPhotoUri = Uri.fromFile(CreateFile("tempCrop.png"));
Intent intent = new Intent("com.android.camera.action.CROP");
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(inputUri, "image/*");
intent.putExtra("crop", "true");
// aspectX aspectY 是宽高的比例
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// outputX outputY 是裁剪图片宽高
intent.putExtra("outputX", 300);
intent.putExtra("outputY", 300);
intent.putExtra("scale", true);
intent.putExtra("return-data", false);
intent.putExtra("noFaceDetection", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mCropPhotoUri);
startActivityForResult(intent, CROP_PHOTO);
}
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if(resultCode == Activity.RESULT_CANCELED)
{
Log.d("unity","user cancel operator!!");
return;
}
switch (requestCode)
{
case TAKE_PHOTO:
{
StartCrop(mPhotoUri);
}
break;
case OPEN_GALLERY:
{
Uri uri = data.getData();
StartCrop(uri);
}
break;
case CROP_PHOTO:
{
try
{
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(mCropPhotoUri));
FileOutputStream fOut = null;
try
{
String path = "/mnt/sdcard/Android/data/com.qxlz.ATRMuseum/files";
File destDir = new File(path);
if(!destDir.exists())
{
destDir.mkdirs();
}
fOut = new FileOutputStream(path + "/" + "image.png");
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
if(bitmap != null)
{
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);
try {
fOut.flush();
} catch (IOException e) {
e.printStackTrace();
}
try {
fOut.close();
} catch (IOException e) {
e.printStackTrace();
}
UnityPlayer.UnitySendMessage("UnityPlugin","OnGetPhoto", "image.png");
}
}
catch(FileNotFoundException e)
{
e.printStackTrace();
}
}
break;
}
}
}
点击“Build -> Build APK”,生成aar文件
拷贝*.aar文件跟AndroidMainfest.xml到Unity的Assets -> Plugins -> Android目录下
之后就可以写unity脚本了
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TestPlugin : MonoBehaviour
{
public Button mBtnCamera;
public Button mBtnGallery;
public RawImage mImage;
public Text mText;
private Action<byte[]> mPhotoAction;
// Use this for initialization
void Start()
{
mBtnCamera.onClick.AddListener(() =>
{
TakePhoto((datas) =>
{
});
});
mBtnGallery.onClick.AddListener(() =>
{
OpenGallery((datas) =>
{
});
});
}
public void TakePhoto(Action<byte[]> callback)
{
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
jo.Call("TakePhoto");
mPhotoAction = callback;
}
public void OpenGallery(Action<byte[]> callback)
{
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
jo.Call("OpenGallery");
mPhotoAction = callback;
GameObject quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
quad.transform.position = Camera.main.transform.position + Camera.main.transform.forward * 2.5f;
quad.transform.forward = Camera.main.transform.forward;
quad.transform.localScale = new Vector3(1f, texture.height / (float)texture.width, 1f);
Material material = quad.GetComponent<Renderer>().material;
if (!material.shader.isSupported) // happens when Standard shader is not included in the build
material.shader = Shader.Find("Legacy Shaders/Diffuse");
material.mainTexture = texture;
Destroy(quad, 5f);
// If a procedural texture is not destroyed manually,
// it will only be freed after a scene change
Destroy(texture, 5f);
}
void OnGetPhoto(string name)
{
StartCoroutine(LoadPhoto(name));
}
IEnumerator LoadPhoto(string name)
{
mText.text = name;
string path = "file://" + Application.persistentDataPath + "/" + name;
WWW www = new WWW(path);
yield return www;
mImage.texture = www.texture;
if (mPhotoAction != null)
{
mPhotoAction(((Texture2D)mImage.texture).EncodeToPNG());
mPhotoAction = null;
//Destroy(texture);
}
}
}
将脚本挂到场景的对象上,关联上图像和按钮,打包编译好后就实现相册和查看图片功能了
(Error)—— Attribute application@icon value=(@mipmap/ic_launcher) from AndroidManifest.xml:7:9-43
百度了一下,这是一个编译的常见错误,主要是AS的Gradle插件默认会启用Manifest Merger Tool,若Library项目中也定义了与主项目相同的属性(例如默认生成的android:icon和android:theme),则此时会合并失败,并报上面的错误。
修改AndroidManifest.xml第2行的:
xmlns:tools=“http://schemas.android.com/tools”
和application最后一行的
tools:replace=“android:icon, android:theme”