0

Problem

Using RelativeLayout.LayoutParams and marginTop before setLayoutParams( params ).
Works on all devices for half a second but some cases it bumps back to top. The view stays centered in the holding view on my Moto X 2014 running Lollipop but not on my Nexus 4 running Lollipop.


Opening activity

  1. Opens activity
  2. The margin is fine and the SurfaceView is centered
  3. ~200ms delay
  4. The margin resets and its back to top (top of SurfaceView at the top of holder)

Closing activity

  1. Back pressed
  2. ~200ms delay
  3. The margin sets in, putting my view to the right position
  4. Activity closes

Code (Edited)

RelativeLayout holder = ( RelativeLayout ) findViewById( R.id.holder );
RelativeLayout.LayoutParams params = ( RelativeLayout.LayoutParams ) holder.getLayoutParams();
CustomCamera view = ( CustomCamera ) findViewById( R.id.surface ); // Extends SurfaceView
params.topMargin = margin;
view.setLayoutParams( params );

Example

I need the margin to work like this every time on every device. On some devices the red (SurfaceView) is aligned with top of screen ignoring the margin and gravity.

What I need

Jonas Borggren
  • 2,591
  • 1
  • 22
  • 40
  • Why would you set the holder's layout parameters to you SurfaceView. Also, why don't you add your CustomCamera View in xml directly and add margins over there? – Mike Feb 05 '15 at 14:36
  • I need the parent layout parameters to use margin? Correct me if I'm wrong. As for the use of margins in the XML I'm changing the width & height of the view programatically so setting it in the XML would not work. **The question has updated code including custom view from xml** – Jonas Borggren Feb 05 '15 at 14:41
  • Please give me some brief details of what would you like to achieve(not code...the idea) so I can help you out. – Mike Feb 05 '15 at 14:43
  • The aspect ratio of the holder is 4:3 while the camera is 16:9 (9:16 because its rotated) and I only want to show the middle 4:3 of it. The height of the SurfaceView is above and below whats shown in the holder - I want to use margin (gravity does not work) to move the SurfaceView up to the middle of the holder. **Remember:** this works on 1 out of 3 devices I've tested it on. – Jonas Borggren Feb 05 '15 at 14:46
  • Okay but what if you initialize your Camera wrong and shouldn't worry about margins? Do you have a SurfaceHolder.Callback? – Mike Feb 05 '15 at 14:55
  • Yes, I have a SurfaceHolder.Callback. Check my updated question for an example of what I need. – Jonas Borggren Feb 05 '15 at 15:07
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/70311/discussion-between-mike-and-jonas-b). – Mike Feb 05 '15 at 15:10

1 Answers1

0

To make your life easier, here is my simple camera implementation which can help you out. Note that this implementation relays on the old android.hardware.Camera API. Starting API level 21 there is a new way of working with the camera.

Anyway here is your basic xml file for your activity:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

    <FrameLayout
        android:id="@+id/root_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <SurfaceView
            android:id="@+id/camera_surfaceview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>

    <Button
        android:id="@+id/take_picture_button"
        android:layout_width="wrap_content"
        android:layout_height="48dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="16dp"
        android:text="Take picture" />
</RelativeLayout>

Your camera activity:

package com.your.package;

import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.ShutterCallback;
import android.hardware.Camera.Size;
import android.media.AudioManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.FrameLayout;

import java.io.IOException;

/**
 * @author Mike Reman
 *
 */
public class MainActivity extends Activity {

    // Constants
    private static final float PREVIEW_SIZE_FACTOR = 1.3f;
    private static final String STATE_SELECTED_CHECKBOX = "CameraType";

    private Camera mCamera;
    private SurfaceHolder mSurfaceHolder = null;

    // Data
//private boolean mHasTwoCameras = (Camera.getNumberOfCameras() > 1);
    private boolean mIsInPreview;
    private boolean mIsUsingFFC = false;
    private boolean mIsLandscape;
    protected AutoFocusCallback autoFocusCallback = new AutoFocusCallback() {

        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            try {
                camera.takePicture(shutterCallback, null, jpegCallback);
            } catch (RuntimeException e) {
                e.printStackTrace();
            }
        }
    };

    private ShutterCallback shutterCallback = new ShutterCallback() {

        @Override
        public void onShutter() {
            AudioManager mgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
            mgr.playSoundEffect(AudioManager.FLAG_PLAY_SOUND);
        }
    };

    private PictureCallback jpegCallback = new PictureCallback() {

        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            //Tadaaa, you got your picture taken
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setFormat(PixelFormat.TRANSLUCENT);
        mIsLandscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;

        if (savedInstanceState != null) {
            mIsUsingFFC = savedInstanceState.getBoolean(STATE_SELECTED_CHECKBOX, false);
        }
        setContentView(R.layout.activity_main);
        initializeCameraAndViews();
    }


    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        outState.putBoolean(STATE_SELECTED_CHECKBOX, mIsUsingFFC);
    }

    /**
     * Initialize the views used by the activity, including the SurfaceView
     * displaying the 'camera'. There is a SurfaceHolder object which is
     * initialized by the SurfaceView's SurfaceHolder
     */
    private void initializeCameraAndViews() {
        FrameLayout frame = (FrameLayout) findViewById(R.id.root_container);
        SurfaceView surfaceView = (SurfaceView) frame.findViewById(R.id.camera_surfaceview);

        if (mSurfaceHolder == null) {
            mSurfaceHolder = surfaceView.getHolder();
        }

        mSurfaceHolder.addCallback(surfaceHolderCallback(mIsUsingFFC));
        findViewById(R.id.take_picture_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    mCamera.autoFocus(autoFocusCallback);
                } catch (RuntimeException e) {
                    try {
                        mCamera.takePicture(shutterCallback, null, jpegCallback);
                    } catch (RuntimeException ex) {
                        // Failed to take the picture
                        ex.printStackTrace();
                    }
                }
            }
        });
    }


    private SurfaceHolder.Callback surfaceHolderCallback(final boolean isUsingFFC) {
        return new SurfaceHolder.Callback() {
            private AsyncTask<Void, Void, Void> initCameraAsyncTask;
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                if (isUsingFFC) {
                    try {
                        mCamera = Camera.open(CameraInfo.CAMERA_FACING_FRONT);
                        CameraUtils.setCameraDisplayOrientation(MainActivity.this, CameraInfo.CAMERA_FACING_FRONT, mCamera);
                    } catch (RuntimeException e) {
                        // Open camera failed
                    }
                } else {
                    try {
                        mCamera = Camera.open(CameraInfo.CAMERA_FACING_BACK);
                        CameraUtils.setCameraDisplayOrientation(MainActivity.this, CameraInfo.CAMERA_FACING_BACK, mCamera);
                    } catch (RuntimeException e) {
                        // Open camera failed
                    }
                }

                try {
                    if (mCamera != null) {
                        mCamera.setPreviewDisplay(holder);
                    } else {
                        // Most probably the device has no camera...yeah it is possible :)
                    }
                } catch (IOException exception) {
                    mCamera.release();
                }
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, final int width, final int height) {
                initCameraAsyncTask = new AsyncTask<Void, Void, Void>() {

                    @Override
                    protected Void doInBackground(Void... params) {
                        if (mCamera != null) {
                            try {
                                Camera.Parameters parameters = mCamera.getParameters();
                                Size size = getOptimalSize(mCamera);
                                parameters.setPreviewSize(size.width, size.height);
                                mCamera.setParameters(parameters);
                            } catch (RuntimeException e) {
                                e.printStackTrace();
                            }

                            mCamera.startPreview();
                        }
                        return null;
                    }

                    @Override
                    protected void onPostExecute(Void result) {
                        super.onPostExecute(result);
                        mIsInPreview = true;

                        // Set the initial FlashMode to OFF
                        if (mCamera != null) {
                            Camera.Parameters parameters = mCamera.getParameters();
                            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
                            mCamera.setParameters(parameters);
                        }
                    }
                };
                initCameraAsyncTask.execute();
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                if (mCamera != null) {
                    mCamera.stopPreview();
                    mIsInPreview = false;
                    initCameraAsyncTask.cancel(true);
                    mCamera.release();
                }
            }
        };
    }


    /**
     * Open the camera by creating a new instance of the Camera object depending
     * on the given parameter. Use this method if you want to switch between Front facing camera(FFC) and back facing cameras
     * <p>
     * If the parameter value is true, a new Camera object will be created using
     * the Front Facing Camera. Otherwise the newly created Camera object will
     * use the Back Facing Camera
     * </p>
     *
     * @param isWithFFC
     *            - the parameter to be the deciding factor on which camera is
     *            used
     */
    private void openCamera(boolean isWithFFC) {
        if (mIsInPreview) {
            mCamera.stopPreview();
            mIsInPreview = false;
        }
        mCamera.release();

        int currentCameraId;
        if (isWithFFC) {
            currentCameraId = CameraInfo.CAMERA_FACING_FRONT;
        } else {
            currentCameraId = CameraInfo.CAMERA_FACING_BACK;
        }
        mCamera = Camera.open(currentCameraId);

        CameraUtils.setCameraDisplayOrientation(MainActivity.this, currentCameraId, mCamera);

        try {
            mCamera.setPreviewDisplay(mSurfaceHolder);
        } catch (IOException e) {
            e.printStackTrace();
        }

        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                try {
                    Camera.Parameters parameters = mCamera.getParameters();
                    Size size = getOptimalSize(mCamera);
                    parameters.setPreviewSize(size.width, size.height);
                    mCamera.setParameters(parameters);
                } catch (RuntimeException e) {
                    e.printStackTrace();
                }
                mCamera.startPreview();
                return null;
            }

            @Override
            protected void onPostExecute(Void result) {
                super.onPostExecute(result);
                mIsInPreview = true;
            }

        }.execute();
    }

    /**
     * Gets an optimal size for the created Camera.
     *
     * @param camera
     *            - the built Camera object
     * @return the optimal Size for the Camera object
     */
    private Size getOptimalSize(Camera camera) {
        Size result = null;
        final Camera.Parameters parameters = camera.getParameters();

        int width = ScreenUtils.getScreenWidth(this);
        int height = ScreenUtils.getScreenHeight(this);

        for (final Size size : parameters.getSupportedPreviewSizes()) {
            if (size.width <= width * PREVIEW_SIZE_FACTOR && size.height <= height * PREVIEW_SIZE_FACTOR) {

                if (mIsLandscape) {
                    size.width = width;
                    size.height = height;
                } else {
                    size.height = width; // switching the values because the default camera is basically always in landscape mode and our camera isn't.
                    size.width = height;
                }
                if (result == null) {
                    result = size;
                } else {
                    final int resultArea = result.width * result.height;
                    final int newArea = size.width * size.height;

                    if (newArea > resultArea) {
                        result = size;
                    }
                }
            }
        }
        if (result == null) {
            result = parameters.getSupportedPreviewSizes().get(0);
        }
        return result;
    }
}

Your needed utils classes. You can add these to your own CameraActivity if you won't use them elsewhere:

CameraUtils:

public final class CameraUtils {

            /**
             * Sets the orientation for the Camera object as the default orientation is
             * in landscape mode.
             * @param activity - the Activity where the orientation is applied to the Camera object
             * @param cameraId - the id of the used Camera(using the Front Facing Camera or the Back Facing Camera)
             * @param camera - the Camera object on which the orientation changes will be applied
             */
            public static void setCameraDisplayOrientation(Activity activity, int cameraId, Camera camera) {
                Camera.CameraInfo info = new Camera.CameraInfo();
                Camera.getCameraInfo(cameraId, info);
                int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();

                int degrees = 0;
                switch (rotation) {
                    case Surface.ROTATION_0:
                        degrees = 0;
                        break;
                    case Surface.ROTATION_90:
                        degrees = 90;
                        break;
                    case Surface.ROTATION_180:
                        degrees = 180;
                        break;
                    case Surface.ROTATION_270:
                        degrees = 270;
                        break;
                }

                int result;
                if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    result = (info.orientation + degrees) % 360;
                    result = (360 - result) % 360; // compensate the mirror(basically turn the image upside down)
                } else { // back-facing
                    result = (info.orientation - degrees + 360) % 360;
                }
                camera.setDisplayOrientation(result);
            }
}

ScreenUtils:

public final class ScreenUtils {
            /**
             * Calculates the screen's width and returns this value.
             * @param activity - the activity where the method is called from
             * @return - the screen's width
             */
            public static int getScreenWidth(Activity activity) {
                Display display = activity.getWindowManager().getDefaultDisplay();
                Point size = new Point();
                display.getSize(size);
                return size.x;
            }

            /**
             * Calculates the screen's height and returns this value.
             * @param activity - the activity where the method is called from
             * @return - the screen's height
             */
            public static int getScreenHeight(Activity activity) {
                Display display = activity.getWindowManager().getDefaultDisplay();
                Point size = new Point();
                display.getSize(size);
                return size.y;
            }
} 

And last but not least, don't forget about you permissions in the manifest file:

<uses-permission android:name="android.permission.CAMERA" />

<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

I hope this answer helps you to get your camera on the go!

Cheers, Mike

Mike
  • 4,550
  • 4
  • 33
  • 47
  • Is there any difference from implementing SurfaceHolder.Callback instead of setting it like you do?(`mSurfaceHolder.addCallback( this );` instead of `mSurfaceHolder.addCallback( surfaceHolderCallback( mIsUsingFFC ) );`) – Jonas Borggren Feb 06 '15 at 08:31
  • Nope, it shouldn't be a significant difference. Feel free to modify the structure of the class for your own needs. – Mike Feb 06 '15 at 08:33
  • I tried changing a lot of activity-related things to make the layout stay put but did not work even with the implementation of your code. :( – Jonas Borggren Feb 06 '15 at 08:55
  • I can confirm you that the code above is 100% functional. If you simply copy and paste it, should definitely work(it is tested on lots of devices and OS versions). Try to work your way up from this code, step by step. – Mike Feb 06 '15 at 08:59
  • I was all aware of that. But just like you said yesterday.... If it gives you a fully functional camera implementation and if it helps you, why should you bother with these margin issues? – Mike Feb 06 '15 at 09:20
  • In portrait mode when recording video I need the middle of the video otherwise the user would point his phone to the ground to be able to see himself when using front-facing camera for recording. This is an app to record video of yourself. – Jonas Borggren Feb 06 '15 at 09:24