Skip to content

Commit 17e5b49

Browse files
authored
feat(ui): update and animate switch camera button (#162)
* feat(ui): animate and update switch camera button * refactor: remove empty function override * feat(ui): use strokes around image preview and flip camera button * docs: update changelog
1 parent 469cc19 commit 17e5b49

File tree

10 files changed

+58
-28
lines changed

10 files changed

+58
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88
### Changed
99
- Swapped positions of camera switch and image preview buttons ([#157])
10+
- Updated and animated the switch camera button
1011

1112
## [1.2.0] - 2025-09-09
1213
### Added

app/src/main/kotlin/org/fossify/camera/activities/MainActivity.kt

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package org.fossify.camera.activities
22

3+
import android.animation.ObjectAnimator
34
import android.annotation.SuppressLint
4-
import android.app.Activity
55
import android.content.Intent
66
import android.content.res.ColorStateList
77
import android.graphics.Bitmap
@@ -15,6 +15,7 @@ import android.widget.LinearLayout
1515
import androidx.constraintlayout.widget.ConstraintSet
1616
import androidx.core.content.ContextCompat
1717
import androidx.core.view.*
18+
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
1819
import androidx.transition.*
1920
import com.bumptech.glide.Glide
2021
import com.bumptech.glide.load.engine.DiskCacheStrategy
@@ -45,11 +46,12 @@ import kotlin.math.abs
4546

4647
class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, CameraXPreviewListener {
4748
private companion object {
48-
const val CAPTURE_ANIMATION_DURATION = 500L
49-
const val PHOTO_MODE_INDEX = 1
50-
const val VIDEO_MODE_INDEX = 0
49+
private const val ANIMATION_DURATION = 500L
50+
private const val PHOTO_MODE_INDEX = 1
51+
private const val VIDEO_MODE_INDEX = 0
5152
private const val MIN_SWIPE_DISTANCE_X = 100
5253
private const val TIMER_2_SECONDS = 2001
54+
private const val SWITCH_CAMERA_ROTATION_ANGLE = 180f
5355
}
5456

5557
private val binding by viewBinding(ActivityMainBinding::inflate)
@@ -372,7 +374,11 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
372374

373375
private fun initButtons() = binding.apply {
374376
timerText.setFactory { layoutInflater.inflate(R.layout.timer_text, null) }
375-
toggleCamera.setOnClickListener { mPreview!!.toggleFrontBackCamera() }
377+
toggleCamera.setOnClickListener {
378+
animateCameraToggle()
379+
mPreview!!.toggleFrontBackCamera()
380+
}
381+
376382
lastPhotoVideoPreview.setOnClickListener { showLastMediaPreview() }
377383

378384
layoutTop.apply {
@@ -423,6 +429,15 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
423429
setTimerModeIcon(config.timerMode)
424430
}
425431

432+
private fun animateCameraToggle() {
433+
ObjectAnimator.ofFloat(binding.toggleCamera, "rotation", 0f, SWITCH_CAMERA_ROTATION_ANGLE)
434+
.apply {
435+
duration = ANIMATION_DURATION
436+
interpolator = FastOutSlowInInterpolator()
437+
start()
438+
}
439+
}
440+
426441
private fun selectTimerMode(timerMode: TimerMode) {
427442
config.timerMode = timerMode
428443
setTimerModeIcon(timerMode)
@@ -565,7 +580,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
565580
runOnUiThread {
566581
if (!isDestroyed) {
567582
val options = RequestOptions()
568-
.centerCrop()
583+
.circleCrop()
569584
.diskCacheStrategy(DiskCacheStrategy.NONE)
570585

571586
Glide.with(this)
@@ -685,10 +700,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
685700
}
686701
}
687702

688-
override fun onChangeCamera(frontCamera: Boolean) {
689-
binding.toggleCamera.setImageResource(if (frontCamera) R.drawable.ic_camera_rear_vector else R.drawable.ic_camera_front_vector)
690-
}
691-
692703
override fun onPhotoCaptureStart() {
693704
toggleActionButtons(enabled = false)
694705
}
@@ -709,7 +720,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
709720

710721
override fun shutterAnimation() {
711722
binding.shutterAnimation.alpha = 1.0f
712-
binding.shutterAnimation.animate().alpha(0f).setDuration(CAPTURE_ANIMATION_DURATION).start()
723+
binding.shutterAnimation.animate().alpha(0f).setDuration(ANIMATION_DURATION).start()
713724
}
714725

715726
override fun onMediaSaved(uri: Uri) {
@@ -719,14 +730,14 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
719730
Intent().apply {
720731
data = uri
721732
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
722-
setResult(Activity.RESULT_OK, this)
733+
setResult(RESULT_OK, this)
723734
}
724735
finish()
725736
} else if (isVideoCaptureIntent()) {
726737
Intent().apply {
727738
data = uri
728739
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
729-
setResult(Activity.RESULT_OK, this)
740+
setResult(RESULT_OK, this)
730741
}
731742
finish()
732743
}
@@ -736,7 +747,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
736747
if (isImageCaptureIntent()) {
737748
Intent().apply {
738749
putExtra("data", bitmap)
739-
setResult(Activity.RESULT_OK, this)
750+
setResult(RESULT_OK, this)
740751
}
741752
finish()
742753
}
@@ -967,7 +978,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
967978
}
968979

969980
if (isImageCaptureIntent()) {
970-
setResult(Activity.RESULT_OK)
981+
setResult(RESULT_OK)
971982
finish()
972983
}
973984
}

app/src/main/kotlin/org/fossify/camera/implementations/CameraXPreviewListener.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ interface CameraXPreviewListener {
1010
fun setCameraAvailable(available: Boolean) {}
1111
fun setHasFrontAndBackCamera(hasFrontAndBack: Boolean)
1212
fun setFlashAvailable(available: Boolean)
13-
fun onChangeCamera(frontCamera: Boolean)
13+
fun onChangeCamera(frontCamera: Boolean) {}
1414
fun shutterAnimation()
1515
fun onMediaSaved(uri: Uri)
1616
fun onImageCaptured(bitmap: Bitmap)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:color="#80FFFFFF">
4+
<item android:id="@android:id/mask">
5+
<shape android:shape="rectangle">
6+
<corners android:radius="100dp" />
7+
<solid android:color="#FFFFFFFF" />
8+
</shape>
9+
</item>
10+
11+
<item>
12+
<shape android:shape="rectangle">
13+
<solid android:color="@android:color/transparent" />
14+
<corners android:radius="100dp" />
15+
<stroke
16+
android:width="2dp"
17+
android:color="#FFFFFFFF" />
18+
</shape>
19+
</item>
20+
</ripple>

app/src/main/res/drawable/ic_camera_front_vector.xml

Lines changed: 0 additions & 3 deletions
This file was deleted.

app/src/main/res/drawable/ic_camera_rear_vector.xml

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960">
2+
<path android:pathData="M480 880q-128 0-230.5-73T104 616q-5-15 3-28t24-18q16-5 30.5 3.5T182 597q36 91 117 147t181 56q86 0 160-42.5T756 640h-76q-17 0-28.5-11.5T640 600q0-17 11.5-28.5T680 560h160q17 0 28.5 11.5T880 600v160q0 17-11.5 28.5T840 800q-17 0-28.5-11.5T800 760v-40q-57 76-141 118t-179 42zm0-720q-86 0-160 42.5T204 320h76q17 0 28.5 11.5T320 360q0 17-11.5 28.5T280 400H120q-17 0-28.5-11.5T80 360V200q0-17 11.5-28.5T120 160q17 0 28.5 11.5T160 200v40q57-76 141-118t179-42q128 0 230.5 73T856 344q5 15-3 28t-24 18q-16 5-30.5-3.5T778 363q-36-91-117-147t-181-56zm0 440q-50 0-85-35t-35-85q0-50 35-85t85-35q50 0 85 35t35 85q0 50-35 85t-85 35z" android:fillColor="#FFFFFFFF"/>
3+
</vector>

app/src/main/res/layout/activity_main.xml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,13 @@
112112
tools:text="00:00"
113113
tools:visibility="visible" />
114114

115-
116115
<ImageView
117116
android:id="@+id/last_photo_video_preview"
118117
android:layout_width="@dimen/icon_size"
119118
android:layout_height="@dimen/icon_size"
119+
android:background="@drawable/camera_button_background"
120120
android:contentDescription="@string/view_last_media"
121-
android:padding="@dimen/medium_margin"
121+
android:padding="@dimen/tiny_margin"
122122
app:layout_constraintBottom_toBottomOf="@id/shutter"
123123
app:layout_constraintEnd_toStartOf="@id/shutter"
124124
app:layout_constraintHorizontal_chainStyle="spread"
@@ -142,9 +142,10 @@
142142
android:id="@+id/toggle_camera"
143143
android:layout_width="@dimen/icon_size"
144144
android:layout_height="@dimen/icon_size"
145+
android:background="@drawable/camera_button_background"
145146
android:contentDescription="@string/toggle_camera"
146-
android:padding="@dimen/normal_margin"
147-
android:src="@drawable/ic_camera_front_vector"
147+
android:padding="@dimen/medium_margin"
148+
android:src="@drawable/ic_flip_camera_vector"
148149
app:layout_constraintBottom_toBottomOf="@id/shutter"
149150
app:layout_constraintEnd_toEndOf="parent"
150151
app:layout_constraintStart_toEndOf="@id/shutter"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
3-
<dimen name="icon_size">64dp</dimen>
3+
<dimen name="icon_size">56dp</dimen>
44
</resources>

app/src/main/res/values/dimens.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
3-
<dimen name="icon_size">56dp</dimen>
3+
<dimen name="icon_size">48dp</dimen>
44
<dimen name="large_icon_size">72dp</dimen>
55
<dimen name="top_icon_size">48dp</dimen>
66
<dimen name="toggle_icon_size">24dp</dimen>

0 commit comments

Comments
 (0)