Fotoğraf çekme

Not: Bu sayfa, kullanımdan kaldırılan Kamera sınıfıyla ilgilidir. KameraX veya belirli kullanım alanlarında Kamera2 kullanmanızı öneririz. Hem CameraX hem de Camera2, Android 5.0 (API düzeyi 21) ve sonraki sürümleri destekler.

Bu derste, çalışma için cihazdaki başka bir kamera uygulamasına yetki vererek fotoğraf çekmeyi öğreteceğiz. (Kendi kamera işlevselliğinizi oluşturmak isterseniz bkz. Kamerayı kontrol etme.)

İstemci uygulamanızı çalıştıran cihazlar tarafından çekilen gökyüzü resimlerini karıştırarak küresel hava durumu haritası oluşturan kitle kaynaklı bir hava durumu hizmeti kullandığınızı varsayalım. Fotoğrafları entegre etmek, uygulamanızın yalnızca küçük bir parçasıdır. Kamerayı yeniden icat etmek yerine, karmaşayı minimum düzeyde kullanarak fotoğraf çekmek istiyorsunuz. Neyse ki, Android destekli çoğu cihazda en az bir kamera uygulaması yüklüdür. Bu derste, sizin için bir fotoğraf çekmeyi öğreneceksiniz.

Kamera özelliğini isteyin

Uygulamanızın temel işlevlerinden biri resim çekmekse uygulamanın Google Play'deki görünürlüğünü kamerası olan cihazlarla sınırlandırın. Uygulamanızın bir kameraya bağlı olduğunun reklamını yapmak için manifest dosyanıza bir <uses-feature> etiketi yerleştirin:

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>

Uygulamanızın çalışması için kamera kullanıyor ancak gerekmiyorsa android:required değerini false olarak ayarlayın. Böylece Google Play, kamerası olmayan cihazların uygulamanızı indirmesine izin verir. Bu durumda, hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) yöntemini çağırarak çalışma zamanında kameranın kullanılabilirliğini kontrol etmek sizin sorumluluğunuzdadır. Kamera kullanılamıyorsa kamera özelliklerinizi devre dışı bırakmanız gerekir.

Küçük resmi al

Fotoğraf çekmenin basitliği uygulamanızın amacının zirvesi değilse muhtemelen resmi kamera uygulamasından geri almak ve bununla bir şeyler yapmak istersiniz.

Android Kamera uygulaması, onActivityResult() öğesine gönderilen iadedeki fotoğrafı "data" anahtarının altında küçük bir Bitmap olarak kodlar.Intent Aşağıdaki kod bu resmi alır ve bir ImageView içinde gösterir.

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        val imageBitmap = data.extras.get("data") as Bitmap
        imageView.setImageBitmap(imageBitmap)
    }
}

Java

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        Bundle extras = data.getExtras();
        Bitmap imageBitmap = (Bitmap) extras.get("data");
        imageView.setImageBitmap(imageBitmap);
    }
}

Not: "data" ürününe ait bu küçük resim, bir simge için uygun olabilir ancak çok daha fazlasını kapsamaz. Tam boyutlu bir resimle ilgilenmek biraz daha fazla çaba gerektirir.

Fotoğrafı tam boyutlu olarak kaydet

Android Kamera uygulaması, kaydedileceği bir dosya verirseniz tam boyutlu bir fotoğrafı kaydeder. Kamera uygulamasının fotoğrafı kaydetmesi gereken tam nitelikli bir dosya adı girmeniz gerekir.

Genel olarak, kullanıcının cihazın kamerasıyla çektiği fotoğrafların tümü, tüm uygulamaların erişebilmesi için cihazda herkese açık harici depolama alanına kaydedilmelidir. Paylaşılan fotoğraflar için doğru dizin, DIRECTORY_PICTURES bağımsız değişkeniyle getExternalStoragePublicDirectory() tarafından sağlanır. Bu yöntemin sağladığı dizin tüm uygulamalar arasında paylaşılır. Android 9 (API düzeyi 28) ve önceki sürümlerde bu dizinde okuma ve yazma işlemleri sırasıyla READ_EXTERNAL_STORAGE ve WRITE_EXTERNAL_STORAGE izinleri gerektirir:

<manifest ...>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>

Android 10 (API düzeyi 29) ve sonraki sürümlerde fotoğraf paylaşmak için doğru dizin MediaStore.Images tablosudur. Uygulamanız yalnızca kullanıcının uygulamanızı kullanarak çektiği fotoğraflara erişmesi gerektiği sürece herhangi bir depolama izni beyan etmeniz gerekmez.

Ancak, fotoğrafların yalnızca uygulamanızda özel kalmasını istiyorsanız bunun yerine Context.getExternalFilesDir() tarafından sağlanan dizini kullanabilirsiniz. Android 4.3 ve önceki sürümlerde bu dizine yazmak için WRITE_EXTERNAL_STORAGE izni de gerekir. Dizine diğer uygulamalar tarafından erişilemediği için Android 4.4'ten itibaren bu izin artık gerekli değildir. Bu yüzden, maxSdkVersion özelliğini ekleyerek iznin yalnızca Android'in alt sürümlerinde istenmesi gerektiğini beyan edebilirsiniz:

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                     android:maxSdkVersion="28" />
    ...
</manifest>

Not: getExternalFilesDir() veya getFilesDir() tarafından sağlanan dizinlere kaydettiğiniz dosyalar, kullanıcı uygulamanızı kaldırdığında silinir.

Dosyanın dizinine karar verdikten sonra, çarpışmaya dayanıklı bir dosya adı oluşturmanız gerekir. Yolu daha sonra kullanmak üzere bir üye değişkenine kaydetmek de isteyebilirsiniz. Aşağıda, yeni bir fotoğraf için tarih-saat damgası kullanarak benzersiz dosya adı döndüren bir yönteme dair örnek bir çözüm verilmiştir. (Bu örnekte, yöntemi bir Context içinden çağırdığınız varsayılmaktadır.)

Kotlin

lateinit var currentPhotoPath: String

@Throws(IOException::class)
private fun createImageFile(): File {
    // Create an image file name
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    return File.createTempFile(
            "JPEG_${timeStamp}_", /* prefix */
            ".jpg", /* suffix */
            storageDir /* directory */
    ).apply {
        // Save a file: path for use with ACTION_VIEW intents
        currentPhotoPath = absolutePath
    }
}

Java

String currentPhotoPath;

private File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    File image = File.createTempFile(
        imageFileName,  /* prefix */
        ".jpg",         /* suffix */
        storageDir      /* directory */
    );

    // Save a file: path for use with ACTION_VIEW intents
    currentPhotoPath = image.getAbsolutePath();
    return image;
}

Fotoğraf için dosya oluşturmak için kullanılabilen bu yöntem sayesinde artık Intent öğesini şu şekilde oluşturup çağırabilirsiniz:

Kotlin

private fun dispatchTakePictureIntent() {
    Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
        // Ensure that there's a camera activity to handle the intent
        takePictureIntent.resolveActivity(packageManager)?.also {
            // Create the File where the photo should go
            val photoFile: File? = try {
                createImageFile()
            } catch (ex: IOException) {
                // Error occurred while creating the File
                ...
                null
            }
            // Continue only if the File was successfully created
            photoFile?.also {
                val photoURI: Uri = FileProvider.getUriForFile(
                        this,
                        "com.example.android.fileprovider",
                        it
                )
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
            }
        }
    }
}

Java

private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    // Ensure that there's a camera activity to handle the intent
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        // Create the File where the photo should go
        File photoFile = null;
        try {
            photoFile = createImageFile();
        } catch (IOException ex) {
            // Error occurred while creating the File
            ...
        }
        // Continue only if the File was successfully created
        if (photoFile != null) {
            Uri photoURI = FileProvider.getUriForFile(this,
                                                  "com.example.android.fileprovider",
                                                  photoFile);
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }
    }
}

Not: content:// URI'si döndüren getUriForFile(Context, String, File) kullanıyoruz. Android 7.0 (API düzeyi 24) ve sonraki sürümleri hedefleyen daha yeni uygulamalar için paket sınırı boyunca bir file:// URI'si geçirmek FileUriExposedException değerine neden olur. Bu nedenle artık resimleri FileProvider kullanarak depolamanın daha genel bir yöntemini sunuyoruz.

Şimdi FileProvider eklentisini yapılandırmanız gerekiyor. Uygulamanızın manifest dosyasında uygulamanıza bir sağlayıcı ekleyin:

<application>
   ...
   <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.example.android.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths"></meta-data>
    </provider>
    ...
</application>

Yetkili dizesinin ikinci bağımsız değişkenle getUriForFile(Context, String, File) ile eşleştiğinden emin olun. Sağlayıcı tanımının meta veri bölümünde, sağlayıcının uygun yolların özel bir kaynak dosyasında (res/xml/file_paths.xml) yapılandırılmasını beklediğini görebilirsiniz. Bu örnek için gereken içerik şu şekildedir:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="my_images" path="Pictures" />
</paths>

Yol bileşeni, Environment.DIRECTORY_PICTURES ile çağrıldığında getExternalFilesDir() tarafından döndürülen yola karşılık gelir. com.example.package.name yerine uygulamanızın gerçek paket adını yazdığınızdan emin olun. external-path dışında kullanabileceğiniz yol belirteçlerinin kapsamlı açıklaması için FileProvider dokümanlarına da göz atın.

Fotoğrafı bir galeriye ekleme

Intent'i kullanarak fotoğraf oluşturduğunuzda, en başta nereye kaydedeceğinizi belirttiğiniz için resminizin nerede olduğunu bilmeniz gerekir. Diğer herkes için, fotoğrafınızı erişilebilir hale getirmenin en kolay yolu, fotoğrafı sistemin Medya Sağlayıcısından erişilebilir hale getirmektir.

Not: Fotoğrafınızı getExternalFilesDir() tarafından sağlanan dizine kaydettiyseniz medya tarayıcı, bu dosyalar uygulamanıza özel olduğundan dosyalara erişemez.

Aşağıdaki örnek yöntem, fotoğrafınızı Medya Sağlayıcısının veritabanına ekleyerek Android Galerisi uygulamasında ve diğer uygulamalarda kullanılabilir hale getirmek için sistemin medya tarayıcısının nasıl çağrılacağını gösterir.

Kotlin

private fun galleryAddPic() {
    Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
        val f = File(currentPhotoPath)
        mediaScanIntent.data = Uri.fromFile(f)
        sendBroadcast(mediaScanIntent)
    }
}

Java

private void galleryAddPic() {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File f = new File(currentPhotoPath);
    Uri contentUri = Uri.fromFile(f);
    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
}

Ölçeklendirilmiş bir resmin kodunu çözme

Sınırlı bellek nedeniyle birden çok tam boyutlu resmi yönetmek zor olabilir. Uygulamanızın yalnızca birkaç resim görüntüledikten sonra belleğinin tükendiğini fark ederseniz JPEG dosyasını, hedef görünümün boyutuyla eşleşecek şekilde önceden ölçeklendirilmiş bir bellek dizisine genişleterek, kullanılan dinamik yığın miktarını önemli ölçüde azaltabilirsiniz. Aşağıdaki örnek yöntem bu tekniği göstermektedir.

Kotlin

private fun setPic() {
    // Get the dimensions of the View
    val targetW: Int = imageView.width
    val targetH: Int = imageView.height

    val bmOptions = BitmapFactory.Options().apply {
        // Get the dimensions of the bitmap
        inJustDecodeBounds = true

        BitmapFactory.decodeFile(currentPhotoPath, bmOptions)

        val photoW: Int = outWidth
        val photoH: Int = outHeight

        // Determine how much to scale down the image
        val scaleFactor: Int = Math.max(1, Math.min(photoW / targetW, photoH / targetH))

        // Decode the image file into a Bitmap sized to fill the View
        inJustDecodeBounds = false
        inSampleSize = scaleFactor
        inPurgeable = true
    }
    BitmapFactory.decodeFile(currentPhotoPath, bmOptions)?.also { bitmap ->
        imageView.setImageBitmap(bitmap)
    }
}

Java

private void setPic() {
    // Get the dimensions of the View
    int targetW = imageView.getWidth();
    int targetH = imageView.getHeight();

    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;

    BitmapFactory.decodeFile(currentPhotoPath, bmOptions);

    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.max(1, Math.min(photoW/targetW, photoH/targetH));

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(currentPhotoPath, bmOptions);
    imageView.setImageBitmap(bitmap);
}