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

@ -22,14 +22,12 @@ import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.VideoResult;
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.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 ViewGroup controlPanel;
@ -67,9 +65,9 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
controlPanel = findViewById(R.id.controls);
ViewGroup group = (ViewGroup) controlPanel.getChildAt(0);
Control[] controls = Control.values();
for (Control control : controls) {
ControlView view = new ControlView(this, control, this);
List<Option<?>> options = Option.getAll();
for (Option option : options) {
OptionView view = new OptionView(this, option, this);
group.addView(view,
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
@ -95,7 +93,7 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
public void onCameraOpened(@NonNull CameraOptions options) {
ViewGroup group = (ViewGroup) controlPanel.getChildAt(0);
for (int i = 0; i < group.getChildCount(); i++) {
ControlView view = (ControlView) group.getChildAt(i);
OptionView view = (OptionView) group.getChildAt(i);
view.onCameraOpened(camera, options);
}
}
@ -220,8 +218,8 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
}
@Override
public boolean onValueChanged(Control control, Object value, String name) {
if ((control == Control.WIDTH || control == Control.HEIGHT)) {
public <T> boolean onValueChanged(@NonNull Option<T> option, @NonNull T value, @NonNull String name) {
if ((option instanceof Option.Width || option instanceof Option.Height)) {
Preview preview = camera.getPreview();
boolean wrapContent = (Integer) value == ViewGroup.LayoutParams.WRAP_CONTENT;
if (preview == Preview.SURFACE && !wrapContent) {
@ -230,10 +228,10 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
return false;
}
}
control.applyValue(camera, value);
option.set(camera, value);
BottomSheetBehavior b = BottomSheetBehavior.from(controlPanel);
b.setState(BottomSheetBehavior.STATE_HIDDEN);
message("Changed " + control.getName() + " to " + name, false);
message("Changed " + option.getName() + " to " + name, false);
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) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
inflate(context, R.layout.control_view, this);
inflate(context, R.layout.option_view, this);
ViewGroup content = findViewById(R.id.content);
inflate(context, R.layout.spinner_text, content);
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.content.Context;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.CameraView;
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 {
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 ArrayList<Value> values;
private ArrayList<String> valuesStrings;
private Control control;
private Option option;
private Callback callback;
private Spinner spinner;
public ControlView(Context context, Control control, Callback callback) {
public OptionView(Context context, Option option, Callback callback) {
super(context);
this.control = control;
this.option = option;
this.callback = callback;
setOrientation(VERTICAL);
inflate(context, R.layout.control_view, this);
inflate(context, R.layout.option_view, this);
TextView title = findViewById(R.id.title);
title.setText(control.getName());
title.setText(option.getName());
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);
spinner = new Spinner(context, Spinner.MODE_DROPDOWN);
content.addView(spinner);
}
public Value getValue() {
return value;
}
@SuppressWarnings("all")
public void onCameraOpened(CameraView view, CameraOptions options) {
values = new ArrayList(control.getValues(view, options));
value = (Value) control.getCurrentValue(view);
values = new ArrayList(option.getAll(view, options));
value = (Value) option.get(view);
valuesStrings = new ArrayList<>();
for (Value value : values) {
valuesStrings.add(stringify(value));
valuesStrings.add(option.toString(value));
}
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) {
if (!values.get(i).equals(value)) {
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.
} else {
value = values.get(i);
@ -96,12 +90,4 @@ public class ControlView<Value> extends LinearLayout implements Spinner.OnItemSe
@Override
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
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

Loading…
Cancel
Save