Xamarin - Droid (SurfaceView) - android

I have an app for face detection, thus I need to open the camera when the app is launched, I have looked into the similar problems yet I can't find a solution to the problem I currently have now. The problem is that the function SurfaceCreated(ISurfaceHolder holder) is not being called, thus, the camera is not being launched. Thanks for the response
CameraLayout.axml
<AppName.Droid.CameraControls.CameraLiveStream
android:id="#+id/cameraPreview"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</App2.Droid.CameraControls.CameraLiveStream>
The CameraLiveStream.cs inherits ViewGroup, ISurfaceHolderCallback2
CameraLiveStream.cs
private Context mContext;
private SurfaceView mSurfaceView;
private bool mStartRequested;
private bool mSurfaceAvailable;
private CameraSource mCameraSource;
public CameraLiveStream(Context context, IAttributeSet attrs) : base(context, attrs)
{
mContext = context;
mStartRequested = false;
mSurfaceAvailable = false;
mSurfaceView = new SurfaceView(context);
mSurfaceView.Holder.AddCallback(this);
AddView(mSurfaceView);
}
#region camera-surface
public void SurfaceChanged(ISurfaceHolder holder, [GeneratedEnum] Format format, int width, int height)
{
throw new NotImplementedException();
}
public void SurfaceCreated(ISurfaceHolder holder)
{
mSurfaceAvailable = true;
try
{
StartCameraIfReady();
}
catch
{
Log.Error("CameraLiveStream", "Error when starting camera");
}
}
Here's my MainActivity.cs OnCreate()
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.CameraLayout);
mPreview = FindViewById<CameraLiveStream>(Resource.Id.cameraPreview);
if (ActivityCompat.CheckSelfPermission(this, Manifest.Permission.Camera) == Permission.Granted)
{
CreateCameraSource();
}
else
{
RequestCameraPermissions();
}
}
CreateCameraSource Function
private void CreateCameraSource()
{
var context = Application.Context;
FaceDetector detector = new FaceDetector.Builder(context)
.SetClassificationType(ClassificationType.All)
.Build();
if (!detector.IsOperational)
{
Log.Warn("MainActivity", "Face detector dependencies are not yet available.");
}
mCameraSource = new CameraSource.Builder(context, detector)
.SetRequestedPreviewSize(200, 200)
.SetFacing(CameraFacing.Back)
.SetRequestedFps(30.0f)
.Build();
}
StartCameraSource Function
private void StartCameraSource()
{
int code = GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(
this.ApplicationContext);
if (code != ConnectionResult.Success)
{
Dialog dlg =
GoogleApiAvailability.Instance.GetErrorDialog(this, code, RC_HANDLE_GMS);
dlg.Show();
}
if (mCameraSource != null)
{
try
{
mPreview.StartCamera(mCameraSource);
}
catch (System.Exception e)
{
Log.Error("MainActivity-StartCameraSource", "Unable to start camera source.", e);
mCameraSource.Release();
mCameraSource = null;
}

You are creating a SurfaceView with no layout params and thus its size it 0,0 and thus a surface would never need to be created.
Full screen:
var layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
var view = new SurfaceView(someContext)
{
LayoutParameters = layoutParams
};
Or Parent the SurfaceView to a container in your layout
var aViewGroup = FindViewById<ViewGroup>(Resource.Id.SomeContainer);
aViewGroup.AddView(yourSurfaceView, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));

Fixed the issue:
I have created another type of view underneath my
AppName.Droid.CameraControls.CameraLiveStream
So it became like this
<AppName.Droid.Camera.Controls.CameraLiveStream>
<AppName.Droid.Camera.Controls.Overlay>
</AppName.Droid.Camera.Controls.Overlay>
</AppName.Droid.Camera.Controls.CameraLiveStream>
Wherein I the overlay inherited the View which implements the width/height of the view.
public int mPrevWidth { get => mWidth; set => mWidth = value; }
public int mPrevHeight { get => mHeight; set => mHeight = value; }
public float mPrevWidthScaleFactor { get => mWidthScaleFactor; set => mWidthScaleFactor = value; }
public float mPrevHeightScaleFactor { get => mHeightScaleFactor; set => mHeightScaleFactor = value; }
public CameraFacing mCameraFacing { get => mFacing; set => mFacing = value; }

Related

Can't enable Flash with the Camera2 Api

I want to enable flash on/off in my camera app, but it isn't working,
Here's the code:
FlashActivity.java
public class FlashActivity extends AppCompatActivity {
private CameraManager mCameraManager;
private String mCameraId;
private Button mTorchOnOffButton;
private Boolean isTorchOn;
private TextureView textureView;
SeekBar sb;
TextToSpeech t1;
private String cameraId;
private CameraDevice cameraDevice;
private CameraCaptureSession cameraCaptureSessions;
private CaptureRequest.Builder captureRequestBuilder;
private Size imageDimension;
Bitmap src;
private static final int REQUEST_CAMERA_PERMISSION = 200;
private Handler mBackgroundHandler;
private HandlerThread mBackgroundThread;
CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback()
{
#Override
public void onOpened(#NonNull CameraDevice camera) {
cameraDevice = camera;
createCameraPreview();
}
#Override
public void onDisconnected(#NonNull CameraDevice cameraDevice) {
cameraDevice.close();
}
#Override
public void onError(#NonNull CameraDevice cameraDevice, int i) {
cameraDevice.close();
cameraDevice=null;
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("FlashActivity", "onCreate()");
setContentView(R.layout.activity_flash);
sb = (SeekBar) findViewById(R.id.seekBar2);
textureView = (TextureView) findViewById(R.id.textureView);
mTorchOnOffButton = (Button) findViewById(R.id.button_on_off);
isTorchOn = false;
textureView.setSurfaceTextureListener(textureListener);
t1 = new TextToSpeech(getApplicationContext(), new TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
if (status != TextToSpeech.ERROR) {
t1.setLanguage(Locale.UK);
}
}
});
ToggleButton toggle = (ToggleButton) findViewById(R.id.toggleButton);
toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
freeze();
String toSpeak = "Camera Freeze";
Toast.makeText(getApplicationContext(), toSpeak, Toast.LENGTH_SHORT).show();
t1.speak(toSpeak, TextToSpeech.QUEUE_FLUSH, null);
} else {
updatePreview();
String toSpeak = "Back to Live Camera";
Toast.makeText(getApplicationContext(), toSpeak, Toast.LENGTH_SHORT).show();
t1.speak(toSpeak, TextToSpeech.QUEUE_FLUSH, null);
}
}
});
Boolean isFlashAvailable = getApplicationContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
if (!isFlashAvailable) {
AlertDialog alert = new AlertDialog.Builder(FlashActivity.this)
.create();
alert.setTitle("Error !!");
alert.setMessage("Your device doesn't support flash light!");
alert.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// closing the application
finish();
System.exit(0);
}
});
alert.show();
return;
}
mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
mCameraId = mCameraManager.getCameraIdList()[0];
} catch (CameraAccessException e) {
e.printStackTrace();
}
mTorchOnOffButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
if (isTorchOn) {
turnOffFlashLight();
isTorchOn = false;
} else {
turnOnFlashLight();
isTorchOn = true;
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
float scale = ((progress / 10.0f) + 1);
textureView.setScaleX(scale);
textureView.setScaleY(scale);
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
public void turnOnFlashLight() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mCameraManager.setTorchMode(mCameraId, true);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void turnOffFlashLight() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mCameraManager.setTorchMode(mCameraId, false);
}
} catch (Exception e) {
e.printStackTrace();
}
}
ArrayList rev;
public void freeze() {
Thread td = new Thread(new Runnable() {
public void run() {
Bitmap bmp = textureView.getBitmap();
rev = new ArrayList<Bitmap>();
rev.add(bmp);
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), "" + rev.size(), Toast.LENGTH_LONG).show();
try {
cameraCaptureSessions.stopRepeating();
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
});
}
});
td.start();
}
private void createCameraPreview() {
try {
SurfaceTexture texture = textureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight());
Surface surface = new Surface(texture);
captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequestBuilder.addTarget(surface);
cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
#Override
public void onConfigured(#NonNull CameraCaptureSession cameraCaptureSession) {
if (cameraDevice == null)
return;
cameraCaptureSessions = cameraCaptureSession;
updatePreview();
}
#Override
public void onConfigureFailed(#NonNull CameraCaptureSession cameraCaptureSession) {
Toast.makeText(FlashActivity.this, "Changed", Toast.LENGTH_SHORT).show();
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void updatePreview() {
if (cameraDevice == null)
Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show();
captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
try {
cameraCaptureSessions.setRepeatingRequest(captureRequestBuilder.build(), null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void openCamera() {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
cameraId = manager.getCameraIdList()[0];
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
assert map != null;
imageDimension = map.getOutputSizes(SurfaceTexture.class)[0];
//Check realtime permission if run higher API 23
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, REQUEST_CAMERA_PERMISSION);
return;
}
manager.openCamera(cameraId, stateCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
openCamera();
}
#Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
}
#Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
return false;
}
#Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
};
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
if(requestCode == REQUEST_CAMERA_PERMISSION)
{
if(grantResults[0] != PackageManager.PERMISSION_GRANTED)
{
Toast.makeText(this, "You can't use camera without permission", Toast.LENGTH_SHORT).show();
finish();
}
}
}
#Override
protected void onStop() {
super.onStop();
if(isTorchOn){
turnOffFlashLight();
}
}
#Override
protected void onPause() {
super.onPause();
if(isTorchOn){
turnOffFlashLight();
}
}
#Override
protected void onResume() {
super.onResume();
if(isTorchOn){
turnOnFlashLight();
}
}
}
Here's what I get at LOG
04-03 16:38:34.948 10905-10905/com.example.sumesh.myapplication W/System.err: android.hardware.camera2.CameraAccessException: The camera device is in use already
at android.hardware.camera2.utils.CameraBinderDecorator.throwOnError(CameraBinderDecorator.java:123)
at android.hardware.camera2.utils.CameraBinderDecorator$CameraBinderDecoratorListener.onAfterInvocation(CameraBinderDecorator.java:73)
at android.hardware.camera2.utils.Decorator.invoke(Decorator.java:81)
at java.lang.reflect.Proxy.invoke(Proxy.java:393)
at $Proxy0.setTorchMode(Unknown Source)
at android.hardware.camera2.CameraManager$CameraManagerGlobal.setTorchMode(CameraManager.java:884)
at android.hardware.camera2.CameraManager.setTorchMode(CameraManager.java:501)
at com.example.sumesh.myapplication.FlashActivity.turnOffFlashLight(FlashActivity.java:204)
at com.example.sumesh.myapplication.FlashActivity$5.onClick(FlashActivity.java:150)
at android.view.View.performClick(View.java:5215)
at android.view.View$PerformClick.run(View.java:21196)
}
I have used the linear layout instead of the relativeIlayout, but I
am unable to figure out what thing is exactly posing the error,
all the permissions are already defined in the android manifest,
any suggestions?
Try this , Full code for taking picture using camera2 API , Fragment_Camera4
/**
* A simple {#link Fragment} subclass.
*/
public class Fragment_Camera4 extends Fragment implements
AspectRatioFragment.Listener {
private View view;
private static final String TAG = "MainActivity";
private static final int REQUEST_CAMERA_PERMISSION = 1;
private static final String FRAGMENT_DIALOG = "dialog";
private static final int[] FLASH_OPTIONS = {
CameraView.FLASH_OFF,
CameraView.FLASH_ON,
};
private static final int[] FLASH_ICONS = {
R.drawable.ic_flash_off,
R.drawable.ic_flash_on,
};
private static final int[] FLASH_TITLES = {
R.string.flash_auto,
R.string.flash_off,
R.string.flash_on,
};
private int mCurrentFlash;
private CameraView mCameraView;
private Handler mBackgroundHandler;
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.take_picture:
if (mCameraView != null) {
mCameraView.takePicture();
}
break;
case R.id.btn_switch_camera:
if (mCameraView != null) {
int facing = mCameraView.getFacing();
mCameraView.setFacing(facing == CameraView.FACING_FRONT ?
CameraView.FACING_BACK : CameraView.FACING_FRONT);
if (facing == CameraView.FACING_FRONT) {
btn_flash_onOff.setVisibility(View.VISIBLE);
} else {
btn_flash_onOff.setVisibility(View.GONE);
}
}
break;
case R.id.btn_flash_onOff:
if (mCameraView != null) {
mCurrentFlash = (mCurrentFlash + 1) % FLASH_OPTIONS.length;
btn_flash_onOff.setImageResource(FLASH_ICONS[mCurrentFlash]);
mCameraView.setFlash(FLASH_OPTIONS[mCurrentFlash]);
}
break;
}
}
};
ImageButton btn_flash_onOff;
public Fragment_Camera4() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
view = inflater.inflate(R.layout.fragment_fragment__camera4, container, false);
mCameraView = (CameraView) view.findViewById(R.id.camera);
if (mCameraView != null) {
mCameraView.addCallback(mCallback);
}
ImageButton fab = (ImageButton) view.findViewById(R.id.take_picture);
ImageButton btn_switch_camera = (ImageButton) view.findViewById(R.id.btn_switch_camera);
btn_flash_onOff = (ImageButton) view.findViewById(R.id.btn_flash_onOff);
if (fab != null) {
fab.setOnClickListener(mOnClickListener);
btn_switch_camera.setOnClickListener(mOnClickListener);
btn_flash_onOff.setOnClickListener(mOnClickListener);
}
return view;
}
#Override
public void onResume() {
super.onResume();
mCameraView.start();
}
#Override
public void onPause() {
mCameraView.stop();
super.onPause();
}
#Override
public void onDestroy() {
super.onDestroy();
if (mBackgroundHandler != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBackgroundHandler.getLooper().quitSafely();
} else {
mBackgroundHandler.getLooper().quit();
}
mBackgroundHandler = null;
}
}
#Override
public void onAspectRatioSelected(#NonNull AspectRatio ratio) {
if (mCameraView != null) {
Toast.makeText(getActivity(), ratio.toString(), Toast.LENGTH_SHORT).show();
mCameraView.setAspectRatio(ratio);
}
}
private Handler getBackgroundHandler() {
if (mBackgroundHandler == null) {
HandlerThread thread = new HandlerThread("background");
thread.start();
mBackgroundHandler = new Handler(thread.getLooper());
}
return mBackgroundHandler;
}
private CameraView.Callback mCallback
= new CameraView.Callback() {
#Override
public void onCameraOpened(CameraView cameraView) {
Log.d(TAG, "onCameraOpened");
}
#Override
public void onCameraClosed(CameraView cameraView) {
Log.d(TAG, "onCameraClosed");
}
#Override
public void onPictureTaken(CameraView cameraView, final byte[] data) {
Log.d(TAG, "onPictureTaken " + data.length);
Toast.makeText(cameraView.getContext(), R.string.picture_taken, Toast.LENGTH_SHORT)
.show();
getBackgroundHandler().post(new Runnable() {
#Override
public void run() {
String root = Environment.getExternalStorageDirectory().toString();
File myDir = new File(root + "/_saved_images");
myDir.mkdirs();
Random generator = new Random();
int n = 10000;
n = generator.nextInt(n);
String fname = "Image-" + n + ".jpg";
File file = new File(myDir, fname);
String final_path = myDir + "/" + fname;
OutputStream os = null;
try {
os = new FileOutputStream(file);
os.write(data);
os.close();
} catch (IOException e) {
Log.w(TAG, "Cannot write to " + file, e);
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
// Ignore
}
}
}
Bundle bundle = new Bundle();
bundle.putString("path", final_path);
bundle.putString("type", "picture");
Fragment_Post_Image_Detail post_image_detail = new Fragment_Post_Image_Detail();
post_image_detail.setArguments(bundle);
((BaseActivity) getActivity()).replaceFragmentWithBackstack(post_image_detail);
}
});
}
};
}
Then Cameraview class
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Set;
public class CameraView extends FrameLayout {
/** The camera device faces the opposite direction as the device's screen. */
public static final int FACING_BACK = Constants.FACING_BACK;
/** The camera device faces the same direction as the device's screen. */
public static final int FACING_FRONT = Constants.FACING_FRONT;
/** Direction the camera faces relative to device screen. */
#IntDef({FACING_BACK, FACING_FRONT})
#Retention(RetentionPolicy.SOURCE)
public #interface Facing {
}
/** Flash will not be fired. */
public static final int FLASH_OFF = Constants.FLASH_OFF;
/** Flash will always be fired during snapshot. */
public static final int FLASH_ON = Constants.FLASH_ON;
/** Constant emission of light during preview, auto-focus and snapshot. */
public static final int FLASH_TORCH = Constants.FLASH_TORCH;
/** Flash will be fired automatically when required. */
public static final int FLASH_AUTO = Constants.FLASH_AUTO;
/** Flash will be fired in red-eye reduction mode. */
public static final int FLASH_RED_EYE = Constants.FLASH_RED_EYE;
/** The mode for for the camera device's flash control */
#IntDef({FLASH_OFF, FLASH_ON, FLASH_TORCH, FLASH_AUTO, FLASH_RED_EYE})
public #interface Flash {
}
CameraViewImpl mImpl;
private final CallbackBridge mCallbacks;
private boolean mAdjustViewBounds;
private final DisplayOrientationDetector mDisplayOrientationDetector;
public CameraView(Context context) {
this(context, null);
}
public CameraView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
#SuppressWarnings("WrongConstant")
public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (isInEditMode()){
mCallbacks = null;
mDisplayOrientationDetector = null;
return;
}
// Internal setup
final PreviewImpl preview = createPreviewImpl(context);
mCallbacks = new CallbackBridge();
if (Build.VERSION.SDK_INT < 21) {
mImpl = new Camera1(mCallbacks, preview);
} else if (Build.VERSION.SDK_INT < 23) {
mImpl = new Camera2(mCallbacks, preview, context);
} else {
mImpl = new Camera2Api23(mCallbacks, preview, context);
}
// Attributes
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView, defStyleAttr,
R.style.Widget_CameraView);
mAdjustViewBounds = a.getBoolean(R.styleable.CameraView_android_adjustViewBounds, false);
setFacing(a.getInt(R.styleable.CameraView_facing, FACING_BACK));
String aspectRatio = a.getString(R.styleable.CameraView_aspectRatio);
if (aspectRatio != null) {
setAspectRatio(AspectRatio.parse(aspectRatio));
} else {
setAspectRatio(Constants.DEFAULT_ASPECT_RATIO);
}
setAutoFocus(a.getBoolean(R.styleable.CameraView_autoFocus, true));
setFlash(a.getInt(R.styleable.CameraView_flash, Constants.FLASH_AUTO));
a.recycle();
// Display orientation detector
mDisplayOrientationDetector = new DisplayOrientationDetector(context) {
#Override
public void onDisplayOrientationChanged(int displayOrientation) {
mImpl.setDisplayOrientation(displayOrientation);
}
};
}
#NonNull
private PreviewImpl createPreviewImpl(Context context) {
PreviewImpl preview;
if (Build.VERSION.SDK_INT < 14) {
preview = new SurfaceViewPreview(context, this);
} else {
preview = new TextureViewPreview(context, this);
}
return preview;
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!isInEditMode()) {
mDisplayOrientationDetector.enable(ViewCompat.getDisplay(this));
}
}
#Override
protected void onDetachedFromWindow() {
if (!isInEditMode()) {
mDisplayOrientationDetector.disable();
}
super.onDetachedFromWindow();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (isInEditMode()){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
// Handle android:adjustViewBounds
if (mAdjustViewBounds) {
if (!isCameraOpened()) {
mCallbacks.reserveRequestLayoutOnOpen();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
final AspectRatio ratio = getAspectRatio();
assert ratio != null;
int height = (int) (MeasureSpec.getSize(widthMeasureSpec) * ratio.toFloat());
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
}
super.onMeasure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
} else if (widthMode != MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
final AspectRatio ratio = getAspectRatio();
assert ratio != null;
int width = (int) (MeasureSpec.getSize(heightMeasureSpec) * ratio.toFloat());
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(width, MeasureSpec.getSize(widthMeasureSpec));
}
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
heightMeasureSpec);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
// Measure the TextureView
int width = getMeasuredWidth();
int height = getMeasuredHeight();
AspectRatio ratio = getAspectRatio();
if (mDisplayOrientationDetector.getLastKnownDisplayOrientation() % 180 == 0) {
ratio = ratio.inverse();
}
assert ratio != null;
if (height < width * ratio.getY() / ratio.getX()) {
mImpl.getView().measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(width * ratio.getY() / ratio.getX(),
MeasureSpec.EXACTLY));
} else {
mImpl.getView().measure(
MeasureSpec.makeMeasureSpec(height * ratio.getX() / ratio.getY(),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
}
#Override
protected Parcelable onSaveInstanceState() {
SavedState state = new SavedState(super.onSaveInstanceState());
state.facing = getFacing();
state.ratio = getAspectRatio();
state.autoFocus = getAutoFocus();
state.flash = getFlash();
return state;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setFacing(ss.facing);
setAspectRatio(ss.ratio);
setAutoFocus(ss.autoFocus);
setFlash(ss.flash);
}
/**
* Open a camera device and start showing camera preview. This is typically called from
* {#link Activity#onResume()}.
*/
public void start() {
if (!mImpl.start()) {
//store the state ,and restore this state after fall back o Camera1
Parcelable state=onSaveInstanceState();
// Camera2 uses legacy hardware layer; fall back to Camera1
mImpl = new Camera1(mCallbacks, createPreviewImpl(getContext()));
onRestoreInstanceState(state);
mImpl.start();
}
}
/**
* Stop camera preview and close the device. This is typically called from
* {#link Activity#onPause()}.
*/
public void stop() {
mImpl.stop();
}
/**
* #return {#code true} if the camera is opened.
*/
public boolean isCameraOpened() {
return mImpl.isCameraOpened();
}
/**
* Add a new callback.
*
* #param callback The {#link Callback} to add.
* #see #removeCallback(Callback)
*/
public void addCallback(#NonNull Callback callback) {
mCallbacks.add(callback);
}
/**
* Remove a callback.
*
* #param callback The {#link Callback} to remove.
* #see #addCallback(Callback)
*/
public void removeCallback(#NonNull Callback callback) {
mCallbacks.remove(callback);
}
/**
* #param adjustViewBounds {#code true} if you want the CameraView to adjust its bounds to
* preserve the aspect ratio of camera.
* #see #getAdjustViewBounds()
*/
public void setAdjustViewBounds(boolean adjustViewBounds) {
if (mAdjustViewBounds != adjustViewBounds) {
mAdjustViewBounds = adjustViewBounds;
requestLayout();
}
}
/**
* #return True when this CameraView is adjusting its bounds to preserve the aspect ratio of
* camera.
* #see #setAdjustViewBounds(boolean)
*/
public boolean getAdjustViewBounds() {
return mAdjustViewBounds;
}
/**
* Chooses camera by the direction it faces.
*
* #param facing The camera facing. Must be either {#link #FACING_BACK} or
* {#link #FACING_FRONT}.
*/
public void setFacing(#Facing int facing) {
mImpl.setFacing(facing);
}
/**
* Gets the direction that the current camera faces.
*
* #return The camera facing.
*/
#Facing
public int getFacing() {
//noinspection WrongConstant
return mImpl.getFacing();
}
/**
* Gets all the aspect ratios supported by the current camera.
*/
public Set<AspectRatio> getSupportedAspectRatios() {
return mImpl.getSupportedAspectRatios();
}
/**
* Sets the aspect ratio of camera.
*
* #param ratio The {#link AspectRatio} to be set.
*/
public void setAspectRatio(#NonNull AspectRatio ratio) {
if (mImpl.setAspectRatio(ratio)) {
requestLayout();
}
}
/**
* Gets the current aspect ratio of camera.
*
* #return The current {#link AspectRatio}. Can be {#code null} if no camera is opened yet.
*/
#Nullable
public AspectRatio getAspectRatio() {
return mImpl.getAspectRatio();
}
/**
* Enables or disables the continuous auto-focus mode. When the current camera doesn't support
* auto-focus, calling this method will be ignored.
*
* #param autoFocus {#code true} to enable continuous auto-focus mode. {#code false} to
* disable it.
*/
public void setAutoFocus(boolean autoFocus) {
mImpl.setAutoFocus(autoFocus);
}
/**
* Returns whether the continuous auto-focus mode is enabled.
*
* #return {#code true} if the continuous auto-focus mode is enabled. {#code false} if it is
* disabled, or if it is not supported by the current camera.
*/
public boolean getAutoFocus() {
return mImpl.getAutoFocus();
}
/**
* Sets the flash mode.
*
* #param flash The desired flash mode.
*/
public void setFlash(#Flash int flash) {
mImpl.setFlash(flash);
}
/**
* Gets the current flash mode.
*
* #return The current flash mode.
*/
#Flash
public int getFlash() {
//noinspection WrongConstant
return mImpl.getFlash();
}
/**
* Take a picture. The result will be returned to
* {#link Callback#onPictureTaken(CameraView, byte[])}.
*/
public void takePicture() {
mImpl.takePicture();
}
private class CallbackBridge implements CameraViewImpl.Callback {
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private boolean mRequestLayoutOnOpen;
CallbackBridge() {
}
public void add(Callback callback) {
mCallbacks.add(callback);
}
public void remove(Callback callback) {
mCallbacks.remove(callback);
}
#Override
public void onCameraOpened() {
if (mRequestLayoutOnOpen) {
mRequestLayoutOnOpen = false;
requestLayout();
}
for (Callback callback : mCallbacks) {
callback.onCameraOpened(CameraView.this);
}
}
#Override
public void onCameraClosed() {
for (Callback callback : mCallbacks) {
callback.onCameraClosed(CameraView.this);
}
}
#Override
public void onPictureTaken(byte[] data) {
for (Callback callback : mCallbacks) {
callback.onPictureTaken(CameraView.this, data);
}
}
public void reserveRequestLayoutOnOpen() {
mRequestLayoutOnOpen = true;
}
}
protected static class SavedState extends BaseSavedState {
#Facing
int facing;
AspectRatio ratio;
boolean autoFocus;
#Flash
int flash;
#SuppressWarnings("WrongConstant")
public SavedState(Parcel source, ClassLoader loader) {
super(source);
facing = source.readInt();
ratio = source.readParcelable(loader);
autoFocus = source.readByte() != 0;
flash = source.readInt();
}
public SavedState(Parcelable superState) {
super(superState);
}
#Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(facing);
out.writeParcelable(ratio, 0);
out.writeByte((byte) (autoFocus ? 1 : 0));
out.writeInt(flash);
}
public static final Parcelable.Creator<SavedState> CREATOR
= ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
#Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}
#Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
});
}
/**
* Callback for monitoring events about {#link CameraView}.
*/
#SuppressWarnings("UnusedParameters")
public abstract static class Callback {
/**
* Called when camera is opened.
*
* #param cameraView The associated {#link CameraView}.
*/
public void onCameraOpened(CameraView cameraView) {
}
/**
* Called when camera is closed.
*
* #param cameraView The associated {#link CameraView}.
*/
public void onCameraClosed(CameraView cameraView) {
}
/**
* Called when a picture is taken.
*
* #param cameraView The associated {#link CameraView}.
* #param data JPEG data.
*/
public void onPictureTaken(CameraView cameraView, byte[] data) {
}
}
}
and xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorgrey">
<com.google.android.cameraview.CameraView
android:id="#+id/camera"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:adjustViewBounds="true"
android:background="#android:color/black" />
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true" />
<ImageButton
android:id="#+id/take_picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
android:background="#0000"
app:srcCompat="#drawable/ic_take_pic" />
<ImageButton
android:id="#+id/btn_switch_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_margin="20dp"
android:background="#0000"
app:srcCompat="#drawable/ic_reset_camera" />
<ImageButton
android:id="#+id/btn_flash_onOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="20dp"
android:background="#0000"
app:srcCompat="#drawable/ic_flash_off" />
</RelativeLayout>
Also DONT FORGET TO ADD RUNTIME PERMISSIONS in OnCreate()

Getting the data from the Custom View Renderer in the platform specific from Shared Project in Xamarin.Forms

I am developing a mobile application using Xamarin.Forms. In my application, I am implementing custom camera using a custom view. I have successfully implemented the custom camera view. What I am trying to do now is, I would like to retrieve some data from custom view renderer in the platform-specific when a button is clicked which is inside the shared project. See my scenario below.
This is my CameraPage.xaml in the shared project.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:local="clr-namespace:MementoApp.Views;"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Title="Capture your moment"
Padding="0,20,0,0"
x:Class="MementoApp.Views.CameraPage">
<ContentPage.Content>
<StackLayout Orientation="Vertical">
<local:CameraPreview x:Name="Camera" Camera="Rear" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
<StackLayout Orientation="Vertical" HorizontalOptions="Center">
<Button x:Name="buttonCapturePhoto" Text="Capture photo" />
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
As you can see, the CameraPreview is the custom view. I also added a button below it.
this is the CameraPreview class
namespace MementoApp.Views
{
public enum CameraOptions
{
Rear,
Front
}
public class CameraPreview : View
{
public static readonly BindableProperty CameraProperty = BindableProperty.Create(
propertyName: "Camera",
returnType: typeof(CameraOptions),
declaringType: typeof(CameraPreview),
defaultValue: CameraOptions.Rear);
public CameraOptions Camera
{
get { return (CameraOptions)GetValue(CameraProperty); }
set { SetValue(CameraProperty, value); }
}
}
}
In the Android Project, I created the Renderer for camera custom view like this.
[assembly: ExportRenderer(typeof(MementoApp.Views.CameraPreview), typeof(MementoApp.Droid.CameraPreviewRenderer))]
namespace MementoApp.Droid
{
public class CameraPreviewRenderer : ViewRenderer<MementoApp.Views.CameraPreview, MementoApp.Droid.CameraPreview>
{
CameraPreview cameraPreview;
public CameraPreview CameraPreview { get { return this.cameraPreview; } }
public CameraPreviewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<MementoApp.Views.CameraPreview> e)
{
base.OnElementChanged(e);
if (Control == null)
{
cameraPreview = new CameraPreview(Context);
SetNativeControl(cameraPreview);
}
if (e.OldElement != null)
{
// Unsubscribe
cameraPreview.Click -= OnCameraPreviewClicked;
}
if (e.NewElement != null)
{
Control.Preview = Camera.Open((int)e.NewElement.Camera);
// Subscribe
cameraPreview.Click += OnCameraPreviewClicked;
}
}
void OnCameraPreviewClicked(object sender, EventArgs e)
{
if (cameraPreview.IsPreviewing)
{
cameraPreview.Preview.StopPreview();
cameraPreview.IsPreviewing = false;
}
else
{
cameraPreview.Preview.StartPreview();
cameraPreview.IsPreviewing = true;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Control.Preview.Release();
}
base.Dispose(disposing);
}
}
}
This is the CameraPreview class in the Android Project.
public sealed class CameraPreview : ViewGroup, ISurfaceHolderCallback
{
SurfaceView surfaceView;
ISurfaceHolder holder;
Camera.Size previewSize;
IList<Camera.Size> supportedPreviewSizes;
Camera camera;
IWindowManager windowManager;
public bool IsPreviewing { get; set; }
public Camera Preview
{
get { return camera; }
set
{
camera = value;
if (camera != null)
{
supportedPreviewSizes = Preview.GetParameters().SupportedPreviewSizes;
RequestLayout();
}
}
}
public CameraPreview(Context context)
: base(context)
{
surfaceView = new SurfaceView(context);
AddView(surfaceView);
windowManager = Context.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
IsPreviewing = false;
holder = surfaceView.Holder;
holder.AddCallback(this);
}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = ResolveSize(SuggestedMinimumWidth, widthMeasureSpec);
int height = ResolveSize(SuggestedMinimumHeight, heightMeasureSpec);
SetMeasuredDimension(width, height);
if (supportedPreviewSizes != null)
{
previewSize = GetOptimalPreviewSize(supportedPreviewSizes, width, height);
}
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly);
var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly);
surfaceView.Measure(msw, msh);
surfaceView.Layout(0, 0, r - l, b - t);
}
public void SurfaceCreated(ISurfaceHolder holder)
{
try
{
if (Preview != null)
{
Preview.SetPreviewDisplay(holder);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(#" ERROR: ", ex.Message);
}
}
public void SurfaceDestroyed(ISurfaceHolder holder)
{
if (Preview != null)
{
Preview.StopPreview();
}
}
public void SurfaceChanged(ISurfaceHolder holder, Android.Graphics.Format format, int width, int height)
{
var parameters = Preview.GetParameters();
parameters.SetPreviewSize(previewSize.Width, previewSize.Height);
RequestLayout();
switch (windowManager.DefaultDisplay.Rotation)
{
case SurfaceOrientation.Rotation0:
camera.SetDisplayOrientation(90);
break;
case SurfaceOrientation.Rotation90:
camera.SetDisplayOrientation(0);
break;
case SurfaceOrientation.Rotation270:
camera.SetDisplayOrientation(180);
break;
}
Preview.SetParameters(parameters);
Preview.StartPreview();
IsPreviewing = true;
}
Camera.Size GetOptimalPreviewSize(IList<Camera.Size> sizes, int w, int h)
{
const double AspectTolerance = 0.1;
double targetRatio = (double)w / h;
if (sizes == null)
{
return null;
}
Camera.Size optimalSize = null;
double minDiff = double.MaxValue;
int targetHeight = h;
foreach (Camera.Size size in sizes)
{
double ratio = (double)size.Width / size.Height;
if (Math.Abs(ratio - targetRatio) > AspectTolerance)
continue;
if (Math.Abs(size.Height - targetHeight) < minDiff)
{
optimalSize = size;
minDiff = Math.Abs(size.Height - targetHeight);
}
}
if (optimalSize == null)
{
minDiff = double.MaxValue;
foreach (Camera.Size size in sizes)
{
if (Math.Abs(size.Height - targetHeight) < minDiff)
{
optimalSize = size;
minDiff = Math.Abs(size.Height - targetHeight);
}
}
}
return optimalSize;
}
}
What I am trying to do now is I want to retrieve some data from the CameraPreviewRenderer from the SharedProject when the "Capture" button is clicked. I created the event for capture button like this in the CameraPage.xaml.cs in the shared project like this.
void ButtonCapturePhoto_Clicked(object sender, EventArgs e)
{
}
Inside that event, I want to retrieve the data from the CameraPreviewRenderer in the Android project. For example, I created a property in the CameraPreviewRenderer class like this.
public string Data { get { return "Message from the Android Project"; } }
Inside the event like, maybe I would retrieve that property like this.
Camera.Data //Data would be "Message from the Android Project" and Camera is the custom view.
How can I achieve this?
Xamarin.Forms Messaging Center is one way to do it. Just subscribe to the event in your Custom Renderer class.
Another way would be to create the Data property in your CameraPreview class instead of the CameraPreviewRenderer, and then you can set that property when you click the button. IE,
void ButtonCapturePhoto_Clicked(object sender, EventArgs e)
{
Camera.Data = ...;
}
Now, you'll be able to get the Data in the OnElementPropertyChanged method in your CameraPreviewRenderer class.
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == CameraPreview.DataProperty.PropertyName)
{
... do what you need to do ...
}
}
UPDATE: The best way to do it would be to use an event handler. In your CameraPreview class, create an event handler and method to invoke it:
public event eventHandler<MyDataObject> OnData;
public void InvokeOnData(MyDataObject obj)
{
OnData?.Invoke(this, obj);
}
Then in your CameraPreviewRenderer's OnElementChanged method, you can set up the event handler:
protected override void OnElementPropertyChanged(...)
{
var cameraPreview = (CameraPreview)this.Element;
cameraPreview.OnData += HandleData;
}
private void HandleData(object sender, MyCameraObject e)
{
... do what you need to do ...
}
And then, you can invoke it this way:
void ButtonCapturePhoto_Clicked(object sender, EventArgs e)
{
Camera.InvokeOnData(your data);
}
You can use DependencyService in the ButtonCapturePhoto_Clicked method, below is my demo:
In SharedProject, define interface IGetData:
namespace MementoApp
{
public interface IGetData
{
string getData();
}
}
In Android project, implement the interface:
[assembly: Xamarin.Forms.Dependency(typeof(GetDataImpl))]
namespace MementoApp.Droid
{
public class GetDataImpl :IGetData
{
public string getData()
{
//in this method, you can also use other approaches to get the data, like delegate/event/interface.
CameraPreviewRenderer cpr= new CameraPreviewRenderer(Application.Context);
cpr.initData();
return cpr.Data;
}
}
}
Your CameraPreviewRenderer add these( I use a method-initData to simulate the Data's generation):
public string Data;
public void initData() {
Data= "Message from the Android Project";
}
And at the last, call it in the ButtonCapturePhoto_Clicked:
private void ButtonCapturePhoto_Clicked(object sender, EventArgs e)
{
string data = DependencyService.Get<IGetData>().getData();
System.Diagnostics.Debug.Write("data====="+data);
}

How to use matrix camera preview left and right reverse on android?

I want camera preview left and right reverse,
and I search for google, answer is use Matrix
is it right answer?
first, my source.
MainActivity.class
private Agen agen = null;
protected static Activity mainActivity = null;
#Override
protected void onCreate(final Bundle savedInstanceState) {
agen = new Agen();
agen.CameraSetting();
}
public static Activity getMainActivity() {
return mainActivity;
}
Agen.class
private CamLiveView camLiveView;
private final FrameLayout liveFrame;
private final Context context;
public Agen() {
Activity activity = MainActivity.getMainActivity();
context = activity;
liveFrame = (FrameLayout) activity.findViewById(R.id.liveview);
public boolean CameraSetting() {
boolean state = false;
try {
LiveCam livecam = CamManage.getInstance();
camLiveView = new CamLiveView(context, livecam, this);
if (liveFrame != null)
liveFrame.addView(camLiveView);
state = true;
} catch (Exception e) {
e.printStackTrace();
} return state;
}
CamManage.class
private static Camera mCamera = null;
public static LiveCam getInstance() {
if (mCamera == null) {
int cameras = Camera.getNumberOfCameras();
mCamera = Camera.open(0);
if (mCamera != null) {
mCamera.lock();
}
} else {
}
return getLiveCam();
}
getLiveCam
static private LiveCam getLiveCam() {
LiveCam livecam = new LiveCam() ;
Camera.Parameters params = mCamera.getParameters();
liveCam.preSize = params.getPreviewSize();
liveCam.preFormat = params.getPreviewFormat();
liveCam.camera = mCamera;
return liveCam;
}
LiveCam.class
public class LiveCam {
public Camera camera;
public Camera.Size preSize;
public int preFormat;
}
this my source.
but I don't know where use matrix part.
in other words, I want camera preview left and right reverse.
please advice for me
thanks.
add
public class CamLiveView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private Camer mCamera;
private int preWidth;
private int preHeight;
private int preFormat;
private boolean playing = false;
public CamLiveView(Context context, LiveCam livecam) {
super(context);
mHolder = getHolder();
mHolder.addCallback(this);
mCamera = livecam.camera;
preWidth = livecam.preSize.width;
preHeight = livecam.preSize.height;
preFormat = livecam.previewFormat;
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
cameraPlay();
}
public boolean cameraPlay() {
if (mHolder == null)
return false;
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
playing = true;
} catch (Exception e) {
return false;
} return true;
}

Xamarin Android drawn bitmaps in grid view causes slow and choppy scrolling

I am building an Android application, using the Xamarin framework, that displays profile photos for users. The photos may either be drawn within an oval, like you'd see on any social network, or in a rectangle based on what screen the user is on. The images I am drawing are downloaded from a web service and may be any size. Thus I must scale the photos, fix any rotation issues, and then draw them either in a rectangle or an oval.
The entire process I have works fine, but the problem I have is when I load several of the profile views into a grid view, then the performance of the scroll event becomes very choppy and slow. I also get warnings in the console saying that 30 frames have been skipped, and that I may be doing to much on the main thread.
To try and fix the issue I've pushed the loading of the images to a background task, and that is also working great. However, the actual onDraw event is drawn on the main thread as it's drawn by the OS.
I've tracked down the issue to the canvas.drawBitmap() function call. When I comment out this command then the slow scrolling issues goes away, and I no longer get the warnings in the logs. This tells me that my process is efficient enough without the draw command, but when I add the draw command I start to get performance issues.
Does anyone know how I can optimize the drawing process so that I can achieve smooth scrolling? My code is below.
This is my grid view adapter class that loads the profile photo views...
namespace AndroidApp
{
public class GridViewAdapter : ArrayAdapter<UserContact>
{
private Activity ParentActivity;
#region Interface
public interface GridViewAdapterCallback
{
void DidSelectRow(Conversation conversation);
}
#endregion
#region Initialization
private int LayoutResourceId;
private List<UserContact> Data = null;
public GridViewAdapterCallback callback;
public GridViewAdapter (Context context, int layoutResourceId, List<UserContact> data, Activity parentActivity) : base (context, layoutResourceId, data)
{
this.LayoutResourceId = layoutResourceId;
this.Data = data;
this.ParentActivity = parentActivity;
}
public void SetData (List<UserContact> data)
{
this.Data = data;
}
#endregion
#region List Delegates
public override Android.Views.View GetView (int position, Android.Views.View convertView, Android.Views.ViewGroup parent)
{
if (convertView == null)
{
LayoutInflater inflater = LayoutInflater.From (this.Context);
convertView = inflater.Inflate(this.LayoutResourceId, parent, false);
}
UserContact row = this.Data[position];
this.SetContactToGridItem (convertView, row);
return convertView;
}
#endregion
#region Setters
private void SetContactToGridItem (Android.Views.View view, UserContact contact)
{
// get view references
ProfileImageView imageView = view.FindViewById(Resource.Id.profileImageView).JavaCast<ProfileImageView>();
imageView.ProfileImageViewStyle = ProfileImageViewStyle.Square;
imageView.ResetImage ();
imageView.SetContact (contact, this.ParentActivity);
TextView textView = (TextView)view.FindViewById(Resource.Id.textView);
textView.SetText (contact.GetFullName (), TextView.BufferType.Normal);
}
#endregion
}
}
This is my main profile view class...
namespace AndroidApp
{
public enum ProfileImageViewColor
{
Default = 0,
White = 1
};
public enum ProfileImageViewStyle
{
Default = 0,
Square = 1
};
public class ProfileImageView : ImageView
{
public ProfileImageViewColor ProfileImageViewColor { get; set; }
public ProfileImageViewStyle ProfileImageViewStyle { get; set; }
private User User { get; set; }
private UserContact Contact { get; set; }
Bitmap Bitmap;
private Activity Activity { get; set; }
public ProfileImageView (System.IntPtr intPtr, Android.Runtime.JniHandleOwnership owner) : base (intPtr, owner)
{
Initialize ();
}
public ProfileImageView (Context context) : base (context)
{
Initialize ();
}
public ProfileImageView (Context context, IAttributeSet attrs) : base (context, attrs)
{
Initialize ();
}
public ProfileImageView (Context context, IAttributeSet attrs, int defStyle) : base (context, attrs, defStyle)
{
Initialize ();
}
void Initialize ()
{
this.ProfileImageViewColor = ProfileImageViewColor.Default;
this.ProfileImageViewStyle = ProfileImageViewStyle.Default;
this.SetScaleType (ScaleType.FitCenter);
this.CropToPadding = true;
}
#region Setters
public void SetUser (User user, Activity activity)
{
this.User = user;
this.Activity = activity;
if (this.User != null)
{
byte[] imageData = this.User.GetProfilePhoto ();
if (imageData != null)
{
this.SetImageData (this.User.GetProfilePhotoPath (), imageData);
}
else
{
UserBusiness.GetUserPhoto (this.User, (Shared.Error error, User usr) => {
activity.RunOnUiThread (() => {
this.SetImageData (this.User.GetProfilePhotoPath (), this.User.GetProfilePhoto ());
});
});
}
}
}
public void SetContact (UserContact contact, Activity activity)
{
this.Contact = contact;
this.Activity = activity;
if (this.Contact != null)
{
byte[] imageData = this.Contact.GetProfilePhoto();
if (imageData != null)
{
this.SetImageData (this.Contact.GetProfilePhotoPath (), imageData);
}
else
{
UserContactPhotoDownloadManager.CreateManager ().DownloadProfilePhoto (contact, (Shared.Error error, UserContact userContact) => {
activity.RunOnUiThread (() => {
this.SetImageData (this.Contact.GetProfilePhotoPath (), this.Contact.GetProfilePhoto ());
});
});
}
}
}
public override void SetImageBitmap (Bitmap bitmap)
{
if (bitmap == null)
{
this.SetImageResource (this.GetDefaultProfileImageID ());
}
else
{
ProfileImageDrawable drawable = new ProfileImageDrawable (bitmap, this.Width, this.Height, this.ProfileImageViewStyle);
this.SetImageDrawable (drawable);
}
}
public void ResetImage ()
{
if (this.Bitmap != null)
this.Bitmap.Recycle ();
this.SetImageBitmap (null);
}
#endregion
#region Private
private void SetImageData (byte[] imageData)
{
if (this.Bitmap != null)
{
this.Bitmap.Recycle ();
this.Bitmap = null;
}
if (imageData == null)
{
this.SetImageResource (this.GetDefaultProfileImageID ());
}
else
{
this.Bitmap = BitmapFactory.DecodeByteArray (imageData, 0, imageData.Length);
ProfileImageDrawable drawable = new ProfileImageDrawable (this.Bitmap, this.Width, this.Height, this.ProfileImageViewStyle);
this.SetImageDrawable (drawable);
}
}
private void SetImageData (string filePath, byte[] imageData)
{
if (this.Bitmap != null)
{
this.Bitmap.Recycle ();
this.Bitmap = null;
}
if (imageData == null)
{
this.SetImageResource (this.GetDefaultProfileImageID ());
}
else
{
this.SetImageAsync (filePath);
}
}
private void SetImageAsync (string filePath)
{
ImageDrawTask task = new ImageDrawTask (filePath, this, (Drawable drawable) => {
this.Activity.RunOnUiThread (() => {
this.SetImageDrawable (drawable);
});
});
task.Execute ();
}
private int GetDefaultProfileImageID ()
{
if (this.ProfileImageViewColor == ProfileImageViewColor.Default)
return (int)typeof (Resource.Drawable).GetField ("profile_image_placeholder").GetValue (null);
else
return (int)typeof (Resource.Drawable).GetField ("profile_image_placeholder_white").GetValue (null);
}
#endregion
}
public class ImageDrawTask: AsyncTask {
public delegate void ImageDrawTaskCompletion (Drawable drawable);
private string FilePath;
private ProfileImageView ImageView;
private ImageDrawTaskCompletion Completion;
public ImageDrawTask (string filePath, ProfileImageView imageView, ImageDrawTaskCompletion completion)
{
this.FilePath = filePath;
this.ImageView = imageView;
this.Completion = completion;
}
protected override void OnPreExecute()
{
}
protected override Java.Lang.Object DoInBackground(params Java.Lang.Object[] #params)
{
Bitmap bitmap = ImageUtilities.FixRotation (this.FilePath);
ProfileImageDrawable drawable = new ProfileImageDrawable (bitmap, this.ImageView.Width, this.ImageView.Height, this.ImageView.ProfileImageViewStyle);
Completion (drawable);
return null;
}
protected override void OnPostExecute(Java.Lang.Object result)
{
}
}
}
And finally here is my drawable class...
namespace AndroidApp
{
public class ProfileImageDrawable : Drawable
{
Bitmap Bitmap;
ProfileImageViewStyle Style;
int Width;
int Height;
private RectF DrawFrame;
public ProfileImageDrawable (Bitmap bmp, int width, int height, ProfileImageViewStyle style)
{
this.Bitmap = bmp;
this.Style = style;
this.DrawFrame = new RectF ();
this.Width = width;
this.Height = height;
}
public override void Draw (Canvas canvas)
{
if (this.Style == ProfileImageViewStyle.Square)
{
canvas.DrawBitmap (this.Bitmap, this.GetMatrix (this.Bitmap, this.Width, this.Height), null);
}
else
{
BitmapShader bmpShader = new BitmapShader (this.Bitmap, Shader.TileMode.Clamp, Shader.TileMode.Clamp);
bmpShader.SetLocalMatrix (this.GetMatrix (this.Bitmap, this.Width, this.Height));
Paint paint = new Paint () { AntiAlias = true, Dither = true };
paint.SetShader (bmpShader);
canvas.DrawOval (this.DrawFrame, paint);
}
}
protected override void OnBoundsChange (Rect bounds)
{
base.OnBoundsChange (bounds);
this.DrawFrame.Set (0, 0, bounds.Width (), bounds.Height ());
}
public override int IntrinsicWidth {
get {
return this.Width;
}
}
public override int IntrinsicHeight {
get {
return this.Height;
}
}
public override void SetAlpha (int alpha)
{
}
public override int Opacity {
get {
return (int)Format.Opaque;
}
}
public override void SetColorFilter (ColorFilter cf)
{
}
private Matrix GetMatrix (Bitmap bmp, int width, int height)
{
Matrix mtx = new Matrix ();
float scaleWidth = ((float) width) / bmp.Width;
float scaleHeight = ((float) height) / bmp.Height;
float newWidth = 0;
float newHeight = 0;
if (scaleWidth > scaleHeight)
{
mtx.PostScale (scaleWidth, scaleWidth);
newWidth = scaleWidth * bmp.Width;
newHeight = scaleWidth * bmp.Height;
}
else
{
mtx.PostScale (scaleHeight, scaleHeight);
newWidth = scaleHeight * bmp.Width;
newHeight = scaleHeight * bmp.Height;
}
mtx.PostTranslate ((width - newWidth) / 2, (height - newHeight) / 2);
return mtx;
}
}
}

SurfaceView not working inside PopupWindow

I gonna show a preview using a PopupWindow based on AirView feature of Samsung SPen
But the problem is that the SurfaceView is not created and non of the SurfaceHolder.Callback methods are called.
The surface region becomes transparent when the popup is displayed because the surface is not created at all.
SurfaceView is not created and is transparent:
HoverPreview:
public class HoverPreview extends LinearLayout implements View.OnHoverListener, SurfaceHolder.Callback {
private static final String TAG = "HoverPreview";
private SurfaceHolder mHolder = null;
View mAnchorView = null;
String videoPath;
int position;
private boolean IsMediaPlayerReady = false;
private MediaPlayer mMediaPlayer;
private SurfaceView mSurfaceView;
Context context;
public HoverPreview(Context context, String videoPath, int position) {
super(context);
this.videoPath = videoPath;
this.position = position;
setupLayout(context);
}
public HoverPreview(Context context, AttributeSet attrs) {
super(context, attrs);
setupLayout(context);
}
public HoverPreview(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setupLayout(context);
}
private void setupLayout(Context context) {
this.context = context;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.media_browser_hover, this);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
}
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
Log.d(TAG, "HoverSurface created");
final Surface surface = surfaceHolder.getSurface();
if (surface == null) return;
if (!surface.isValid()) return;
mHolder = surfaceHolder;
mMediaPlayer = new MediaPlayer();
try {
mMediaPlayer.setDataSource(videoPath);
} catch (IOException e) {
e.printStackTrace();
}
mMediaPlayer.setDisplay(mHolder);
mAnchorView.setTag(mMediaPlayer);
mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
#Override
public void onVideoSizeChanged(MediaPlayer mediaPlayer, int i, int i2) {
mHolder.setFixedSize(i, i2);
}
});
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
Log.d(TAG, "MediaPlayer preview is prepared");
IsMediaPlayerReady = true;
if (mMediaPlayer != null && IsMediaPlayerReady) {
if (position > 0)
mMediaPlayer.seekTo(position);
mMediaPlayer.start();
}
}
});
Log.d(TAG, "MediaPlayer is created");
try {
mMediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i2, int i3) {
Log.d(TAG, "HoverSurface changed");
}
#Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
Log.d(TAG, "HoverSurface destroyed");
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
//thumbnailImageView.setTag(null);
}
}
public void setAnchorView(View view) {
mAnchorView = view;
}
#Override
public boolean onHover(View view, MotionEvent motionEvent) {
try {
if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
Log.d(TAG, "ACTION_HOVER_ENTER");
mSurfaceView = (SurfaceView) findViewById(R.id.media_browser_hoverSurfaceView);
mHolder = mSurfaceView.getHolder();
if (mHolder != null) {
mHolder.addCallback(this);
}
} else if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
Log.d(TAG, "ACTION_HOVER_EXIT");
if (mAnchorView.getTag() != null) {
MediaPlayer mMediaPlayer = (MediaPlayer) mAnchorView.getTag();
mMediaPlayer.stop();
mMediaPlayer.release();
mAnchorView.setTag(null);
}
}
} catch (Exception e) {
Log.e(TAG, e.getMessage() + Utils.toString(e.getStackTrace()));
}
return false;
}
}
The code to show the preview:
final PopupWindow popupWindow = new PopupWindow(context);
final HoverPreview hoverPreview = new HoverPreview(context, videoPath, 0);
hoverPreview.setAnchorView(thumbnailImageView);
thumbnailImageView.setOnHoverListener(new View.OnHoverListener() {
#Override
public boolean onHover(View view, MotionEvent motionEvent) {
hoverPreview.onHover(view, motionEvent);
if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
popupWindow.setContentView(hoverPreview);
popupWindow.setWidth(600);
popupWindow.setHeight(400);
popupWindow.showAtLocation(thumbnailImageView, ToolHoverPopup.Gravity.NO_GRAVITY, 10, 10);
Log.d(TAG, "Manual Hover Enter");
} else if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
Log.d(TAG, "Manual Hover Exit");
if (popupWindow != null)
popupWindow.dismiss();
}
return true;
});
Here's my complete working solution:
I borrowed some code from ToolHoverPopup class from SPen library, also I customized for this special popup so that nothing is created or inflated until the actual hovering is happened so that we don't consume resources for enabling such a preview in lists.
We need to have our preview attached to a Window so because of this we have to manage all the underlying job of positioning which is normally done by PopupWindow, so I completely removed the dependency on the PopupWindow and now my HoverPreview class is fully working and manages all the jobs, also it has the ability to determine the Hover Detection delay in milliseconds.
Screenshot (SurfaceView is created)
Usage: (Since the layout contains SurfaceView and is resource intensive, I manually trigger onHover event so that the real surface creation is performed only when the real hover is performed. Also by this, I don't create any object of HoverPreview before it's needed)
thumbnailImageView.setOnHoverListener(new View.OnHoverListener() {
#Override
public boolean onHover(View view, MotionEvent motionEvent) {
HoverPreview hoverPreview;
if (thumbnailImageView.getTag() == null) {
hoverPreview = new HoverPreview(context, getActivity().getWindow(), videoPath, 0);
hoverPreview.setHoverDetectTime(1000);
thumbnailImageView.setTag(hoverPreview);
} else
hoverPreview = (HoverPreview) thumbnailImageView.getTag();
hoverPreview.onHover(null, motionEvent);
if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_EXIT)
thumbnailImageView.setTag(null);
return true;
}
});
HoverPreview:
public class HoverPreview extends LinearLayout implements View.OnHoverListener, SurfaceHolder.Callback {
private static final int MSG_SHOW_POPUP = 1;
private static final int MSG_DISMISS_POPUP = 2;
private static final int HOVER_DETECT_TIME_MS = 300;
private static final int POPUP_TIMEOUT_MS = 60 * 1000;
protected int mHoverDetectTimeMS;
private static final String TAG = "HoverPreview";
private SurfaceHolder mHolder = null;
String videoPath;
int position;
private boolean IsMediaPlayerReady = false;
private MediaPlayer mMediaPlayer;
private SurfaceView mSurfaceView;
Context context;
private HoverPopupHandler mHandler;
Window window;
public HoverPreview(Context context, Window window, String videoPath, int position) {
super(context);
this.mHoverDetectTimeMS = HOVER_DETECT_TIME_MS;
this.videoPath = videoPath;
this.position = position;
this.window = window;
setupLayout(context);
}
private void setupLayout(Context context) {
this.context = context;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
rootView = inflater.inflate(R.layout.media_browser_hover, this);
mSurfaceView = (SurfaceView) findViewById(R.id.media_browser_hoverSurfaceView);
}
View rootView;
#Override
protected void onFinishInflate() {
super.onFinishInflate();
}
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
Log.d(TAG, "HoverSurface created");
final Surface surface = surfaceHolder.getSurface();
if (surface == null) return;
if (!surface.isValid()) return;
mHolder = surfaceHolder;
mMediaPlayer = new MediaPlayer();
try {
mMediaPlayer.setDataSource(videoPath);
} catch (IOException e) {
e.printStackTrace();
return;
}
mMediaPlayer.setDisplay(mHolder);
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
Log.d(TAG, "MediaPlayer preview is prepared");
IsMediaPlayerReady = true;
int videoWidth = mMediaPlayer.getVideoWidth();
int videoHeight = mMediaPlayer.getVideoHeight();
Point size = new Point();
int screenHeight = 0;
int screenWidth = 0;
Display display = getDisplay();
display.getSize(size);
screenWidth = size.x - (350 + 30); // margin + padding
screenHeight = size.y;
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mSurfaceView.getLayoutParams();
lp.width = screenWidth;
lp.height = (int) (((float) videoHeight / (float) videoWidth) * (float) screenWidth);
mSurfaceView.setLayoutParams(lp);
if (mMediaPlayer != null && IsMediaPlayerReady) {
if (position > 0)
mMediaPlayer.seekTo(position);
mMediaPlayer.start();
findViewById(R.id.media_browser_hoverRootFrameLayout).setVisibility(VISIBLE);
}
}
});
Log.d(TAG, "MediaPlayer is created");
try {
mMediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i2, int i3) {
Log.d(TAG, "HoverSurface changed");
}
#Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
Log.d(TAG, "HoverSurface destroyed");
try {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
}
} catch (Exception e) {
}
}
#Override
public boolean onHover(View view, MotionEvent motionEvent) {
try {
if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
Log.d(TAG, "ACTION_HOVER_ENTER");
show(); // checks the timing
} else if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
Log.d(TAG, "ACTION_HOVER_EXIT");
dismiss();
}
} catch (Exception e) {
Log.e(TAG, e.getMessage() + Utils.toString(e.getStackTrace()));
}
return false;
}
/**
* Sets the time that detecting hovering.
*
* #param ms The time, milliseconds
*/
public void setHoverDetectTime(int ms) {
mHoverDetectTimeMS = ms;
}
public void dismiss() {
dismissPopup();
}
private void dismissPopup() {
// remove pending message and dismiss popup
getMyHandler().removeMessages(MSG_SHOW_POPUP);
getMyHandler().removeMessages(MSG_DISMISS_POPUP);
try {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
}
} catch (Exception e) {
}
if (getParent() != null)
((ViewGroup) getParent()).removeView(this);
}
private Handler getMyHandler() {
if (mHandler == null)
mHandler = new HoverPopupHandler();
return mHandler;
}
public void show() {
// send message to show.
if (getMyHandler().hasMessages(MSG_SHOW_POPUP)) {
return;
// getHandler().removeMessages(MSG_SHOW_POPUP);
}
getMyHandler().sendEmptyMessageDelayed(MSG_SHOW_POPUP, mHoverDetectTimeMS);
}
private void showPopup() {
if (getParent() == null) {
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.CENTER_VERTICAL;
params.x = 350;
window.addContentView(this, params);
}
mHolder = mSurfaceView.getHolder();
if (mHolder != null) {
mHolder.addCallback(this);
}
}
;
private class HoverPopupHandler extends Handler {
#Override
public void handleMessage(Message msg) {
// if (DEBUG)
// android.util.Log.e(TAG, "handleMessage : " + ((msg.what == MSG_SHOW_POPUP) ? "SHOW" : "DISMISS"));
switch (msg.what) {
case MSG_SHOW_POPUP:
showPopup();
sendEmptyMessageDelayed(MSG_DISMISS_POPUP, POPUP_TIMEOUT_MS);
break;
case MSG_DISMISS_POPUP:
dismissPopup();
break;
}
}
}
}

Categories

Resources