I am working on an Android application with an embedded QR code scanner using the Google Vision API. The scanner functions, but the SurfaceView that acts as camera preview is stretched vertically. The degree of distortion is different for different emulated devices.
As I understand it, you would use mCameraSource.setRequestedPreviewSize(w,h) to set the correct size. w and h I have set as Resources.getSystem().getDisplayMetrics().widthPixels and Resources.getSystem().getDisplayMetrics().heightPixels, respectively. However, I have noticed that regardless of what numbers I parse as width and height, there are no changes in the way it displays.
However, resizing the SurfaceView on which it is displayed does have an effect on the distortion. For one particular emulated Android device I can statically set the right width and height. For different devices, however, with a slightly different pixel w:h ratio, the distortion can become quite large.
I have read various solutions on StackOverflow, but most use the CameraPreview instead of the CameraSource.Builder.
My code thus far is (part of ScannerActivity.java):
private SurfaceView svCamera;
private BarcodeDetector barcodeDetector;
private CameraSource cameraSource;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scanner);
initViews();
initListeners();
barcodeDetector = new BarcodeDetector.Builder(this)
.setBarcodeFormats(Barcode.QR_CODE)
.build();
cameraSource = new CameraSource.Builder(this, barcodeDetector)
.setRequestedPreviewSize(Resources.getSystem().getDisplayMetrics().widthPixels, Resources.getSystem().getDisplayMetrics().heightPixels)
.setAutoFocusEnabled(true)
.build();
svCamera.getHolder().addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
requestPermission();
try {
if (ActivityCompat.checkSelfPermission(ScannerActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
return;
}
cameraSource.start(svCamera.getHolder());
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
#Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
cameraSource.stop();
}
});
barcodeDetector.setProcessor(new Detector.Processor<Barcode>() {
#Override
public void release() {
scanned = true;
}
#Override
public void receiveDetections(Detector.Detections<Barcode> detections) {
...
}
});
} }
Can someone help me with setting preview size?
The way I fixed it with help of Alex Cohn's answer:
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
...
cameraSource.setRequestedPreviewSize(1280, 720); // Hardcoded, for now.
And I set the size of the SurfaceView with:
svCamera.setLayoutParams(new RelativeLayout.LayoutParams(width, width/9*16));
If I remember I will update this to a non-hardcoded version.
Quite contrary. You cannot choose arbitrary resolution for setRequestedPreviewSize(). This CameraSource API wraps the ordinary Camera API, which exposes only some, "supported" pairs of width and height.
If you want to display the live view undistorted, you must setup your SurfaceView to match the aspect ratio of chosen camera preview. This means, it's OK to use surface of 320x240 pixel for camera 640x480. It's even OK to use surface of 1280x720 for camera 1920x1080.
But if you have surface of 800x480 for camera of 1280x720 (and if your devices supports such camera resolution), the picture will be slightly stretched.
Related
I need to get the size of the layout which height set as WRAP_CONTENT.
I tried to get it by calling
LinearLayout.getLayoutParams()
Which returns the height = -1 or -2 ( I know this is due to WRAP/MATCH Content ).
I've also tried
LinearLayout.getMeasuredHeight()
it returns 0.
How could I get the real size of the layout ? Bellow is my sample code.
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
private void loadComponents() {
showMenuButton = findViewById(R.id.showMenuButton);
showMenuButton.setOnClickListener( v -> inflateMenu());
listNote = findViewById(R.id.listNote);
Point point = new Point();
getWindowManager().getDefaultDisplay().getRealSize(point);
LinearLayout thisLayout = findViewById(R.id.main_layout);
this.Y = thisLayout.getMeasuredHeight();
this.offsetY = point.y - Y;
}
Perhaps you could try this
DisplayMetrics displayMetrics = thisLayout.getResources().getDisplayMetrics();
int height = displayMetrics.heightPixels;
First of all notice that if your Linerlayout is not yet drawn both thisLayout.getHeight() and thisLayout.getWidth() will return 0.
So :
thisLayout.post(new Runnable(){
public void run(){
int height = thisLayout.getHeight();
int weight = thisLayout.getWidth();
}
});
You can use bellow code to get size of the layout whenever it changes.
// In you Activity / Fragment
private ViewTreeObserver.OnGlobalLayoutListener thisLayoutTreeObserver =
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// here you can get updated height of the View
thisLayout.getHeight();
}
};
#Override
public void onStart() {
super.onStart();
thisLayout.getViewTreeObserver()
.addOnGlobalLayoutListener(thisLayoutTreeObserver);
}
#Override
public void onStop() {
super.onStop();
thisLayout.getViewTreeObserver()
.removeOnGlobalLayoutListener(thisLayoutTreeObserver);
}
ViewTreeObserver can be used to get notifications when global events, like layout, happen.
Here is an additional important consideration from the official documentation:
The returned ViewTreeObserver observer is not guaranteed to remain
valid for the lifetime of this View. If the caller of this method
keeps a long-lived reference to ViewTreeObserver, it should always
check for the return value of ViewTreeObserver.isAlive().
I am creating an app that pulls images from urls and puts them into a recyclerview. The user can then access those images and view it fullscreen. This is achieved with Picasso. I would now like the ability to fingerpaint over the image loaded with Picasso with an onTouchEvent or something but not sure how to do it.
This class sets the image to a map_edit_gallery.xml loaded with Picasso:
public class EditMapImage extends AppCompatActivity {
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.map_edit_gallery);
checkIntent();
//Find savebutton
ImageButton saveMapButton = findViewById(R.id.saveEditImagebutton);
saveMapButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(getApplicationContext(),"Saved",Toast.LENGTH_SHORT).show();
}
});
}
//This will check to see if the intent extras exist and if they do get the extra
private void checkIntent(){
if(getIntent().hasExtra("image_url") && getIntent().hasExtra("name_url")){
String imageUrl = getIntent().getStringExtra("image_url");
String nameUrl = getIntent().getStringExtra("name_url");
setMapImage(imageUrl, nameUrl);
}
}
private void setMapImage(String imageUrl, String nameUrl){
//Set the Text view
TextView name = findViewById(R.id.mapNameEditor);
name.setText(nameUrl);
//Set the Image
ImageView imageView = findViewById(R.id.mapEditScreen);
Picasso.get().load(imageUrl).into(imageView);
Picasso picasso = Picasso.get();
DrawToImage myTransformation = new DrawToImage();
picasso.load(imageUrl).transform(myTransformation).into(imageView);
}
}
EDIT:
This class has allowed me to draw over the loaded image using canvas but cannot figure out how to use touch to draw:
public class DrawToImage implements Transformation {
#Override
public String key() {
// TODO Auto-generated method stub
return "drawline";
}
public Bitmap transform(Bitmap bitmap) {
// TODO Auto-generated method stub
synchronized (DrawToImage.class) {
if(bitmap == null) {
return null;
}
Bitmap resultBitmap = bitmap.copy(bitmap.getConfig(), true);
Canvas canvas = new Canvas(resultBitmap);
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(10);
canvas.drawLine(0, resultBitmap.getHeight()/2, resultBitmap.getWidth(), resultBitmap.getHeight()/2, paint);
bitmap.recycle();
return resultBitmap;
}
}
}
Try using the image selected by the user to set it in a canvas object and draw on the canvas object itself, as opposed to the image. There are plenty of tutorials out there to help you with how to draw on a canvas.
This process isn't connected with the Picasso Image Library in any way so I would recommend first getting the image through Picasso, then sending the image into your custom canvas implementation, then returning a bitmap/drawable which you could set into Picasso after editing.
There's also plenty of tutorials on how to export an image from the canvas to get your edited image when you need it.
I hope this helped, Panos.
I have a kids drawing app, but the gestures in Q keep quitting the app. I tried removing the system gesture but it does not seem to work.
In this case, I am trying to exclude the whole screen from system gesture:
List<Rect> exclusionRects = new ArrayList();
public void onLayout(boolean changedCanvas, int left, int top, int right, int bottom) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
exclusionRects.clear();
exclusionRects.add(new Rect(left, top, right, bottom));
setSystemGestureExclusionRects(exclusionRects);
}
}
As stated by Google:
First, to ensure reliable and consistent operation, there’s a 200dp vertical app exclusion limit for the Back gesture.
https://android-developers.googleblog.com/2019/08/final-beta-update-official-android-q.html
This means that the operating system will not allow you to override the back gesture fully.
This makes sense as it is a fairly fundamental part of the operating system and they probably don't want to allow apps that remove the gesture entirely, as it is bad for consistency across the platform
Try this.
Define this code in your Utils class.
static List<Rect> exclusionRects = new ArrayList<>();
public static void updateGestureExclusion(AppCompatActivity activity) {
if (Build.VERSION.SDK_INT < 29) return;
exclusionRects.clear();
Rect rect = new Rect(0, 0, SystemUtil.dpToPx(activity, 16), getScreenHeight(activity));
exclusionRects.add(rect);
activity.findViewById(android.R.id.content).setSystemGestureExclusionRects(exclusionRects);
}
public static int getScreenHeight(AppCompatActivity activity) {
DisplayMetrics displayMetrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int height = displayMetrics.heightPixels;
return height;
}
public static int dpToPx(Context context, int i) {
return (int) (((float) i) * context.getResources().getDisplayMetrics().density);
}
Check if your layout is set in that activity where you want to exclude the edge getures and then apply this code.
// 'content' is the root view of your layout xml.
ViewTreeObserver treeObserver = content.getViewTreeObserver();
treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
content.getViewTreeObserver().removeOnGlobalLayoutListener(this);
SystemUtil.updateGestureExclusion(MainHomeActivity.this);
}
});
We are adding the view width to 16dp to trigger the code when user swipe right from left edge & height to screen height to do it fully left side.
I have a RecyclerView. You can scroll vertically and horizontally too.
Like this:
Check the item's width.
holder.itemView.post(new Runnable()
{
#Override
public void run()
{
int cellWidth = holder.itemView.getWidth();
}
});
The width is 5234.
When I want to scroll to the center.(I didn't calculate with device's screen's size, but this is not important now.) I call this:
recyclerView.smoothScrollBy((5234/2), 0)
It is work good on these emulators:
480x800 mdpi (API 24)
480x800 hdpi (API 24)
Isn't work good:
1080x1920 420dpi (API 21)
In 1080x1920. I the recycler width is 5234, when I call the smoothScrollBy with 5234/2 . Than It just go to ~1000. So can't jump to center.
Why? :/ The smoothScrollBy is waiting PX.
UPDATE:
If I use Handler with postDelay, then it works well. So maybe the scrolling is called soo fast. I trying to find better solution.
new Handler().postDelayed(new Runnable(){
#Override
public void run() {
recyclerView.smoothScrollBy(5234/2, 0);
}
} ,1000);
I found the solution
ViewTreeObserver viewTreeObserver = this.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
recyclerView.smoothScrollBy(5234/2, 0);
}
});
I am adding an adMob banner to my app successfully. When banner appears I need to get its height in order to resize all layout elements. I am using the event onReceivedAd, that is properly fired. However, alturaBanner is = 0. Then, how to get its height? thank you.
/** Called when an ad is received. */
#Override
public void onReceiveAd(Ad ad)
{
adView.setVisibility(View.VISIBLE);
int alturaBanner = adView.getHeight();
RelativeLayout.LayoutParams params1 = (android.widget.RelativeLayout.LayoutParams) browse2
.getLayoutParams();
params1.setMargins(0, alturaBanner, 0, 0);
Log.d(LOG_TAG, "onReceiveAd");
Toast.makeText(this, "onReceiveAd", Toast.LENGTH_SHORT).show();
}
You can get the height of any type of banner before it is even added to the layout.
int heightPixels = AdSize.SMART_BANNER.getHeightInPixels(this);
or
int heightPixels = AdSize.FULL_BANNER.getHeightInPixels(myContext);
or for DIP's
int heightDP = AdSize.BANNER.getHeight();
So for your need, you could do this:
/** Called when an ad is received. */
#Override
public void onReceiveAd(Ad ad)
{
adView.setVisibility(View.VISIBLE);
int alturaBanner = AdSize.BANNER.getHeight(); // This gets the adsize, even if the view is not inflated.
RelativeLayout.LayoutParams params1 = (android.widget.RelativeLayout.LayoutParams) browse2
.getLayoutParams();
params1.setMargins(0, alturaBanner, 0, 0);
Log.d(LOG_TAG, "onReceiveAd");
Toast.makeText(this, "onReceiveAd", Toast.LENGTH_SHORT).show();
}
Just change AdSize.BANNER to AdSize.SMART_BANNER or whatever banner type your using.
Add Sizes Get Height
getting the height of the view before it was prepared will always return you 0 .
use the next code in order to get its correct size , no matter which device/screen you have:
private static void runJustBeforeBeingDrawn(final View view, final Runnable runnable)
{
final ViewTreeObserver vto = view.getViewTreeObserver();
final OnPreDrawListener preDrawListener = new OnPreDrawListener()
{
#Override
public boolean onPreDraw()
{
runnable.run();
final ViewTreeObserver vto = view.getViewTreeObserver();
vto.removeOnPreDrawListener(this);
return true;
}
};
vto.addOnPreDrawListener(preDrawListener);
}
inside the given runnable , you can query the real size of the view.
alternatively , you can use addOnGlobalLayoutListener instead of addOnPreDrawListener if you wish.
another approach is to use onWindowFocusChanged (and check that hasFocus==true) , but that's not always the best way ( only use for simple views-creation, not for dynamic creations)
EDIT: Alternative to runJustBeforeBeingDrawn: https://stackoverflow.com/a/28136027/878126
I use the following method to get AdView's height:
adView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int height = adView.getHeight();
if (height > 0) {
// now the height is gotten, you can do things you want
}
}
});
onGlobalLayout() is triggered when the global layout state or the visibility of views within the view tree changes.
Actually, you don't need to wait for appearance of adview to get adMob banner height if you are using smart banner type for showing banner ads.
Use Smart banner as it automatically decides the height of ad based on device size. Use full width of screen to show the ad.
From android developers site:
Smart Banners are ad units that will render screen-wide banner ads on any screen size across different devices in either orientation. Smart Banners help deal with increasing screen fragmentation across different devices by "smartly" detecting the width of the phone in its current orientation, and making the ad view that size.
Three ad heights (in dp, density-independent pixel) are available:
32 - used when the screen height of a device is less than 400
50 - used when the screen height of a device is between 400 and 720
90 - used when the screen height of a device is greater than 720
Now, get the height of AdView and adjust the margin of the layout where you wish to place the banner ad. Once the ad is loaded (by overriding on onAdLoaded API), you know the height using below method:
public static int getAdViewHeightInDP(Activity activity) {
int adHeight = 0;
int screenHeightInDP = getScreenHeightInDP(activity);
if (screenHeightInDP < 400)
adHeight = 32;
else if (screenHeightInDP <= 720)
adHeight = 50;
else
adHeight = 90;
return adHeight;
}
public static int getScreenHeightInDP(Activity activity) {
DisplayMetrics displayMetrics = ((Context) activity).getResources().getDisplayMetrics();
float screenHeightInDP = displayMetrics.heightPixels / displayMetrics.density;
return Math.round(screenHeightInDP);
}
I had the same need to be able to display my own ads when AdMob fails to receive an ad or receives an empty ad (height=0).
I use the following code based on the fact that an AdView extends RelativeLayout:
mAdMobView = new AdView(pActivity, AdSize.SMART_BANNER, Constants.ADMOB_AD_UNIT_ID);
mAdMobView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
#Override
public void onLayoutChange(final View pV, final int pLeft, final int pTop, final int pRight, final int pBottom, final int pOldLeft, final int pOldTop, final int pOldRight, final int pOldBottom) {
final float lAdHeight = mAdMobView.getHeight();
if (lAdHeight == 0) {
Debug.i(LOG_TAG, "mAdMobView.onLayoutChange(...) mAdMobView.height='" + lAdHeight + "'. AdMob returned an empty ad !");
// Show custom ads
} else {
Debug.d(LOG_TAG, "mAdMobView.onLayoutChange(...) mAdMobView.height='" + lAdHeight + "'");
// Make AdView visible
}
}
});
mAdMobView.setAdListener(new AdListener() {
#Override public void onReceiveAd(final Ad pAd) {
Debug.d(LOG_TAG, "onReceiveAd(...) AdMob ad received (mAdMobView.visibility='" + mAdMobView.getVisibility() + "').");
}
#Override public void onPresentScreen(final Ad pAd) {
Debug.d(LOG_TAG, "onPresentScreen(...)");
}
#Override public void onLeaveApplication(final Ad pAd) {
Debug.d(LOG_TAG, "onLeaveApplication(...)");
}
#Override public void onDismissScreen(final Ad pAd) {
Debug.d(LOG_TAG, "onDismissScreen(...)");
}
#Override
public void onFailedToReceiveAd(final Ad pAd, final ErrorCode pErrorCode) {
Debug.i(LOG_TAG, "onFailedToReceiveAd(...) AdMob ad error (" + pErrorCode + ").");
// Show custom ads
}
});
The code in 'onLayoutChange' is executed every time Admob receives a new ad.
EDIT: My answer is not proper since this method was added with the API 11... I changed it for the use of onPreDraw() as explained in the previous answer.