Square Camera View in Android

How to make a square camera look like on Instagram

Photo by Victor Freitas from Pexels

Introduction

Di Instagram ada salah satu fitur yang menarik untuk dibahas yaitu crop image pada gambar yang diupload agar berbentuk petak. Walaupun kelihatan simple ternyata ada alasan dan kebutuhan dimana, agar dari view tampilan aplikasi mereka lebih menarik dilihat dan konsisten. Seperti yang Anda ketahui bahwa ukuran gambar dari setiap foto memiliki varian ukuran yang berbeda-beda dan jika app mereka mengikuti varian-varian ukuran tersebut maka, tampilan di app mereka akan tidak konsisten dikarenakan ukuran gambarnya. Lalu solusinya Instagram membuat cropper image pada gambar sebelum diupload ke aplikasi mereka dimana, tujuannya agar tampilan aplikasi mereka menjadi konsisten dan intinya ukuran gambar yang diupload harus berbentuk petak (square).

Camera View

Untuk menggunakan Camera View pada artikel ini penulis menggunakan library CameraView dari Github.

Contoh Projek

Pada artikel ini penulis akan membuat aplikasi yang akan menampilkan daftar foto yang diambil dari kamera dimana, hasil gambarnya akan berbentuk petak.

Konfigurasi build.gradle

Silakan Anda buat projek baru di Android Studio dan beri nama Example Square Camera View lalu, ubah isi file build.gradle-nya menjadi seperti berikut.

/*
* Created by YSN Studio on 8/11/18 11:00 PM
* Copyright (c) 2018. All rights reserved.
*
* Last modified 8/11/18 11:00 PM
*/

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 27
defaultConfig {
applicationId "com.ysn.examplesquarecamera"
minSdkVersion 19
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
/* Directory lib */
implementation fileTree(include: ['*.jar'], dir: 'libs')

/* Kotlin Android */
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

/* Android support*/
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'com.otaliastudios:cameraview:1.5.1'
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support:exifinterface:27.1.1'

/* Glide */
implementation 'com.github.bumptech.glide:glide:4.7.1'

/* Testing Framework */
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

Untuk versi harap disesuaikan dengan versi terbaru ya.

Konfigurasi strings.xml

Buat file strings.xml dan isi dengan source code berikut.

<resources>
<string name="app_name">Example Square Camera</string>
<string name="image_view_stroke_take_picture">image view stroke take picture</string>
<string name="image_view_solid_take_picture">image view solid take picture</string>
<string name="image_view_take_picture">image view take picture</string>
<string name="image_view_item_photo">image view item photo</string>
</resources>

Konfigurasi dimens.xml

Buat file dimens.xml dan isi dengan source code berikut.

<resources>
<dimen name="margin_16dp">16dp</dimen>
<dimen name="margin_8dp">8dp</dimen>
</resources>

Pada aplikasi ini kita akan membuat 2 activity yakni MainActivity dan TakePictureActivity dimana, fungsinya sebagai berikut.

  • MainActivity berfungsi untuk menampilkan daftar foto yang sudah Anda ambil dari kamera.
  • TakePictureActivity berfungsi untuk mengambil foto dari kamera yang sudah Anda bentuk menjadi square camera view.

Layout MainActivity

Silakan Anda buka file layout activity_main.xml dan isi dengan source code berikut.

<?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"
android:paddingTop="@dimen/margin_16dp"
android:paddingEnd="@dimen/margin_16dp"
android:paddingBottom="@dimen/margin_16dp"
android:paddingStart="@dimen/margin_16dp"
tools:context=".MainActivity">

<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view_photos_activity_main"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<android.support.design.widget.FloatingActionButton
android:id="@+id/floating_action_button_take_picture_activity_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_photo_camera_white_24dp"
app:fabSize="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />

</android.support.constraint.ConstraintLayout>

Pada layout diatas, Anda menggunakan RecyclerView sebagai list yang berfungsi untuk menampilkan daftar foto dan Floating Action Button sebagai action untuk mengambil foto dari kamera yang mana ini nanti akan di-intent ke TakePictureActivity.

Logic MainActivity

Untuk logic-nya silakan Anda buka MainActivity.kt dan isi dengan source code berikut.

package com.ysn.examplesquarecamera

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.DividerItemDecoration
import android.support.v7.widget.LinearLayoutManager
import com.ysn.examplesquarecamera.adapter.AdapterPhotos
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File

class MainActivity : AppCompatActivity() {

private lateinit var photos: MutableList<File>
private lateinit var adapterPhotos: AdapterPhotos

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|| checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
val permissions = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
requestPermissions(permissions, 100)
}
}
floating_action_button_take_picture_activity_main.setOnClickListener {
startActivity(Intent(this@MainActivity, TakePictureActivity::class.java))
}
photos = mutableListOf()
adapterPhotos = AdapterPhotos(photos = photos)
val dividerItemDecoration = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
recycler_view_photos_activity_main.layoutManager = LinearLayoutManager(this)
recycler_view_photos_activity_main.addItemDecoration(dividerItemDecoration)
recycler_view_photos_activity_main.adapter = adapterPhotos
}

override fun onResume() {
super.onResume()
val appName = getString(R.string.app_name)
val directory = File(Environment.getExternalStorageDirectory().path + "/" + appName)
if (!directory.exists()) {
directory.mkdirs()
}
val files = directory.listFiles()
photos.clear()
if (files != null) {
photos.addAll(files)
}
adapterPhotos.refresh(photos = photos)
if (photos.size == 0) {
Snackbar.make(findViewById(android.R.id.content), "No photos available", Snackbar.LENGTH_LONG)
.show()
}
}

}

Adapun logic dari MainActivity.kt adalah sebagai berikut.

  • Anda menyediakan photos dengan tipe objek MutableList dimana, ini nantinya akan Anda gunakan untuk dimasukkan ke objek AdapterPhotos.
  • AdapterPhotos merupakan adapter untuk si RecyclerView dan file ini nanti akan Anda buat.
  • Lalu, di method onCreate Anda ada melakukan permintaan permission untuk menulis file dan membaca file untuk versi Android M.
  • Di method onCreate juga ada Anda set listener on click untuk Floating Action Button dan setup adapter untuk si RecyclerView.
  • Di method onResume Anda ada melakukan refresh data RecyclerView.

AdapterPhotos

Sebelum Anda membuat AdapterPhotos.kt terlebih dahulu Anda harus membuat item layout dengan nama item_photo.xml dimana, isi dari source code-nya adalah sebagai berikut.

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/margin_16dp"
android:paddingBottom="@dimen/margin_16dp"
>

<ImageView
android:id="@+id/image_view_photo_item_photo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/image_view_item_photo"
android:layout_centerInParent="true"
android:adjustViewBounds="true" />

</RelativeLayout>

Pada layout tersebut, Anda hanya menampilkan ImageView sebagai penampil gambar yang sudah tersimpan pada storage Anda.

Sekarang, silakan Anda buat file AdapterPhotos.kt dan isi dengan source code berikut.

package com.ysn.examplesquarecamera.adapter

import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.ysn.examplesquarecamera.R
import java.io.File

class AdapterPhotos (private var photos: MutableList<File>) : RecyclerView.Adapter<AdapterPhotos.ViewHolderPhoto>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderPhoto {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.item_photo, parent, false)
return ViewHolderPhoto(itemView)
}

override fun onBindViewHolder(holder: ViewHolderPhoto, position: Int) {
val photo = photos[position]
holder.bindData(photo = photo)
}

override fun getItemCount(): Int = photos.size

fun refresh(photos: MutableList<File>) {
this.photos = photos
notifyDataSetChanged()
}

inner class ViewHolderPhoto(itemView: View) : RecyclerView.ViewHolder(itemView) {

private val imageView = itemView.findViewById<ImageView>(R.id.image_view_photo_item_photo)

fun bindData(photo: File) {
Glide.with(itemView)
.load(photo)
.into(imageView)
}

}

}

Diatas merupakan file adapter RecyclerView yang biasa dimana, fungsinya cuma untuk menampilkan gambar yang ada dari MutableList photos.

Layout TakePictureActivity

Silakan Anda buat file activity baru dengan nama TakePictureActivity.kt dan layout activity_take_picture.xml. Untuk activity_take_picture.xml silakan Anda isi dengan source code berikut.

<?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=".TakePictureActivity">

<com.otaliastudios.cameraview.CameraView
android:id="@+id/camera_view_activity_take_picture"
android:layout_width="match_parent"
android:layout_height="0dp"
app:cameraCropOutput="true"
app:cameraJpegQuality="100"
app:layout_constraintBottom_toTopOf="@+id/relative_layout_overlay_activity_take_picture"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<RelativeLayout
android:id="@+id/relative_layout_overlay_activity_take_picture"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/camera_view_activity_take_picture">

<RelativeLayout
android:id="@+id/relative_layout_container_activity_take_picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
tools:ignore="UselessParent">

<ImageView
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_centerInParent="true"
android:contentDescription="@string/image_view_stroke_take_picture"
android:src="@drawable/background_circle_stroke" />

<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_centerInParent="true"
android:contentDescription="@string/image_view_solid_take_picture"
android:src="@drawable/background_circle_solid" />

<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_centerInParent="true"
android:contentDescription="@string/image_view_take_picture"
android:src="@drawable/ic_photo_camera_white_24dp" />

</RelativeLayout>

</RelativeLayout>

<RelativeLayout
android:id="@+id/relative_layout_container_progress_dialog_activity_take_picture"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.8"
android:background="@android:color/black" />

<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@drawable/background_rounded_white"
android:padding="@dimen/margin_16dp" />

</RelativeLayout>

</android.support.constraint.ConstraintLayout>

Bisa Anda lihat pada layout diatas, bahwa untuk membuat square camera view penulis memanfaatkan ConstraintLayout untuk membentuk square camera view-nya. Bagi Anda yang belum paham dasar-dasar ConstraintLayout saya pernah menulis artikelnya dan bisa Anda pelajari terlebih dahulu.

Logic TakePictureActivity

Silakan Anda buka TakePictureActivity.kt dan isi dengan source code berikut.

package com.ysn.examplesquarecamera

import android.graphics.Bitmap
import android.os.Bundle
import android.os.Environment
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.view.WindowManager
import com.otaliastudios.cameraview.CameraListener
import com.otaliastudios.cameraview.CameraUtils
import kotlinx.android.synthetic.main.activity_take_picture.*
import java.io.File
import java.io.FileOutputStream

class TakePictureActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_take_picture)
val appName = getString(R.string.app_name)
val directory = Environment.getExternalStorageDirectory().path + "/" + appName
camera_view_activity_take_picture.addCameraListener(object : CameraListener() {
override fun onPictureTaken(jpeg: ByteArray?) {
val fileName = "${System.currentTimeMillis()}_image.jpg"
CameraUtils.decodeBitmap(jpeg) {
val photo = File(directory, fileName)
val fileOutputStream = FileOutputStream(photo)
it.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream)
finish()
}
}
})
relative_layout_container_activity_take_picture.setOnClickListener {
showProgressDialog()
camera_view_activity_take_picture.capturePicture()
}
}

private fun showProgressDialog() {
relative_layout_container_progress_dialog_activity_take_picture.visibility = View.VISIBLE
window
.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
}

override fun onResume() {
super.onResume()
camera_view_activity_take_picture.start()
}

override fun onPause() {
super.onPause()
camera_view_activity_take_picture.stop()
}
}

Adapun logic pada TakePictureActivity.kt adalah sebagai berikut.

  • Di method onCreate, Anda ada menambahkan listener pada CameraView ketika selesai mengambil gambar dimana, didalamnya ketika CameraView selesai mengambil gambar maka, Anda membaca objek bitmap lalu disimpan kedalam penyimpanan file pada device Android-nya dimana, letak direktori-nya ada didalam folder SquareCameraView bila Anda memberikan nama aplikasinya SquareCameraView karena, pada coding diatas, saya mengambilnya dari nilai strings.xml pada field app_name. Dan panggil fungsi finish dimana, setelah selesai mengambil gambarnya app akan kembali ke MainActivity.
  • Lalu di method onResume, Anda ada melakukan start untuk si CameraView dan stop untuk si CameraView di method onPause.

Konfigurasi AndroidManifest.xml

Untuk AndroidManifest.xml silakan Anda buka dan isi dengan source code berikut.

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ysn.examplesquarecamera">

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

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".TakePictureActivity" />
</application>

</manifest>

Output

Adapun output dari contoh projek ini adalah sebagai berikut.

Output Contoh Projek

Dan sekarang coba Anda lihat pada keterangan gambarnya maka, info width dan height sama ukurannya seperti pada gambar berikut.

Ukuran width dan height hasil square camera view

Projek lengkapnya bisa Anda pelajari di repository punya saya di Github.