今天有個需求,就是要點擊一個按鈕通過相機(jī)拍攝一張帶位置信息的圖片, 想了下直接打開相機(jī)的保存位置的功能選項即可,可咋才能直接打開這個功能,或者跳轉(zhuǎn)到這個設(shè)置頁面。
搜了搜,貌似沒找到。
最簡單的當(dāng)然是用系統(tǒng)相機(jī),直接跳到系統(tǒng)拍照界面,方便省事拉。可這個貌似沒有太多 可以設(shè)置的地方
后來想自己用camera這個東西吧,我看這里可以setParams,而params里是有經(jīng)緯度參數(shù)可以設(shè)置的。
就想著不用系統(tǒng)的,自己弄個頁面來拍照吧,順道把信息放進(jìn)去。
當(dāng)然這個是可行的,我們這里沒太大需求,我就直接跳到相機(jī)了,省事。
關(guān)鍵是用camera還得分兩種api21以上還得camera2.最討厭這種適配的了。也許以后最低api21了可以再弄這種吧。
注意事項
ExifInterface only supports saving attributes on JPEG formats.
這玩意只支持jpg的格式,png不支持的,
后來就想著能不能直接給圖片添加信息,還真找到了
https://blog.csdn.net/goumiaoqiong/article/details/52136547
我就正常用系統(tǒng)拍照,然后給圖片文件添加額外信息,下邊代碼是錯誤的
val exif=ExifInterface(mPhotoFile?.absolutePath)
val longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)
val latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)
if(TextUtils.isEmpty(longitude)||TextUtils.isEmpty(latitude)) {
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, "100");
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, "100");
exif.saveAttributes();
}else{
println("==========$longitude======$latitude")
}
結(jié)果看到了如下的錯誤信息
錯誤:Given tag (GPSLongitude) value didn't match with one of expected formats: URATIONAL (guess: USHORT, ULONG)
然后我看了下這個tag的描述,發(fā)現(xiàn)它對格式是有要求的,不是直接存的經(jīng)緯度
/** Type is rational. Format is "num1/denom1,num2/denom2,num3/denom3". */
public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
然后又搜到了這個
http://www.mamicode.com/info-detail-404391.html
復(fù)制了下轉(zhuǎn)換的方法,然后發(fā)現(xiàn)日志里還是提示格式不對,然后我打印了下帶定位的圖片的位置信息,發(fā)現(xiàn)人家最后也是整數(shù),沒有小數(shù)的,我們復(fù)制的那個最后一位還是小數(shù),不符合要求
我這里是適合android的,具體要求還是看參數(shù)的解釋吧
就像上邊貼的Format is "num1/denom1,num2/denom2,num3/denom3" ,這樣的“123/1,27/1,24/1”
既然是分子,分母,應(yīng)該都是整數(shù)了。而且我試了下分子弄成負(fù)數(shù)也提示格式不對。
val exif = ExifInterface(mPhotoFile?.absolutePath)
val longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)
val latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)
if (TextUtils.isEmpty(longitude) || TextUtils.isEmpty(latitude)) {
val jd=123.456789
val wd=33.987654321
val longitude = gpsInfoConvert(jd)
val latitude = gpsInfoConvert(wd)
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, if (jd > 0) "E" else "W")//東經(jīng)西經(jīng)
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF,if (wd > 0) "N" else "S")//北緯,南緯
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, longitude);
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, latitude);
//因為格式化以后的經(jīng)緯度,負(fù)數(shù)都沒了,所以要設(shè)置ref來確定是正的還是負(fù)的。
exif.saveAttributes();
println("change============$longitude====$latitude")
} else {
println("==========$longitude======$latitude")
}
最后的方法,我是根據(jù)下邊這個方法來修改了一下
Location.convert(gpsInfo, Location.FORMAT_SECONDS);
fun gpsInfoConvert(coordinate: Double): String {
var coordinate = coordinate
if (coordinate < -180.0 || coordinate > 180.0 ||
java.lang.Double.isNaN(coordinate)) {
throw IllegalArgumentException("coordinate=$coordinate")
}
val sb = StringBuilder()
if (coordinate < 0) {
coordinate = -coordinate
}
val degrees = Math.floor(coordinate).toInt()
sb.append(degrees)
sb.append("/1,")
coordinate -= degrees.toDouble()
coordinate *= 60.0
val minutes = Math.floor(coordinate).toInt()
sb.append(minutes)
sb.append("/1,")
coordinate -= minutes.toDouble()
coordinate *= 60.0
sb.append(Math.floor(coordinate).toInt())
sb.append("/1")
return sb.toString()
}
坐標(biāo)系統(tǒng) coordinate system
decimal 小數(shù),就是一個double值 -180到180
sexagesimal 六十進(jìn)制,這種格式的 100'22'123.333 這種,其實就是對上邊的double值的小數(shù)部分乘以60取整就是第二位,然后再對剩下的小數(shù)部分乘以60就是第三位
中間插播一下,如何處理旋轉(zhuǎn)圖片
如果你拿到一張旋轉(zhuǎn)的圖片,那么android種顯示的時候得處理
根據(jù)圖片的旋轉(zhuǎn)角度,處理Matrix
來源:https://mp.weixin.qq.com/s/1M6rlgxsZDupkugDePbGZw
private fun loadRotatePicture(imageView: ImageView,uri: Uri){
println("uri====${uri.path}============file path=${UriUtils.getFilePath(baseContext,uri)}")
var bitmap = BitmapFactory.decodeFile(uri.path)
val matrix = genOrientationMatrix(ExifInterface(uri.path))
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
imageView.setImageBitmap(bitmap)
}
fun genOrientationMatrix(exifInterface: ExifInterface): Matrix {
val matrix = Matrix()
try {
var orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
if (orientation > 0) {
orientation--
if (orientation and 0b100 != 0) { //對角線翻轉(zhuǎn)
matrix.postScale(-1.0f, 1.0f)
matrix.postRotate(-90f)
}
if (orientation and 0b010 != 0) { //旋轉(zhuǎn)180度
matrix.postRotate(180f)
}
if (orientation and 0b001 != 0) { //水平翻轉(zhuǎn)
matrix.postScale(-1.0f, 1.0f)
}
}
return matrix
} catch (e: IOException) {
e.printStackTrace()
}
return matrix
}
先說第一種直接調(diào)用系統(tǒng)相機(jī)拍照
這里也有分兩種,一種是圖片保存在我們提供的File里【原圖大小】,一種是返回一個bitmap【小圖】
布局文件很簡單,2個按鈕測試兩種方法,一個imageview顯示結(jié)果
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".camera.ActivityCapturePic">
<include layout="@layout/include_toolbar" />
<Button
android:id="@+id/btn_capture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:text="capture"
android:onClick="startCamera"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_capture2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:text="capture2"
android:onClick="startCamera2"
app:layout_constraintLeft_toRightOf="@+id/btn_capture"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_capture" />
</android.support.constraint.ConstraintLayout>
代碼也簡單
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_capture_pic)
defaultSetTitle("camera")
requestCamera()
}
companion object {
private val REQUEST_SAVE_TO_FILE = 123//將圖片保存在我們提供的file里
private val REQUEST_RETURN_BITMAP_DATA = 124//返回bitmap的data數(shù)據(jù)
}
var fileUri: Uri? = null;
var mPhotoFile: File? = null
/**save picture to our provided file*/
fun startCamera(v: View) {
mPhotoFile = File(getExternalFilesDir("externalfilespath"), "${System.currentTimeMillis()}.jpg")
val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//參數(shù)1 上下文, 參數(shù)2 Provider主機(jī)地址和清單文件中保持一致 參數(shù)3 共享的文件
fileUri = FileProvider.getUriForFile(baseContext, "com.charlie.fileProvider", mPhotoFile!!)
//添加這一句表示對目標(biāo)應(yīng)用臨時授權(quán)該Uri所代表的文件
captureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
} else{
fileUri = Uri.fromFile(mPhotoFile)
}
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri)
startActivityForResult(captureIntent, REQUEST_SAVE_TO_FILE)
}
/**return bitmap data*/
fun startCamera2(v: View) {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(intent, REQUEST_RETURN_BITMAP_DATA)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != Activity.RESULT_OK) {
return
}
when (requestCode) {
REQUEST_SAVE_TO_FILE -> {
iv_result.setImageURI(fileUri)
mPhotoFile?.absolutePath?.apply {
addGPSInfo(this)
}
}
REQUEST_RETURN_BITMAP_DATA -> {
// content://media/external/images/media/98430
data?.apply {
val bitmap2 = data.getParcelableExtra("data") as Bitmap
iv_result.setImageBitmap(bitmap2)
val filePath=UriUtils.getFilePath(baseContext,data.data)
filePath?.apply {
println("file path===============${filePath}")
addGPSInfo(filePath)
}
}
}
}
}
private fun addGPSInfo(path:String){
val exif = ExifInterface(path)//根據(jù)文件完整名字獲取一個exif實例
val longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)
val latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)
if (TextUtils.isEmpty(longitude) || TextUtils.isEmpty(latitude)) {
val jd = 123.456789
val wd = 33.987654321
val longitude = gpsInfoConvert(jd)
val latitude = gpsInfoConvert(wd)
//設(shè)置一下4個gps相關(guān)屬性
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, if (jd > 0) "E" else "W")
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, if (wd > 0) "N" else "S")
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, longitude);
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, latitude);
exif.saveAttributes();//保存屬性到文件
} else {
println("==========$longitude======$latitude")
}
}
fun gpsInfoConvert(coordinate: Double): String {
var coordinate = coordinate
if (coordinate < -180.0 || coordinate > 180.0 ||
java.lang.Double.isNaN(coordinate)) {
throw IllegalArgumentException("coordinate=$coordinate")
}
val sb = StringBuilder()
if (coordinate < 0) {//符號位丟掉
coordinate = -coordinate
}
val degrees = Math.floor(coordinate).toInt()
sb.append(degrees)
sb.append("/1,")
coordinate -= degrees.toDouble()
coordinate *= 60.0
val minutes = Math.floor(coordinate).toInt()
sb.append(minutes)
sb.append("/1,")
coordinate -= minutes.toDouble()
coordinate *= 60.0
sb.append(Math.floor(coordinate).toInt())
sb.append("/1")
return sb.toString()
}
看下 "return-data"為true的情況返回的intent里都有啥
println("onActivityResult===========${data}")
println("data===================${data.getParcelableExtra<Parcelable>("data")}")
println("bounds===============${data.extras}")
onActivityResult===========Intent { act=inline-data dat=content://media/external/images/media/98430 flg=0x1 (has extras) }
data===================android.graphics.Bitmap@cf2ee70
bounds===============Bundle[{data=android.graphics.Bitmap@cf2ee70, bitmap-data=true}]
intent的extras也就是bounds里有2個,一個data就是bitmap,另外一個bitmap-data就是個true。
然后看下整個intent的打印結(jié)果,可以看到兩邊有個uri =dat=content://media/external/images/media/98430
這個就是小圖的uri了,我們可以根據(jù)小圖找到大圖
百度搜了個帖子https://blog.csdn.net/smileiam/rss/list
其實以前用過一個庫,里邊代碼也差不多,也有uri轉(zhuǎn)換的適配代碼的,那庫不在手邊。
工具類如下
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.provider.DocumentsContract
import android.content.ContentUris
import android.os.Environment
import android.support.annotation.RequiresApi
import android.graphics.BitmapFactory
import android.graphics.Bitmap
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
object UriUtils {
fun getFilePath(context: Context, uri: Uri): String? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return getPath_above19(context, uri)
} else {
return getFilePath_below19(context, uri)
}
}
fun getFilePath_below19(context: Context, uri: Uri): String? {
return getDataColumn(context, uri, null, null)
}
@RequiresApi(Build.VERSION_CODES.KITKAT)
private fun getPath_above19(context: Context, uri: Uri): String? {
val pathHead = "file:///"
// 1. DocumentProvider
if (DocumentsContract.isDocumentUri(context, uri)) {
// 1.1 ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":")
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
return pathHead + Environment.getExternalStorageDirectory() + "/" + split[1]
}
} else if (isDownloadsDocument(uri)) {
val id = DocumentsContract.getDocumentId(uri)
val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
return pathHead + getDataColumn(context,
contentUri, null, null)
} else if (isMediaDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val type = split[0]
var contentUri: Uri? = null
if ("image" == type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else if ("video" == type) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
} else if ("audio" == type) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val selection = "_id=?"
val selectionArgs = arrayOf(split[1])
return pathHead + getDataColumn(context, contentUri, selection, selectionArgs)
}// 1.3 MediaProvider
// 1.2 DownloadsProvider
} else if ("content".equals(uri.scheme!!, ignoreCase = true)) {
return if (isGooglePhotosUri(uri)) {//判斷是否是google相冊圖片
uri.lastPathSegment
} else if (isGooglePlayPhotosUri(uri)) {//判斷是否是Google相冊圖片
getImageUrlWithAuthority(context, uri)
} else {//其他類似于media這樣的圖片,和android4.4以下獲取圖片path方法類似
getFilePath_below19(context, uri)
}
} else if ("file".equals(uri.scheme!!, ignoreCase = true)) {
return pathHead + uri.path!!
}// 3. 判斷是否是文件形式 File
// 2. MediaStore (and general)
return ""
}
private fun getDataColumn(context: Context, uri: Uri?, selection: String?, selectionArgs: Array<String>?): String? {
var cursor: Cursor? = null
var path = ""
try {
val proj = arrayOf(MediaStore.Images.Media.DATA)
//好像是android多媒體數(shù)據(jù)庫的封裝接口,具體的看Android文檔
cursor = context.getContentResolver().query(uri, proj, selection, selectionArgs, null)
cursor?.apply {
//獲得用戶選擇的圖片的索引值
val column_index = getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
//將光標(biāo)移至開頭 ,這個很重要,不小心很容易引起越界
moveToFirst()
//最后根據(jù)索引值獲取圖片路徑 結(jié)果類似:/mnt/sdcard/DCIM/Camera/IMG_20151124_013332.jpg
path = getString(column_index)
}
println("p================$path")
} catch (e: Exception) {
} finally {
cursor?.close()
}
return path
}
/**從相冊中選擇圖片,如果手機(jī)安裝了Google Photo,它的路徑格式如下:
content://com.google.android.apps.photos.contentprovider/0/1/mediakey%3A%2Flocal%253A821abd2f-9f8c-4931-bbe9-a975d1f5fabc/ORIGINAL/NONE/1754758324
用原來的方式獲取是不起作用的,path會是null,我們可以通過下面的形式獲取*/
fun getImageUrlWithAuthority(context: Context, uri: Uri): String? {
var stream: InputStream? = null
if (uri.authority != null) {
try {
stream= context.contentResolver.openInputStream(uri)
val bmp = BitmapFactory.decodeStream(stream)
return writeToTempImageAndGetPathUri(context, bmp).toString()
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
stream?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
return null
}
fun writeToTempImageAndGetPathUri(inContext: Context, inImage: Bitmap): Uri {
val bytes = ByteArrayOutputStream()
inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes)
val path = MediaStore.Images.Media.insertImage(inContext.contentResolver, inImage, "Title", null)
return Uri.parse(path)
}
/**
* @param uri
* The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
private fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
/**
* @param uri
* The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
private fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
/**
* @param uri
* The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
private fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
/**
* 判斷是否是Google相冊的圖片,類似于content://com.google.android.apps.photos.content/...
*/
fun isGooglePhotosUri(uri: Uri): Boolean {
return "com.google.android.apps.photos.content" == uri.authority
}
/**
* 判斷是否是Google相冊的圖片,類似于content://com.google.android.apps.photos.contentprovider/0/1/mediakey:/local%3A821abd2f-9f8c-4931-bbe9-a975d1f5fabc/ORIGINAL/NONE/1075342619
*/
fun isGooglePlayPhotosUri(uri: Uri): Boolean {
return "com.google.android.apps.photos.contentprovider" == uri.authority
}
}
問題
使用模擬器測試的時候,發(fā)現(xiàn)return-data為true的那種,我拍照完點擊ok對號按鈕
這邊接收到的resultCode=0,intent=null,我拍照完點擊cancel叉號按鈕,接收到的resultCode=0,intent=Intent()一個啥都沒有的Intent
完事看下日志
CAM_StateMachine: Failed to process event: com.android.camera.captureintent.event.EventTapOnConfirmPhotoButton@34298f1
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.android.camera2, PID: 12354
java.lang.NullPointerException
at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:210)
at com.google.common.base.Optional.of(Optional.java:85)
at com.android.camera.captureintent.state.StateSavingPicture.onEnter(StateSavingPicture.java:77)
然后看了下出錯的代碼的源碼如下,第四行Optional.of 參數(shù)為空,肯定的啊,我們沒有傳uri的。
Optional<Uri> saveUri = Optional.absent();
final Bundle myExtras = mResourceConstructed.get().getIntent().getExtras();
if (myExtras != null) {//感覺沒有設(shè)置uri的情況下這里不應(yīng)該走。
saveUri = Optional.of((Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT));//of里邊的參數(shù)是null
String cropValue = myExtras.getString("crop");
}
//下邊的有uri的返回的是空的new Intent(),而沒有uri的返回的是bitmap,可現(xiàn)在上邊就掛了
if (saveUri.isPresent()) {
OutputStream outputStream = null;
try {
outputStream = mResourceConstructed.get().getContext().getContentResolver()
.openOutputStream(saveUri.get());
outputStream.write(mPictureData);
outputStream.close();
Log.v(TAG, "saved result to URI: " + saveUri);
return Optional.of((State) StateIntentCompleted.from(
this, mResourceConstructed, new Intent()));
} catch (IOException ex) {
Log.e(TAG, "exception while saving result to URI: " + saveUri, ex);
} finally {
CameraUtil.closeSilently(outputStream);
}
} else {
/** Inline the bitmap into capture intent result */
final Bitmap bitmap = CameraUtil.makeBitmap(
mPictureData, CaptureIntentConfig.INLINE_BITMAP_MAX_PIXEL_NUM);
return Optional.of((State) StateIntentCompleted.from(
this, mResourceConstructed,
new Intent("inline-data").putExtra("data", bitmap)));
}
如果要保證不掛,那么就不能讓方法走到if條件里,也就是getExtras必須為空,那我們跳轉(zhuǎn)的時候啥額外參數(shù)都不能傳,只能這么寫
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(intent, REQUEST_RETURN_BITMAP_DATA)
返回的intent打印如下
Intent { act=inline-data (has extras) },很明顯沒有圖片的地址。
試了下,真的不掛了,返回了一個bitmap,而intent.data返回的uri是空的。查了下相冊里也沒有新的圖片,也不知道是模擬器這樣還是跟手機(jī)有關(guān)。
我三星6.0的測試機(jī)是沒有問題的。返回的bitmap。intent.data是有值的,相冊里有新的圖片的。
第二種,自己用surfaceview來顯示相機(jī)預(yù)覽圖片以及拍照
布局比較簡單,一個surfaceview,2個按鈕,一個用來拍照,一個用來重置,因為拍照完畫面就不動了。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".camera.ActivityCustomCamera">
<!--<include layout="@layout/include_toolbar" />-->
<com.charliesong.demo0327.camera.CameraSurface
android:id="@+id/sf"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_capture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="takePIC"
android:text="capture"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_reset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="resetSurface"
android:text="reset"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
2個點擊事件也簡單,都在自定義的surfaceview里
fun takePIC(v:View){
val file=File(getExternalFilesDir(""),"${System.currentTimeMillis()}.jpg")
sf.takePic(file)
}
fun resetSurface(v:View){
sf.resetThis()
}
自定義的view如下
import android.content.Context
import android.hardware.Camera
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.graphics.ImageFormat
import android.util.AttributeSet
import java.io.File
import java.io.FileOutputStream
class CameraSurface : SurfaceView, SurfaceHolder.Callback2 {
constructor(c: Context) : super(c) {
initHolder()
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
initHolder()
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
initHolder()
}
private fun initHolder() {
val holder = getHolder()
//指定回調(diào)接口
holder.addCallback(this)
}
private var theCamera: Camera? = null
override fun surfaceRedrawNeeded(holder: SurfaceHolder?) {
println("========================surfaceRedrawNeeded")
}
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
println("===================surfaceChanged==$width/$height===========$format")
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
println("=================surfaceDestroyed")
theCamera?.stopPreview()
theCamera = null;
}
override fun surfaceCreated(holder: SurfaceHolder) {
println("====================surfaceCreated")
if (theCamera == null) {
theCamera = Camera.open(1)
}
theCamera?.apply {
val myParameters = getParameters();
val supportPicSize = myParameters.supportedPictureSizes
supportPicSize.forEach {
println("support============${it.width}/${it.height}")
}
val support = myParameters.supportedPreviewSizes
support.forEach {
println("preview size=========${it.width}/${it.height}")
}
val index=Math.min(supportPicSize.size-1,supportPicSize.size/2)
val saveSize=supportPicSize[index]
println("index===$index=====${saveSize.width}/${saveSize.height}")
myParameters.setPictureFormat(ImageFormat.JPEG);
myParameters.set("jpeg-quality", 85);
//需要注意下邊2個size的大小不能隨便改,需要是上邊打印的support的值才可以。
//而且寬高也是當(dāng)前屏幕方向的值,如果屏幕旋轉(zhuǎn)了,寬高值就反過來了。
myParameters.setPreviewSize(640, 480)//如果要改預(yù)覽圖片大小也可以改
myParameters.setPictureSize(saveSize.width, saveSize.height);
myParameters.setGpsLongitude(131.123456)
myParameters.setGpsLatitude(34.5555)
myParameters.setGpsTimestamp(System.currentTimeMillis())
myParameters.setGpsAltitude(521.125)
myParameters.setGpsProcessingMethod("gps")
parameters = myParameters
setPreviewDisplay(holder);
startPreview()
}
}
fun resetThis() {
surfaceCreated(holder)
}
fun takePic(desFile: File) {
theCamera?.takePicture(null, null, object : Camera.PictureCallback {
override fun onPictureTaken(data: ByteArray?, camera: Camera?) {
try {
//弄成bitmap壓縮以后,額外信息就丟失了,而且文件還比直接保存data的大一倍。
// val a = BitmapFactory.decodeByteArray(data, 0, data?.size ?: 0)
// val out = FileOutputStream(desFile)
// val result = a?.compress(Bitmap.CompressFormat.JPEG, 100, out)
val fos = FileOutputStream(desFile)
fos.write(data)
fos.close()
println("f===============${desFile.absolutePath}")
} catch (e: Exception) {
e.printStackTrace()
}
}
})
}
}
模擬器api28問題
發(fā)生異常,在調(diào)用camera的setParameters方法重新設(shè)置參數(shù)的時候,掛了
錯誤日志如下
Camera2-Parameters: set: Incomplete set of GPS parameters provided
而且測試了下就是加了這兩行就掛了,注釋掉就沒事。
myParameters.setGpsLongitude(131.123456)
myParameters.setGpsLatitude(34.5555)
從錯誤日志來看,好像是說數(shù)據(jù)不完整,難道要把其他gps的參數(shù)都添加進(jìn)去嗎?
那就試試,貌似這5個少一個都不行。我測試機(jī)就弄經(jīng)緯度也沒事,模擬器就掛了,不知道是不是和版本有關(guān)
myParameters.setGpsLongitude(131.123456)
myParameters.setGpsLatitude(34.5555)
myParameters.setGpsTimestamp(System.currentTimeMillis())
myParameters.setGpsAltitude(521.125)
myParameters.setGpsProcessingMethod("gps")