Skip to content

Commit 7039cf2

Browse files
authoredDec 10, 2021
Pinch to zoom (#388)
* Update gradle and libs * Implemented pinch to zoom in example app
1 parent 09a5516 commit 7039cf2

File tree

8 files changed

+149
-43
lines changed

8 files changed

+149
-43
lines changed
 

‎build.gradle

+5-9
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ subprojects {
99
buildscript {
1010
ext {
1111
versions = [
12-
gradle : '4.10.2',
13-
kotlin : '1.3.0',
12+
kotlin : '1.3.50',
1413
code : 1,
1514
name : '1.0.0',
1615
sdk : [
@@ -19,13 +18,13 @@ buildscript {
1918
],
2019
android: [
2120
buildTools: '28.0.3',
22-
appcompat : '1.0.1',
23-
annotation : '1.0.0',
21+
appcompat : '1.1.0',
22+
annotation : '1.1.0',
2423
exifinterface : '1.0.0'
2524
],
2625
rx : [
2726
rxJava1: '1.3.8',
28-
rxJava2: '2.2.3'
27+
rxJava2: '2.2.12'
2928
],
3029
test : [
3130
junit : '4.12',
@@ -38,7 +37,7 @@ buildscript {
3837
jcenter()
3938
}
4039
dependencies {
41-
classpath 'com.android.tools.build:gradle:3.3.0'
40+
classpath 'com.android.tools.build:gradle:3.5.1'
4241
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
4342

4443
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
@@ -57,6 +56,3 @@ task clean(type: Delete) {
5756
delete rootProject.buildDir
5857
}
5958

60-
task wrapper(type: Wrapper) {
61-
gradleVersion = versions.gradle
62-
}

‎fotoapparat/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ dependencies {
3232
implementation "androidx.annotation:annotation:${versions.android.annotation}"
3333
implementation "androidx.exifinterface:exifinterface:${versions.android.exifinterface}"
3434
implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
35-
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'
35+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
3636
testImplementation "junit:junit:${versions.test.junit}"
3737
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${versions.kotlin}"
3838
testImplementation "org.mockito:mockito-core:${versions.test.mockito}"

‎fotoapparat/src/main/java/io/fotoapparat/coroutines/AwaitBroadcastChannel.kt

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package io.fotoapparat.coroutines
22

3-
import kotlinx.coroutines.CompletableDeferred
4-
import kotlinx.coroutines.Deferred
3+
import kotlinx.coroutines.*
54
import kotlinx.coroutines.channels.BroadcastChannel
65
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
76

87
/**
98
* A [ConflatedBroadcastChannel] which exposes a [getValue] which will [await] for at least one value.
109
*/
10+
@ExperimentalCoroutinesApi
1111
internal class AwaitBroadcastChannel<T>(
1212
private val channel: ConflatedBroadcastChannel<T> = ConflatedBroadcastChannel(),
1313
private val deferred: CompletableDeferred<Boolean> = CompletableDeferred()
@@ -31,7 +31,13 @@ internal class AwaitBroadcastChannel<T>(
3131
channel.send(element)
3232
}
3333

34+
override fun cancel(cause: CancellationException?) {
35+
channel.cancel(cause)
36+
deferred.cancel(cause)
37+
}
38+
3439
override fun cancel(cause: Throwable?): Boolean {
35-
return channel.cancel(cause) && deferred.cancel(cause)
40+
deferred.cancel(cause?.message ?:"", cause)
41+
return channel.close(cause)
3642
}
3743
}

‎fotoapparat/src/main/java/io/fotoapparat/view/FocusView.kt

+30-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.content.Context
55
import android.util.AttributeSet
66
import android.view.GestureDetector
77
import android.view.MotionEvent
8+
import android.view.ScaleGestureDetector
89
import android.widget.FrameLayout
910
import io.fotoapparat.hardware.metering.FocalRequest
1011
import io.fotoapparat.hardware.metering.PointF
@@ -15,7 +16,7 @@ import io.fotoapparat.parameter.Resolution
1516
*
1617
* If the camera doesn't support focus metering on specific area this will only display a visual feedback.
1718
*/
18-
class FocusView
19+
open class FocusView
1920
@JvmOverloads constructor(
2021
context: Context,
2122
attrs: AttributeSet? = null,
@@ -25,6 +26,15 @@ class FocusView
2526
private val visualFeedbackCircle = FeedbackCircleView(context, attrs, defStyleAttr)
2627
private var focusMeteringListener: ((FocalRequest) -> Unit)? = null
2728

29+
var scaleListener: ((Float) -> Unit)? = null
30+
var ptrListener: ((Int) -> Unit)? = null
31+
32+
private var mPtrCount: Int = 0
33+
set(value) {
34+
field = value
35+
ptrListener?.invoke(value)
36+
}
37+
2838
init {
2939
clipToPadding = false
3040
clipChildren = false
@@ -38,6 +48,14 @@ class FocusView
3848
@SuppressLint("ClickableViewAccessibility")
3949
override fun onTouchEvent(event: MotionEvent): Boolean {
4050
tapDetector.onTouchEvent(event)
51+
scaleDetector.onTouchEvent(event)
52+
53+
when (event.action and MotionEvent.ACTION_MASK) {
54+
MotionEvent.ACTION_POINTER_DOWN -> mPtrCount++
55+
MotionEvent.ACTION_POINTER_UP -> mPtrCount--
56+
MotionEvent.ACTION_DOWN -> mPtrCount++
57+
MotionEvent.ACTION_UP -> mPtrCount--
58+
}
4159
return true
4260
}
4361

@@ -64,4 +82,15 @@ class FocusView
6482
}
6583

6684
private val tapDetector = GestureDetector(context, gestureDetectorListener)
85+
86+
private val scaleGestureDetector = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
87+
override fun onScale(detector: ScaleGestureDetector): Boolean {
88+
return scaleListener
89+
?.let {
90+
it(detector.scaleFactor)
91+
true
92+
}?: super.onScale(detector)
93+
}
94+
}
95+
private val scaleDetector = ScaleGestureDetector(context, scaleGestureDetector)
6796
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
43
zipStoreBase=GRADLE_USER_HOME
54
zipStorePath=wrapper/dists
5+
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

‎sample/src/main/java/io/fotoapparat/sample/ActivityJava.java

+72-8
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,35 @@
55
import android.view.View;
66
import android.widget.CompoundButton;
77
import android.widget.ImageView;
8-
import android.widget.SeekBar;
8+
import android.widget.TextView;
99
import android.widget.Toast;
1010

1111
import org.jetbrains.annotations.NotNull;
1212
import org.jetbrains.annotations.Nullable;
1313

1414
import java.io.File;
15+
import java.util.Locale;
1516

1617
import androidx.annotation.NonNull;
1718
import androidx.appcompat.app.AppCompatActivity;
1819
import androidx.appcompat.widget.SwitchCompat;
1920
import io.fotoapparat.Fotoapparat;
21+
import io.fotoapparat.capability.Capabilities;
2022
import io.fotoapparat.configuration.CameraConfiguration;
2123
import io.fotoapparat.configuration.UpdateConfiguration;
2224
import io.fotoapparat.error.CameraErrorListener;
2325
import io.fotoapparat.exception.camera.CameraException;
2426
import io.fotoapparat.parameter.ScaleType;
27+
import io.fotoapparat.parameter.Zoom;
2528
import io.fotoapparat.preview.Frame;
2629
import io.fotoapparat.preview.FrameProcessor;
2730
import io.fotoapparat.result.BitmapPhoto;
2831
import io.fotoapparat.result.PhotoResult;
2932
import io.fotoapparat.result.WhenDoneListener;
3033
import io.fotoapparat.view.CameraView;
3134
import io.fotoapparat.view.FocusView;
35+
import kotlin.Unit;
36+
import kotlin.jvm.functions.Function1;
3237

3338
import static io.fotoapparat.log.LoggersKt.fileLogger;
3439
import static io.fotoapparat.log.LoggersKt.logcat;
@@ -57,9 +62,13 @@ public class ActivityJava extends AppCompatActivity {
5762
private boolean hasCameraPermission;
5863
private CameraView cameraView;
5964
private FocusView focusView;
65+
private TextView zoomLvl;
66+
private ImageView switchCamera;
6067
private View capture;
6168

6269
private Fotoapparat fotoapparat;
70+
private Zoom.VariableZoom cameraZoom;
71+
private float curZoom = 0f;
6372

6473
boolean activeCameraBack = true;
6574

@@ -92,6 +101,8 @@ protected void onCreate(Bundle savedInstanceState) {
92101
cameraView = findViewById(R.id.cameraView);
93102
focusView = findViewById(R.id.focusView);
94103
capture = findViewById(R.id.capture);
104+
zoomLvl = findViewById(R.id.zoomLvl);
105+
switchCamera = findViewById(R.id.switchCamera);
95106
hasCameraPermission = permissionsDelegate.hasCameraPermission();
96107

97108
if (hasCameraPermission) {
@@ -105,7 +116,6 @@ protected void onCreate(Bundle savedInstanceState) {
105116
takePictureOnClick();
106117
switchCameraOnClick();
107118
toggleTorchOnSwitch();
108-
zoomSeekBar();
109119
}
110120

111121
private Fotoapparat createFotoapparat() {
@@ -129,15 +139,66 @@ public void onError(@NotNull CameraException e) {
129139
.build();
130140
}
131141

132-
private void zoomSeekBar() {
133-
SeekBar seekBar = findViewById(R.id.zoomSeekBar);
134-
135-
seekBar.setOnSeekBarChangeListener(new OnProgressChanged() {
142+
private void adjustViewsVisibility() {
143+
fotoapparat.getCapabilities().whenAvailable(new Function1<Capabilities, Unit>() {
136144
@Override
137-
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
138-
fotoapparat.setZoom(progress / (float) seekBar.getMax());
145+
public Unit invoke(Capabilities capabilities) {
146+
Zoom zoom = capabilities != null ? capabilities.getZoom() : null;
147+
if(zoom instanceof Zoom.VariableZoom){
148+
cameraZoom = (Zoom.VariableZoom) zoom;
149+
focusView.setScaleListener(new Function1<Float, Unit>() {
150+
@Override
151+
public Unit invoke(Float aFloat) {
152+
scaleZoom(aFloat);
153+
return null;
154+
}
155+
});
156+
focusView.setPtrListener(new Function1<Integer, Unit>() {
157+
@Override
158+
public Unit invoke(Integer integer) {
159+
pointerChanged(integer);
160+
return null;
161+
}
162+
});
163+
} else {
164+
zoomLvl.setVisibility(View.GONE);
165+
focusView.setScaleListener(null);
166+
focusView.setPtrListener(null);
167+
}
168+
return null;
139169
}
140170
});
171+
if (fotoapparat.isAvailable(front())){
172+
switchCamera.setVisibility(View.VISIBLE);
173+
} else {
174+
switchCamera.setVisibility(View.GONE);
175+
}
176+
}
177+
178+
private void scaleZoom(float scaleFactor){
179+
float plusZoom = 0;
180+
if (scaleFactor < 1) plusZoom = -1 * (1 - scaleFactor);
181+
else plusZoom = scaleFactor - 1;
182+
183+
float newZoom = curZoom + plusZoom;
184+
if (newZoom < 0 || newZoom > 1) return;
185+
186+
curZoom = newZoom;
187+
fotoapparat.setZoom(curZoom);
188+
189+
int progress = Math.round (cameraZoom.getMaxZoom() * curZoom);
190+
int value = cameraZoom.getZoomRatios().get(progress);
191+
192+
float roundedValue = (float)(Math.round(((float)value) / 10f)) / 10f;
193+
194+
zoomLvl.setVisibility(View.VISIBLE);
195+
zoomLvl.setText(String.format(Locale.getDefault(), "%.1f×", roundedValue));
196+
}
197+
198+
private void pointerChanged(int fingerCount){
199+
if(fingerCount == 0) {
200+
zoomLvl.setVisibility(View.GONE);
201+
}
141202
}
142203

143204
private void switchCameraOnClick() {
@@ -180,6 +241,7 @@ public void onClick(View v) {
180241
activeCameraBack ? back() : front(),
181242
cameraConfiguration
182243
);
244+
adjustViewsVisibility();
183245
}
184246
});
185247
}
@@ -223,6 +285,7 @@ protected void onStart() {
223285
super.onStart();
224286
if (hasCameraPermission) {
225287
fotoapparat.start();
288+
adjustViewsVisibility();
226289
}
227290
}
228291

@@ -242,6 +305,7 @@ public void onRequestPermissionsResult(int requestCode,
242305
if (permissionsDelegate.resultGranted(requestCode, permissions, grantResults)) {
243306
hasCameraPermission = true;
244307
fotoapparat.start();
308+
adjustViewsVisibility();
245309
cameraView.setVisibility(View.VISIBLE);
246310
}
247311
}

‎sample/src/main/java/io/fotoapparat/sample/MainActivity.kt

+30-12
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class MainActivity : AppCompatActivity() {
2828
private lateinit var fotoapparat: Fotoapparat
2929
private lateinit var cameraZoom: Zoom.VariableZoom
3030

31+
private var curZoom: Float = 0f
32+
3133
override fun onCreate(savedInstanceState: Bundle?) {
3234
super.onCreate(savedInstanceState)
3335
setContentView(R.layout.activity_main)
@@ -148,8 +150,16 @@ class MainActivity : AppCompatActivity() {
148150
capabilities
149151
?.let {
150152
(it.zoom as? Zoom.VariableZoom)
151-
?.let { zoom -> setupZoom(zoom) }
152-
?: run { zoomSeekBar.visibility = View.GONE }
153+
?.let {
154+
cameraZoom = it
155+
focusView.scaleListener = this::scaleZoom
156+
focusView.ptrListener = this::pointerChanged
157+
}
158+
?: run {
159+
zoomLvl?.visibility = View.GONE
160+
focusView.scaleListener = null
161+
focusView.ptrListener = null
162+
}
153163

154164
torchSwitch.visibility = if (it.flashModes.contains(Flash.Torch)) View.VISIBLE else View.GONE
155165
}
@@ -159,19 +169,27 @@ class MainActivity : AppCompatActivity() {
159169
switchCamera.visibility = if (fotoapparat.isAvailable(front())) View.VISIBLE else View.GONE
160170
}
161171

162-
private fun setupZoom(zoom: Zoom.VariableZoom) {
163-
zoomSeekBar.max = zoom.maxZoom
164-
cameraZoom = zoom
165-
zoomSeekBar.visibility = View.VISIBLE
166-
zoomSeekBar onProgressChanged { updateZoom(zoomSeekBar.progress) }
167-
updateZoom(0)
168-
}
172+
//When zooming slowly, the values are approximately 0.9 ~ 1.1
173+
private fun scaleZoom(scaleFactor: Float) {
174+
//convert to -0.1 ~ 0.1
175+
val plusZoom = if (scaleFactor < 1) -1 * (1 - scaleFactor) else scaleFactor - 1
176+
val newZoom = curZoom + plusZoom
177+
if (newZoom < 0 || newZoom > 1) return
169178

170-
private fun updateZoom(progress: Int) {
171-
fotoapparat.setZoom(progress.toFloat() / zoomSeekBar.max)
179+
curZoom = newZoom
180+
fotoapparat.setZoom(curZoom)
181+
val progress = (cameraZoom.maxZoom * curZoom).roundToInt()
172182
val value = cameraZoom.zoomRatios[progress]
173183
val roundedValue = ((value.toFloat()) / 10).roundToInt().toFloat() / 10
174-
zoomLvl.text = String.format("%.1f ×", roundedValue)
184+
185+
zoomLvl.visibility = View.VISIBLE
186+
zoomLvl.text = String.format("%.1f×", roundedValue)
187+
}
188+
189+
private fun pointerChanged(fingerCount: Int){
190+
if(fingerCount == 0) {
191+
zoomLvl?.visibility = View.GONE
192+
}
175193
}
176194
}
177195

‎sample/src/main/res/layout/activity_main.xml

+1-8
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,6 @@
5353
android:padding="20dp"
5454
tools:ignore="RtlHardcoded" />
5555

56-
<SeekBar
57-
android:id="@+id/zoomSeekBar"
58-
android:layout_width="200dp"
59-
android:layout_height="wrap_content"
60-
android:layout_gravity="center" />
61-
6256
<ImageView
6357
android:id="@+id/switchCamera"
6458
android:layout_width="wrap_content"
@@ -74,8 +68,7 @@
7468
android:id="@+id/zoomLvl"
7569
android:layout_width="wrap_content"
7670
android:layout_height="wrap_content"
77-
android:layout_gravity="center_horizontal|top"
78-
android:layout_marginTop="50dp"
71+
android:layout_gravity="center"
7972
android:textColor="#FFF"
8073
android:textSize="20sp"
8174
tools:text="2.4" />

0 commit comments

Comments
 (0)
Please sign in to comment.