Demo improvements (#499)

* Demo app changes

* Add tests

* Doc changes
pull/501/head
Mattia Iavarone 5 years ago committed by GitHub
parent dc31c40dd9
commit 8d3013ff53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 36
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
  2. 34
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  3. 22
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  4. 274
      demo/src/main/java/com/otaliastudios/cameraview/demo/Control.java
  5. 2
      demo/src/main/java/com/otaliastudios/cameraview/demo/MessageView.java
  6. 444
      demo/src/main/java/com/otaliastudios/cameraview/demo/Option.java
  7. 44
      demo/src/main/java/com/otaliastudios/cameraview/demo/OptionView.java
  8. 0
      demo/src/main/res/layout/option_view.xml
  9. 3
      docs/_posts/2018-12-20-controls.md

@ -561,74 +561,74 @@ public class CameraViewTest extends BaseTest {
@Test @Test
public void testSetFlash() { public void testSetFlash() {
cameraView.set(Flash.TORCH); cameraView.set(Flash.TORCH);
assertEquals(cameraView.getFlash(), Flash.TORCH); assertEquals(cameraView.get(Flash.class), Flash.TORCH);
cameraView.set(Flash.OFF); cameraView.set(Flash.OFF);
assertEquals(cameraView.getFlash(), Flash.OFF); assertEquals(cameraView.get(Flash.class), Flash.OFF);
} }
@Test @Test
public void testSetFacing() { public void testSetFacing() {
cameraView.set(Facing.FRONT); cameraView.set(Facing.FRONT);
assertEquals(cameraView.getFacing(), Facing.FRONT); assertEquals(cameraView.get(Facing.class), Facing.FRONT);
cameraView.set(Facing.BACK); cameraView.set(Facing.BACK);
assertEquals(cameraView.getFacing(), Facing.BACK); assertEquals(cameraView.get(Facing.class), Facing.BACK);
} }
@Test @Test
public void testToggleFacing() { public void testToggleFacing() {
cameraView.set(Facing.FRONT); cameraView.set(Facing.FRONT);
cameraView.toggleFacing(); cameraView.toggleFacing();
assertEquals(cameraView.getFacing(), Facing.BACK); assertEquals(cameraView.get(Facing.class), Facing.BACK);
cameraView.toggleFacing(); cameraView.toggleFacing();
assertEquals(cameraView.getFacing(), Facing.FRONT); assertEquals(cameraView.get(Facing.class), Facing.FRONT);
} }
@Test @Test
public void testSetGrid() { public void testSetGrid() {
cameraView.set(Grid.DRAW_3X3); cameraView.set(Grid.DRAW_3X3);
assertEquals(cameraView.getGrid(), Grid.DRAW_3X3); assertEquals(cameraView.get(Grid.class), Grid.DRAW_3X3);
cameraView.set(Grid.OFF); cameraView.set(Grid.OFF);
assertEquals(cameraView.getGrid(), Grid.OFF); assertEquals(cameraView.get(Grid.class), Grid.OFF);
} }
@Test @Test
public void testSetWhiteBalance() { public void testSetWhiteBalance() {
cameraView.set(WhiteBalance.CLOUDY); cameraView.set(WhiteBalance.CLOUDY);
assertEquals(cameraView.getWhiteBalance(), WhiteBalance.CLOUDY); assertEquals(cameraView.get(WhiteBalance.class), WhiteBalance.CLOUDY);
cameraView.set(WhiteBalance.AUTO); cameraView.set(WhiteBalance.AUTO);
assertEquals(cameraView.getWhiteBalance(), WhiteBalance.AUTO); assertEquals(cameraView.get(WhiteBalance.class), WhiteBalance.AUTO);
} }
@Test @Test
public void testMode() { public void testMode() {
cameraView.set(Mode.VIDEO); cameraView.set(Mode.VIDEO);
assertEquals(cameraView.getMode(), Mode.VIDEO); assertEquals(cameraView.get(Mode.class), Mode.VIDEO);
cameraView.set(Mode.PICTURE); cameraView.set(Mode.PICTURE);
assertEquals(cameraView.getMode(), Mode.PICTURE); assertEquals(cameraView.get(Mode.class), Mode.PICTURE);
} }
@Test @Test
public void testHdr() { public void testHdr() {
cameraView.set(Hdr.ON); cameraView.set(Hdr.ON);
assertEquals(cameraView.getHdr(), Hdr.ON); assertEquals(cameraView.get(Hdr.class), Hdr.ON);
cameraView.set(Hdr.OFF); cameraView.set(Hdr.OFF);
assertEquals(cameraView.getHdr(), Hdr.OFF); assertEquals(cameraView.get(Hdr.class), Hdr.OFF);
} }
@Test @Test
public void testAudio() { public void testAudio() {
cameraView.set(Audio.ON); cameraView.set(Audio.ON);
assertEquals(cameraView.getAudio(), Audio.ON); assertEquals(cameraView.get(Audio.class), Audio.ON);
cameraView.set(Audio.OFF); cameraView.set(Audio.OFF);
assertEquals(cameraView.getAudio(), Audio.OFF); assertEquals(cameraView.get(Audio.class), Audio.OFF);
} }
@Test @Test
public void testVideoCodec() { public void testVideoCodec() {
cameraView.set(VideoCodec.H_263); cameraView.set(VideoCodec.H_263);
assertEquals(cameraView.getVideoCodec(), VideoCodec.H_263); assertEquals(cameraView.get(VideoCodec.class), VideoCodec.H_263);
cameraView.set(VideoCodec.H_264); cameraView.set(VideoCodec.H_264);
assertEquals(cameraView.getVideoCodec(), VideoCodec.H_264); assertEquals(cameraView.get(VideoCodec.class), VideoCodec.H_264);
} }
@Test @Test

@ -769,6 +769,40 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
} }
/**
* Shorthand for the appropriate get* method.
* For example, if control class is a {@link Grid}, this calls {@link #getGrid()}.
*
* @param controlClass desired value class
*/
@SuppressWarnings("unchecked")
@NonNull
public <T extends Control> T get(@NonNull Class<T> controlClass) {
if (controlClass == Audio.class) {
return (T) getAudio();
} else if (controlClass == Facing.class) {
return (T) getFacing();
} else if (controlClass == Flash.class) {
return (T) getFlash();
} else if (controlClass == Grid.class) {
return (T) getGrid();
} else if (controlClass == Hdr.class) {
return (T) getHdr();
} else if (controlClass == Mode.class) {
return (T) getMode();
} else if (controlClass == WhiteBalance.class) {
return (T) getWhiteBalance();
} else if (controlClass == VideoCodec.class) {
return (T) getVideoCodec();
} else if (controlClass == Preview.class) {
return (T) getPreview();
} else if (controlClass == Engine.class) {
return (T) getEngine();
} else {
throw new IllegalArgumentException("Unknown control class: " + controlClass);
}
}
/** /**
* Controls the preview engine. Should only be called * Controls the preview engine. Should only be called

@ -22,14 +22,12 @@ import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.controls.Mode; import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.controls.Preview; import com.otaliastudios.cameraview.controls.Preview;
import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameProcessor;
import com.otaliastudios.cameraview.size.SizeSelectors;
import java.io.File; import java.io.File;
import java.util.List;
public class CameraActivity extends AppCompatActivity implements View.OnClickListener, ControlView.Callback { public class CameraActivity extends AppCompatActivity implements View.OnClickListener, OptionView.Callback {
private CameraView camera; private CameraView camera;
private ViewGroup controlPanel; private ViewGroup controlPanel;
@ -67,9 +65,9 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
controlPanel = findViewById(R.id.controls); controlPanel = findViewById(R.id.controls);
ViewGroup group = (ViewGroup) controlPanel.getChildAt(0); ViewGroup group = (ViewGroup) controlPanel.getChildAt(0);
Control[] controls = Control.values(); List<Option<?>> options = Option.getAll();
for (Control control : controls) { for (Option option : options) {
ControlView view = new ControlView(this, control, this); OptionView view = new OptionView(this, option, this);
group.addView(view, group.addView(view,
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT); ViewGroup.LayoutParams.WRAP_CONTENT);
@ -95,7 +93,7 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
public void onCameraOpened(@NonNull CameraOptions options) { public void onCameraOpened(@NonNull CameraOptions options) {
ViewGroup group = (ViewGroup) controlPanel.getChildAt(0); ViewGroup group = (ViewGroup) controlPanel.getChildAt(0);
for (int i = 0; i < group.getChildCount(); i++) { for (int i = 0; i < group.getChildCount(); i++) {
ControlView view = (ControlView) group.getChildAt(i); OptionView view = (OptionView) group.getChildAt(i);
view.onCameraOpened(camera, options); view.onCameraOpened(camera, options);
} }
} }
@ -220,8 +218,8 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
} }
@Override @Override
public boolean onValueChanged(Control control, Object value, String name) { public <T> boolean onValueChanged(@NonNull Option<T> option, @NonNull T value, @NonNull String name) {
if ((control == Control.WIDTH || control == Control.HEIGHT)) { if ((option instanceof Option.Width || option instanceof Option.Height)) {
Preview preview = camera.getPreview(); Preview preview = camera.getPreview();
boolean wrapContent = (Integer) value == ViewGroup.LayoutParams.WRAP_CONTENT; boolean wrapContent = (Integer) value == ViewGroup.LayoutParams.WRAP_CONTENT;
if (preview == Preview.SURFACE && !wrapContent) { if (preview == Preview.SURFACE && !wrapContent) {
@ -230,10 +228,10 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
return false; return false;
} }
} }
control.applyValue(camera, value); option.set(camera, value);
BottomSheetBehavior b = BottomSheetBehavior.from(controlPanel); BottomSheetBehavior b = BottomSheetBehavior.from(controlPanel);
b.setState(BottomSheetBehavior.STATE_HIDDEN); b.setState(BottomSheetBehavior.STATE_HIDDEN);
message("Changed " + control.getName() + " to " + name, false); message("Changed " + option.getName() + " to " + name, false);
return true; return true;
} }

@ -1,274 +0,0 @@
package com.otaliastudios.cameraview.demo;
import android.graphics.Color;
import androidx.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import com.otaliastudios.cameraview.CameraListener;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.controls.Engine;
import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.controls.Preview;
import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.gesture.GestureAction;
import com.otaliastudios.cameraview.controls.Grid;
import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.VideoCodec;
import com.otaliastudios.cameraview.controls.WhiteBalance;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* Controls that we want to display in a ControlView.
*/
public enum Control {
// Layout
WIDTH("Width", false),
HEIGHT("Height", true),
// Some controls
MODE("Mode", false),
FLASH("Flash", false),
WHITE_BALANCE("White balance", false),
HDR("Hdr", true),
// Engine and preview
ENGINE("Engine", false),
PREVIEW("Preview Surface", true),
// Video recording
VIDEO_CODEC("Video codec", false),
AUDIO("Audio", true),
// TODO audio bitRate
// TODO video bitRate
// They are a bit annoying because it's not clear what the default should be.
// Gestures
PINCH("Pinch", false),
HSCROLL("Horizontal scroll", false),
VSCROLL("Vertical scroll", false),
TAP("Single tap", false),
LONG_TAP("Long tap", true),
// Others
GRID("Grid lines", false),
GRID_COLOR("Grid color", false),
USE_DEVICE_ORIENTATION("Use device orientation", true);
private String name;
private boolean last;
Control(String n, boolean l) {
name = n;
last = l;
}
public String getName() {
return name;
}
public boolean isSectionLast() {
return last;
}
public Collection<?> getValues(CameraView view, @NonNull CameraOptions options) {
switch (this) {
case WIDTH:
case HEIGHT:
View root = (View) view.getParent();
ArrayList<Integer> list = new ArrayList<>();
int boundary = this == WIDTH ? root.getWidth() : root.getHeight();
if (boundary == 0) boundary = 1000;
int step = boundary / 10;
// list.add(this == WIDTH ? 300 : 700);
list.add(ViewGroup.LayoutParams.WRAP_CONTENT);
list.add(ViewGroup.LayoutParams.MATCH_PARENT);
for (int i = step; i < boundary; i += step) {
list.add(i);
}
return list;
case MODE: return options.getSupportedControls(Mode.class);
case FLASH: return options.getSupportedControls(Flash.class);
case WHITE_BALANCE: return options.getSupportedControls(WhiteBalance.class);
case HDR: return options.getSupportedControls(Hdr.class);
case GRID: return options.getSupportedControls(Grid.class);
case AUDIO: return options.getSupportedControls(Audio.class);
case VIDEO_CODEC: return options.getSupportedControls(VideoCodec.class);
case ENGINE: return options.getSupportedControls(Engine.class);
case PREVIEW: return options.getSupportedControls(Preview.class);
case PINCH:
case HSCROLL:
case VSCROLL:
ArrayList<GestureAction> list1 = new ArrayList<>();
addIfSupported(options, list1, GestureAction.NONE);
addIfSupported(options, list1, GestureAction.ZOOM);
addIfSupported(options, list1, GestureAction.EXPOSURE_CORRECTION);
return list1;
case TAP:
case LONG_TAP:
ArrayList<GestureAction> list2 = new ArrayList<>();
addIfSupported(options, list2, GestureAction.NONE);
addIfSupported(options, list2, GestureAction.TAKE_PICTURE);
addIfSupported(options, list2, GestureAction.AUTO_FOCUS);
return list2;
case GRID_COLOR:
ArrayList<GridColor> list3 = new ArrayList<>();
list3.add(new GridColor(Color.argb(160, 255, 255, 255), "default"));
list3.add(new GridColor(Color.WHITE, "white"));
list3.add(new GridColor(Color.BLACK, "black"));
list3.add(new GridColor(Color.YELLOW, "yellow"));
return list3;
case USE_DEVICE_ORIENTATION:
return Arrays.asList(true, false);
}
return null;
}
private void addIfSupported(CameraOptions options, List<GestureAction> list, GestureAction value) {
if (options.supports(value)) list.add(value);
}
public Object getCurrentValue(CameraView view) {
switch (this) {
case WIDTH: return view.getLayoutParams().width;
case HEIGHT: return view.getLayoutParams().height;
case MODE: return view.getMode();
case FLASH: return view.getFlash();
case WHITE_BALANCE: return view.getWhiteBalance();
case GRID: return view.getGrid();
case GRID_COLOR: return new GridColor(view.getGridColor(), "color");
case AUDIO: return view.getAudio();
case VIDEO_CODEC: return view.getVideoCodec();
case HDR: return view.getHdr();
case PINCH: return view.getGestureAction(Gesture.PINCH);
case HSCROLL: return view.getGestureAction(Gesture.SCROLL_HORIZONTAL);
case VSCROLL: return view.getGestureAction(Gesture.SCROLL_VERTICAL);
case TAP: return view.getGestureAction(Gesture.TAP);
case LONG_TAP: return view.getGestureAction(Gesture.LONG_TAP);
case USE_DEVICE_ORIENTATION: return view.getUseDeviceOrientation();
case ENGINE: return view.getEngine();
case PREVIEW: return view.getPreview();
}
return null;
}
public void applyValue(final CameraView camera, final Object value) {
switch (this) {
case WIDTH:
camera.getLayoutParams().width = (int) value;
camera.setLayoutParams(camera.getLayoutParams());
break;
case HEIGHT:
camera.getLayoutParams().height = (int) value;
camera.setLayoutParams(camera.getLayoutParams());
break;
case MODE:
case FLASH:
case WHITE_BALANCE:
case GRID:
case AUDIO:
case VIDEO_CODEC:
case HDR:
camera.set((com.otaliastudios.cameraview.controls.Control) value);
break;
case PINCH:
camera.mapGesture(Gesture.PINCH, (GestureAction) value);
break;
case HSCROLL:
camera.mapGesture(Gesture.SCROLL_HORIZONTAL, (GestureAction) value);
break;
case VSCROLL:
camera.mapGesture(Gesture.SCROLL_VERTICAL, (GestureAction) value);
break;
case TAP:
camera.mapGesture(Gesture.TAP, (GestureAction) value);
break;
case LONG_TAP:
camera.mapGesture(Gesture.LONG_TAP, (GestureAction) value);
break;
case GRID_COLOR:
camera.setGridColor(((GridColor) value).color);
break;
case USE_DEVICE_ORIENTATION:
camera.setUseDeviceOrientation((Boolean) value);
break;
case ENGINE:
boolean started = camera.isOpened();
if (started) {
camera.addCameraListener(new CameraListener() {
@Override
public void onCameraClosed() {
super.onCameraClosed();
camera.removeCameraListener(this);
camera.setEngine((Engine) value);
camera.open();
}
});
camera.close();
} else {
camera.setEngine((Engine) value);
}
break;
case PREVIEW:
boolean opened = camera.isOpened();
if (opened) {
camera.addCameraListener(new CameraListener() {
@Override
public void onCameraClosed() {
super.onCameraClosed();
camera.removeCameraListener(this);
applyPreview(camera, (Preview) value, true);
}
});
camera.close();
} else {
applyPreview(camera, (Preview) value, false);
}
}
}
// This is really tricky since the preview can only be changed when not attached to window.
private void applyPreview(@NonNull CameraView cameraView, @NonNull Preview newPreview, boolean openWhenDone) {
ViewGroup.LayoutParams params = cameraView.getLayoutParams();
ViewGroup parent = (ViewGroup) cameraView.getParent();
int index = 0;
for (int i = 0; i < parent.getChildCount(); i++) {
if (parent.getChildAt(i) == cameraView) {
index = i;
break;
}
}
parent.removeView(cameraView);
cameraView.setPreview(newPreview);
parent.addView(cameraView, index, params);
if (openWhenDone) cameraView.open();
}
static class GridColor {
int color;
String name;
GridColor(int color, String name) {
this.color = color;
this.name = name;
}
@Override
public String toString() {
return name;
}
@Override
public boolean equals(Object obj) {
return obj instanceof GridColor && color == ((GridColor) obj).color;
}
}
}

@ -24,7 +24,7 @@ public class MessageView extends LinearLayout {
public MessageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { public MessageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
setOrientation(VERTICAL); setOrientation(VERTICAL);
inflate(context, R.layout.control_view, this); inflate(context, R.layout.option_view, this);
ViewGroup content = findViewById(R.id.content); ViewGroup content = findViewById(R.id.content);
inflate(context, R.layout.spinner_text, content); inflate(context, R.layout.spinner_text, content);
title = findViewById(R.id.title); title = findViewById(R.id.title);

@ -0,0 +1,444 @@
package com.otaliastudios.cameraview.demo;
import android.graphics.Color;
import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import com.otaliastudios.cameraview.CameraListener;
import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.gesture.Gesture;
import com.otaliastudios.cameraview.gesture.GestureAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* Controls that we want to display in a ControlView.
*/
public abstract class Option<T> {
public static List<Option<?>> getAll() {
return Arrays.asList(
// Layout
new Width(false),
new Height(true),
// Engine and preview
new Mode(false),
new Engine(false),
new Preview(true),
// Some controls
new Flash(false),
new WhiteBalance(false),
new Hdr(true),
// Video recording
new VideoCodec(false),
new Audio(true),
// TODO audio bitRate
// TODO video bitRate
// They are a bit annoying because it's not clear what the default should be.
// Gestures
new Pinch(false),
new HorizontalScroll(false),
new VerticalScroll(false),
new Tap(false),
new LongTap(true),
// Other
new Grid(false),
new GridColor(false),
new UseDeviceOrientation(true)
);
}
private String name;
private boolean hasDividerBelow;
private Option(@NonNull String name, boolean hasDividerBelow) {
this.name = name;
this.hasDividerBelow = hasDividerBelow;
}
@SuppressWarnings("WeakerAccess")
@NonNull
public final String getName() {
return name;
}
@SuppressWarnings("WeakerAccess")
public final boolean hasDividerBelow() {
return hasDividerBelow;
}
@NonNull
public abstract T get(@NonNull CameraView view);
@NonNull
public abstract Collection<T> getAll(@NonNull CameraView view, @NonNull CameraOptions options);
public abstract void set(@NonNull CameraView view, @NonNull T value);
@NonNull
public String toString(@NonNull T value) {
return String.valueOf(value).replace("_", " ").toLowerCase();
}
public static class Width extends Option<Integer> {
Width(boolean hasDividerBelow) {
super("Width", hasDividerBelow);
}
@NonNull
@Override
public Collection<Integer> getAll(@NonNull CameraView view, @NonNull CameraOptions options) {
View root = (View) view.getParent();
ArrayList<Integer> list = new ArrayList<>();
int boundary = root.getWidth();
if (boundary == 0) boundary = 1000;
int step = boundary / 10;
list.add(ViewGroup.LayoutParams.WRAP_CONTENT);
list.add(ViewGroup.LayoutParams.MATCH_PARENT);
for (int i = step; i < boundary; i += step) {
list.add(i);
}
return list;
}
@NonNull
@Override
public Integer get(@NonNull CameraView view) {
return view.getLayoutParams().width;
}
@Override
public void set(@NonNull CameraView view, @NonNull Integer value) {
view.getLayoutParams().width = (int) value;
view.setLayoutParams(view.getLayoutParams());
}
@NonNull
@Override
public String toString(@NonNull Integer value) {
if (value == ViewGroup.LayoutParams.MATCH_PARENT) return "match parent";
if (value == ViewGroup.LayoutParams.WRAP_CONTENT) return "wrap content";
return super.toString(value);
}
}
public static class Height extends Option<Integer> {
Height(boolean hasDividerBelow) {
super("Height", hasDividerBelow);
}
@NonNull
@Override
public Collection<Integer> getAll(@NonNull CameraView view, @NonNull CameraOptions options) {
View root = (View) view.getParent();
ArrayList<Integer> list = new ArrayList<>();
int boundary = root.getHeight();
if (boundary == 0) boundary = 1000;
int step = boundary / 10;
list.add(ViewGroup.LayoutParams.WRAP_CONTENT);
list.add(ViewGroup.LayoutParams.MATCH_PARENT);
for (int i = step; i < boundary; i += step) {
list.add(i);
}
return list;
}
@NonNull
@Override
public Integer get(@NonNull CameraView view) {
return view.getLayoutParams().height;
}
@Override
public void set(@NonNull CameraView view, @NonNull Integer value) {
view.getLayoutParams().height = (int) value;
view.setLayoutParams(view.getLayoutParams());
}
@NonNull
@Override
public String toString(@NonNull Integer value) {
if (value == ViewGroup.LayoutParams.MATCH_PARENT) return "match parent";
if (value == ViewGroup.LayoutParams.WRAP_CONTENT) return "wrap content";
return super.toString(value);
}
}
private static abstract class ControlOption<T extends com.otaliastudios.cameraview.controls.Control> extends Option<T> {
private final Class<T> controlClass;
ControlOption(@NonNull Class<T> controlClass, String name, boolean hasDividerBelow) {
super(name, hasDividerBelow);
this.controlClass = controlClass;
}
@Override
public void set(@NonNull CameraView view, @NonNull T value) {
view.set(value);
}
@NonNull
@Override
public T get(@NonNull CameraView view) {
return view.get(controlClass);
}
@NonNull
@Override
public Collection<T> getAll(@NonNull CameraView view, @NonNull CameraOptions options) {
return options.getSupportedControls(controlClass);
}
}
public static class Mode extends ControlOption<com.otaliastudios.cameraview.controls.Mode> {
Mode(boolean hasDividerBelow) {
super(com.otaliastudios.cameraview.controls.Mode.class, "Mode", hasDividerBelow);
}
}
public static class Engine extends ControlOption<com.otaliastudios.cameraview.controls.Engine> {
Engine(boolean hasDividerBelow) {
super(com.otaliastudios.cameraview.controls.Engine.class, "Engine", hasDividerBelow);
}
@Override
public void set(final @NonNull CameraView view, final @NonNull com.otaliastudios.cameraview.controls.Engine value) {
boolean started = view.isOpened();
if (started) {
view.addCameraListener(new CameraListener() {
@Override
public void onCameraClosed() {
super.onCameraClosed();
view.removeCameraListener(this);
view.setEngine(value);
view.open();
}
});
view.close();
} else {
view.setEngine(value);
}
}
}
public static class Preview extends ControlOption<com.otaliastudios.cameraview.controls.Preview> {
Preview(boolean hasDividerBelow) {
super(com.otaliastudios.cameraview.controls.Preview.class, "Preview Surface", hasDividerBelow);
}
@Override
public void set(final @NonNull CameraView view, final @NonNull com.otaliastudios.cameraview.controls.Preview value) {
boolean opened = view.isOpened();
if (opened) {
view.addCameraListener(new CameraListener() {
@Override
public void onCameraClosed() {
super.onCameraClosed();
view.removeCameraListener(this);
applyPreview(view, value);
view.open();
}
});
view.close();
} else {
applyPreview(view, value);
}
}
// This is really tricky since the preview can only be changed when not attached to window.
private void applyPreview(@NonNull CameraView cameraView,
@NonNull com.otaliastudios.cameraview.controls.Preview newPreview) {
ViewGroup.LayoutParams params = cameraView.getLayoutParams();
ViewGroup parent = (ViewGroup) cameraView.getParent();
int index = 0;
for (int i = 0; i < parent.getChildCount(); i++) {
if (parent.getChildAt(i) == cameraView) {
index = i;
break;
}
}
parent.removeView(cameraView);
cameraView.setPreview(newPreview);
parent.addView(cameraView, index, params);
}
}
public static class Flash extends ControlOption<com.otaliastudios.cameraview.controls.Flash> {
Flash(boolean hasDividerBelow) {
super(com.otaliastudios.cameraview.controls.Flash.class, "Flash", hasDividerBelow);
}
}
public static class WhiteBalance extends ControlOption<com.otaliastudios.cameraview.controls.WhiteBalance> {
WhiteBalance(boolean hasDividerBelow) {
super(com.otaliastudios.cameraview.controls.WhiteBalance.class, "White Balance", hasDividerBelow);
}
}
public static class Hdr extends ControlOption<com.otaliastudios.cameraview.controls.Hdr> {
Hdr(boolean hasDividerBelow) {
super(com.otaliastudios.cameraview.controls.Hdr.class, "HDR", hasDividerBelow);
}
}
public static class VideoCodec extends ControlOption<com.otaliastudios.cameraview.controls.VideoCodec> {
VideoCodec(boolean hasDividerBelow) {
super(com.otaliastudios.cameraview.controls.VideoCodec.class, "Video Codec", hasDividerBelow);
}
}
public static class Audio extends ControlOption<com.otaliastudios.cameraview.controls.Audio> {
Audio(boolean hasDividerBelow) {
super(com.otaliastudios.cameraview.controls.Audio.class, "Audio", hasDividerBelow);
}
}
private static abstract class GestureOption extends Option<GestureAction> {
private final Gesture gesture;
private final GestureAction[] allActions = GestureAction.values();
GestureOption(@NonNull Gesture gesture, String name, boolean hasDividerBelow) {
super(name, hasDividerBelow);
this.gesture = gesture;
}
@Override
public void set(@NonNull CameraView view, @NonNull GestureAction value) {
view.mapGesture(gesture, value);
}
@NonNull
@Override
public GestureAction get(@NonNull CameraView view) {
return view.getGestureAction(gesture);
}
@NonNull
@Override
public Collection<GestureAction> getAll(@NonNull CameraView view, @NonNull CameraOptions options) {
List<GestureAction> list = new ArrayList<>();
for (GestureAction action : allActions) {
if (gesture.isAssignableTo(action) && options.supports(action)) {
list.add(action);
}
}
return list;
}
}
public static class Pinch extends GestureOption {
Pinch(boolean hasDividerBelow) {
super(Gesture.PINCH, "Pinch", hasDividerBelow);
}
}
public static class HorizontalScroll extends GestureOption {
HorizontalScroll(boolean hasDividerBelow) {
super(Gesture.SCROLL_HORIZONTAL, "Horizontal Scroll", hasDividerBelow);
}
}
public static class VerticalScroll extends GestureOption {
VerticalScroll(boolean hasDividerBelow) {
super(Gesture.SCROLL_VERTICAL, "Vertical Scroll", hasDividerBelow);
}
}
public static class Tap extends GestureOption {
Tap(boolean hasDividerBelow) {
super(Gesture.TAP, "Tap", hasDividerBelow);
}
}
public static class LongTap extends GestureOption {
LongTap(boolean hasDividerBelow) {
super(Gesture.LONG_TAP, "Long Tap", hasDividerBelow);
}
}
public static class Grid extends ControlOption<com.otaliastudios.cameraview.controls.Grid> {
Grid(boolean hasDividerBelow) {
super(com.otaliastudios.cameraview.controls.Grid.class, "Grid Lines", hasDividerBelow);
}
}
public static class GridColor extends Option<Pair<Integer, String>> {
GridColor(boolean hasDividerBelow) {
super("Grid Color", hasDividerBelow);
}
private static final List<Pair<Integer, String>> ALL = Arrays.asList(
new Pair<>(Color.argb(160, 255, 255, 255), "default"),
new Pair<>(Color.WHITE, "white"),
new Pair<>(Color.BLACK, "black"),
new Pair<>(Color.YELLOW, "yellow")
);
@NonNull
@Override
public Collection<Pair<Integer, String>> getAll(@NonNull CameraView view, @NonNull CameraOptions options) {
return ALL;
}
@NonNull
@Override
public Pair<Integer, String> get(@NonNull CameraView view) {
for (Pair<Integer, String> pair : ALL) {
//noinspection ConstantConditions
if (pair.first == view.getGridColor()) {
return pair;
}
}
throw new RuntimeException("Could not find grid color");
}
@Override
public void set(@NonNull CameraView view, @NonNull Pair<Integer, String> value) {
//noinspection ConstantConditions
view.setGridColor(value.first);
}
@NonNull
@Override
public String toString(@NonNull Pair<Integer, String> value) {
//noinspection ConstantConditions
return value.second;
}
}
public static class UseDeviceOrientation extends Option<Boolean> {
UseDeviceOrientation(boolean hasDividerBelow) {
super("Use Device Orientation", hasDividerBelow);
}
@NonNull
@Override
public Collection<Boolean> getAll(@NonNull CameraView view, @NonNull CameraOptions options) {
return Arrays.asList(true, false);
}
@NonNull
@Override
public Boolean get(@NonNull CameraView view) {
return view.getUseDeviceOrientation();
}
@Override
public void set(@NonNull CameraView view, @NonNull Boolean value) {
view.setUseDeviceOrientation(value);
}
}
}

@ -3,66 +3,60 @@ package com.otaliastudios.cameraview.demo;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.CameraOptions; import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.CameraView; import com.otaliastudios.cameraview.CameraView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
public class ControlView<Value> extends LinearLayout implements Spinner.OnItemSelectedListener { @SuppressLint("ViewConstructor")
public class OptionView<Value> extends LinearLayout implements Spinner.OnItemSelectedListener {
interface Callback { interface Callback {
boolean onValueChanged(Control control, Object value, String name); <T> boolean onValueChanged(@NonNull Option<T> option, @NonNull T value, @NonNull String name);
} }
private Value value; private Value value;
private ArrayList<Value> values; private ArrayList<Value> values;
private ArrayList<String> valuesStrings; private ArrayList<String> valuesStrings;
private Control control; private Option option;
private Callback callback; private Callback callback;
private Spinner spinner; private Spinner spinner;
public ControlView(Context context, Control control, Callback callback) { public OptionView(Context context, Option option, Callback callback) {
super(context); super(context);
this.control = control; this.option = option;
this.callback = callback; this.callback = callback;
setOrientation(VERTICAL); setOrientation(VERTICAL);
inflate(context, R.layout.control_view, this); inflate(context, R.layout.option_view, this);
TextView title = findViewById(R.id.title); TextView title = findViewById(R.id.title);
title.setText(control.getName()); title.setText(option.getName());
View divider = findViewById(R.id.divider); View divider = findViewById(R.id.divider);
divider.setVisibility(control.isSectionLast() ? View.VISIBLE : View.GONE); divider.setVisibility(option.hasDividerBelow() ? View.VISIBLE : View.GONE);
ViewGroup content = findViewById(R.id.content); ViewGroup content = findViewById(R.id.content);
spinner = new Spinner(context, Spinner.MODE_DROPDOWN); spinner = new Spinner(context, Spinner.MODE_DROPDOWN);
content.addView(spinner); content.addView(spinner);
} }
public Value getValue() {
return value;
}
@SuppressWarnings("all") @SuppressWarnings("all")
public void onCameraOpened(CameraView view, CameraOptions options) { public void onCameraOpened(CameraView view, CameraOptions options) {
values = new ArrayList(control.getValues(view, options)); values = new ArrayList(option.getAll(view, options));
value = (Value) control.getCurrentValue(view); value = (Value) option.get(view);
valuesStrings = new ArrayList<>(); valuesStrings = new ArrayList<>();
for (Value value : values) { for (Value value : values) {
valuesStrings.add(stringify(value)); valuesStrings.add(option.toString(value));
} }
if (values.isEmpty()) { if (values.isEmpty()) {
@ -86,7 +80,7 @@ public class ControlView<Value> extends LinearLayout implements Spinner.OnItemSe
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) { public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
if (!values.get(i).equals(value)) { if (!values.get(i).equals(value)) {
Log.e("ControlView", "curr: " + value + " new: " + values.get(i)); Log.e("ControlView", "curr: " + value + " new: " + values.get(i));
if (!callback.onValueChanged(control, values.get(i), valuesStrings.get(i))) { if (!callback.onValueChanged(option, values.get(i), valuesStrings.get(i))) {
spinner.setSelection(values.indexOf(value)); // Go back. spinner.setSelection(values.indexOf(value)); // Go back.
} else { } else {
value = values.get(i); value = values.get(i);
@ -96,12 +90,4 @@ public class ControlView<Value> extends LinearLayout implements Spinner.OnItemSe
@Override @Override
public void onNothingSelected(AdapterView<?> adapterView) {} public void onNothingSelected(AdapterView<?> adapterView) {}
private String stringify(Value value) {
if (value instanceof Integer) {
if ((Integer) value == ViewGroup.LayoutParams.MATCH_PARENT) return "match parent";
if ((Integer) value == ViewGroup.LayoutParams.WRAP_CONTENT) return "wrap content";
}
return String.valueOf(value).replace("_", " ").toLowerCase();
}
} }

@ -14,7 +14,8 @@ quality of the output.
Everything can be controlled through XML parameters or programmatically. For convenience, most options Everything can be controlled through XML parameters or programmatically. For convenience, most options
are represented by `enum` classes extending the `Control` class. This makes it possible to use are represented by `enum` classes extending the `Control` class. This makes it possible to use
`CameraView.set(Control)` to set the given control, or `CameraOptions.supports(Control)` to see if it is supported. `CameraView.set(Control)` to set the given control, `CameraView.get(Class<Control>)` to get it,
or `CameraOptions.supports(Control)` to see if it is supported.
### XML Attributes ### XML Attributes

Loading…
Cancel
Save