From 8d3013ff53b92af1854b82a1468d7b3d9c27ae45 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 12 Jul 2019 12:41:56 -0300 Subject: [PATCH] Demo improvements (#499) * Demo app changes * Add tests * Doc changes --- .../cameraview/CameraViewTest.java | 36 +- .../otaliastudios/cameraview/CameraView.java | 34 ++ .../cameraview/demo/CameraActivity.java | 22 +- .../cameraview/demo/Control.java | 274 ----------- .../cameraview/demo/MessageView.java | 2 +- .../otaliastudios/cameraview/demo/Option.java | 444 ++++++++++++++++++ .../{ControlView.java => OptionView.java} | 44 +- .../{control_view.xml => option_view.xml} | 0 docs/_posts/2018-12-20-controls.md | 3 +- 9 files changed, 524 insertions(+), 335 deletions(-) delete mode 100644 demo/src/main/java/com/otaliastudios/cameraview/demo/Control.java create mode 100644 demo/src/main/java/com/otaliastudios/cameraview/demo/Option.java rename demo/src/main/java/com/otaliastudios/cameraview/demo/{ControlView.java => OptionView.java} (65%) rename demo/src/main/res/layout/{control_view.xml => option_view.xml} (100%) diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java index d4cb6e55..b66c20a3 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java @@ -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 diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 4214c49e..f7d5c08e 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -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 get(@NonNull Class 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 diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java index e7c35455..9b888ade 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java @@ -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> 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 boolean onValueChanged(@NonNull Option 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; } diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/Control.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/Control.java deleted file mode 100644 index 41813789..00000000 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/Control.java +++ /dev/null @@ -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 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 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 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 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 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; - } - } -} diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/MessageView.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/MessageView.java index 0d9d8ce3..3e318a40 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/MessageView.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/MessageView.java @@ -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); diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/Option.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/Option.java new file mode 100644 index 00000000..3d4c6f73 --- /dev/null +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/Option.java @@ -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 { + + public static List> 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 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 { + Width(boolean hasDividerBelow) { + super("Width", hasDividerBelow); + } + + @NonNull + @Override + public Collection getAll(@NonNull CameraView view, @NonNull CameraOptions options) { + View root = (View) view.getParent(); + ArrayList 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 { + Height(boolean hasDividerBelow) { + super("Height", hasDividerBelow); + } + + @NonNull + @Override + public Collection getAll(@NonNull CameraView view, @NonNull CameraOptions options) { + View root = (View) view.getParent(); + ArrayList 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 extends Option { + private final Class controlClass; + + ControlOption(@NonNull Class 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 getAll(@NonNull CameraView view, @NonNull CameraOptions options) { + return options.getSupportedControls(controlClass); + } + } + + public static class Mode extends ControlOption { + Mode(boolean hasDividerBelow) { + super(com.otaliastudios.cameraview.controls.Mode.class, "Mode", hasDividerBelow); + } + } + + public static class Engine extends ControlOption { + 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 { + 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 { + Flash(boolean hasDividerBelow) { + super(com.otaliastudios.cameraview.controls.Flash.class, "Flash", hasDividerBelow); + } + } + + public static class WhiteBalance extends ControlOption { + WhiteBalance(boolean hasDividerBelow) { + super(com.otaliastudios.cameraview.controls.WhiteBalance.class, "White Balance", hasDividerBelow); + } + } + + public static class Hdr extends ControlOption { + Hdr(boolean hasDividerBelow) { + super(com.otaliastudios.cameraview.controls.Hdr.class, "HDR", hasDividerBelow); + } + } + + public static class VideoCodec extends ControlOption { + VideoCodec(boolean hasDividerBelow) { + super(com.otaliastudios.cameraview.controls.VideoCodec.class, "Video Codec", hasDividerBelow); + } + } + + public static class Audio extends ControlOption { + Audio(boolean hasDividerBelow) { + super(com.otaliastudios.cameraview.controls.Audio.class, "Audio", hasDividerBelow); + } + } + + private static abstract class GestureOption extends Option { + 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 getAll(@NonNull CameraView view, @NonNull CameraOptions options) { + List 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 { + Grid(boolean hasDividerBelow) { + super(com.otaliastudios.cameraview.controls.Grid.class, "Grid Lines", hasDividerBelow); + } + } + + public static class GridColor extends Option> { + + GridColor(boolean hasDividerBelow) { + super("Grid Color", hasDividerBelow); + } + + private static final List> 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> getAll(@NonNull CameraView view, @NonNull CameraOptions options) { + return ALL; + } + + @NonNull + @Override + public Pair get(@NonNull CameraView view) { + for (Pair 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 value) { + //noinspection ConstantConditions + view.setGridColor(value.first); + } + + @NonNull + @Override + public String toString(@NonNull Pair value) { + //noinspection ConstantConditions + return value.second; + } + } + + public static class UseDeviceOrientation extends Option { + UseDeviceOrientation(boolean hasDividerBelow) { + super("Use Device Orientation", hasDividerBelow); + } + + @NonNull + @Override + public Collection 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); + } + } +} diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/ControlView.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/OptionView.java similarity index 65% rename from demo/src/main/java/com/otaliastudios/cameraview/demo/ControlView.java rename to demo/src/main/java/com/otaliastudios/cameraview/demo/OptionView.java index 28b5724d..48937343 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/ControlView.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/OptionView.java @@ -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 extends LinearLayout implements Spinner.OnItemSelectedListener { +@SuppressLint("ViewConstructor") +public class OptionView extends LinearLayout implements Spinner.OnItemSelectedListener { interface Callback { - boolean onValueChanged(Control control, Object value, String name); + boolean onValueChanged(@NonNull Option option, @NonNull T value, @NonNull String name); } private Value value; private ArrayList values; private ArrayList 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 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 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(); - } } diff --git a/demo/src/main/res/layout/control_view.xml b/demo/src/main/res/layout/option_view.xml similarity index 100% rename from demo/src/main/res/layout/control_view.xml rename to demo/src/main/res/layout/option_view.xml diff --git a/docs/_posts/2018-12-20-controls.md b/docs/_posts/2018-12-20-controls.md index e98f3db1..6ba825a5 100644 --- a/docs/_posts/2018-12-20-controls.md +++ b/docs/_posts/2018-12-20-controls.md @@ -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)` to get it, +or `CameraOptions.supports(Control)` to see if it is supported. ### XML Attributes