So I'm developing a cross platform React Native app, the app is using allot of images as buttons as per design requirements that need to be given an initial height and width so that their aspect ratios are correct. From there I've built components that use these image buttons and then placed those components on the main screen. I can get things to look perfect on one screen by using tops and lefts/ rights to get the components positioned according to the design requirements that I've been given.
The problem I'm running into is now scaling this main screen for different screen sizes. I'm basically scaling the x and y via the transform property on the parent most view as such. transform: [{ scaleX: .8 }, { scaleY: .8 }] After writing a scaling function that accounts for a base height and current height this approach works for the actual size of things but my positioning is all screwy.
I know I'm going about this wrong and am starting to think that i need to rethink my approach but am stumped on how to get these components positioned correctly on each screen without having to hard code it.
Is there any way to position a view using tops and lefts/rights, lock that in place, then scale it more like an image?
First of all, try using flex as far as you can. Then when you need extra scaling for inner parts for example, you can use scale functions. I have been using a scale function based on the screen size and the pixel density, and works almost flawless so far.
import { Dimensions } from "react-native";
const { width, height } = Dimensions.get("window");
//Guideline sizes are based on standard ~5" screen mobile device
const guidelineBaseWidth = 350;
const guidelineBaseHeight = 680;
const screenSize = Math.sqrt(width * height) / 100;
const scale = size => (width / guidelineBaseWidth) * size;
const verticalScale = size => (height / guidelineBaseHeight) * size;
const moderateScale = (size, factor = 0.5) =>
size + (scale(size) - size) * factor;
export { scale, verticalScale, moderateScale, screenSize };
Then you can import these and use in your components. There are different types of scales, you can try and see the best one for your components.Hope that helps.
I ended up going through each view and converting everything that was using a hard coded height and width pixel to setting the width and then using the property aspectRatio and giving that the hard coded height and widths. That along with implementing a scaling function that gave me a fraction of the largest view, so say .9, and then scaling the main view using transform. People arent kidding when they say this responsive ui stuff is tough.
2022 update -
I resolved this problem on my next app by using flex everywhere & a function called rem that I use everywhere that needs a fixed pixel count. With this I can set the width on an image and define an aspect ratio based on the images original dimensions and get an image that scales to the screen size, it's been super reliable.
static width = Dimensions.get("window").width;
static height = Dimensions.get("window").height;
static orientation = 'PORTRAIT';
static maxWidth = 428;
static rem = size => {
let divisor = window.lockedToPortrait || Styles.orientation === 'PORTRAIT' ? Styles.width : Styles.height;
return Math.floor(size * (divisor / Styles.maxWidth))
};
The maxWidth is a predefined value from the largest device I could find to simulate which was probably an iPhone max.
Related
I am learning the use of libgdx and I got confused by the viewport and how objects are arranged on the screen. Let's assume my 2D world is 2x2 units wide and high. Now I create a camera which viewport is 1x1. So I should see 25% of my world. Usually displays are not square shaped. So I would expect libgdx to squish and stretch this square to fit the display.
For a side scroller you would set the viewport height the same as the world height and adjust the viewport width according to the aspect ratio. Independent of the aspect ratio of your display you always see the full height of the world but different expansions on the x-axis. Somebody with a wider than high display could look further on the x-axis than somebody with a square shaped display. But proportions will be maintained and there is no distortion. So far I thought I mastered how the viewport logic works.
I am working with the book "Learning LibGDX Game Development" in which you develop the game "canyon bunny". The source code can be found here:
Canyon Bunny - GitHub
In the WorldRenderer Class you find the initilization of the camera:
private void init() {
batch = new SpriteBatch();
camera = new OrthographicCamera(Constants.VIEWPORT_WIDTH, Constants.VIEWPORT_HEIGHT);
camera.position.set(0, 0, 0);
camera.update();
}
The viewport constants are saved in a separate Constants-Class:
public class Constants {
// Visible game world is 5 meters wide
public static final float VIEWPORT_WIDTH = 5.0f;
// Visible game world is 5 meters tall
public static final float VIEWPORT_HEIGHT = 5.0f;
}
As you can see the viewport is 5x5. But the game objects have the right proportion on my phone (16:9) and even on a desktop when you change the windows size the game maintains the correct proportions. I don't understand why. I would expect that the game tries to paint a square shaped cutout of the world onto a rectangle shaped display which would lead to distortion. Why is that not the case? And why don't you need the adaption of width or height of the viewport to the aspect ratio?
The line:
cameraGUI.setToOrtho(true);
Overrides the values you gave when you called:
cameraGUI = new OrthographicCamera(Constants.VIEWPORT_GUI_WIDTH, Constants.VIEWPORT_GUI_HEIGHT);
Here's the LibGDX code that shows why/how the viewport sizes you set were ignored:
/** Sets this camera to an orthographic projection using a viewport fitting the screen resolution, centered at
* (Gdx.graphics.getWidth()/2, Gdx.graphics.getHeight()/2), with the y-axis pointing up or down.
* #param yDown whether y should be pointing down */
public void setToOrtho (boolean yDown) {
setToOrtho(yDown, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
}
/** Sets this camera to an orthographic projection, centered at (viewportWidth/2, viewportHeight/2), with the y-axis pointing up
* or down.
* #param yDown whether y should be pointing down.
* #param viewportWidth
* #param viewportHeight */
public void setToOrtho (boolean yDown, float viewportWidth, float viewportHeight) {
if (yDown) {
up.set(0, -1, 0);
direction.set(0, 0, 1);
} else {
up.set(0, 1, 0);
direction.set(0, 0, -1);
}
position.set(zoom * viewportWidth / 2.0f, zoom * viewportHeight / 2.0f, 0);
this.viewportWidth = viewportWidth;
this.viewportHeight = viewportHeight;
update();
}
So you would need to do this instead:
cameraGUI.setToOrtho(true, Constants.VIEWPORT_GUI_WIDTH, Constants.VIEWPORT_GUI_HEIGHT);
Also don't forget to call update() right after wherever you change the position or viewport dimensions of your camera (Or other properties)
I found the reason. If you take a look on the worldRenderer class there is a method resize(). In this method the viewport is adapted to the aspect ratio. I am just wondering because until now I thought the resize method is only called when resizing the window. Apparently it's also called at start up. Can anybody clarify?
I'm trying to get the height of a local image to scale it on React-Native. I tried the following code but I get Failed to get size for image: ../../images/body.png error.
scaleImg(h) {
Image.getSize('../../images/body.png', (width, height) => {
const scaleFactor = height / h
const imageWidth = width / scaleFactor
this.setState({imgWidth: imageWidth, imgHeight: h})
})
}
I'm late to the party, but there's still no answer. For local assets you gotta use Image.resolveAssetSource(require('./path')); instead.
As per the documentation, you can't use Image.getSize on static image resources, which is what your path seems to link to.
static getSize(uri: string, success: function, failure?: function):
Retrieve the width and height (in pixels) of an image prior to
displaying it. This method can fail if the image cannot be found, or
fails to download.
In order to retrieve the image dimensions, the image may first need to
be loaded or downloaded, after which it will be cached. This means
that in principle you could use this method to preload images, however
it is not optimized for that purpose, and may in future be implemented
in a way that does not fully load/download the image data. A proper,
supported way to preload images will be provided as a separate API.
Does not work for static image resources.
I have some basic styles like:
"#foo": {
backgroundColor: '#ff0000', // red
}
"#foo[if=Alloy.Globals.isSmallHeight]": {
backgroundColor: '#0000ff', // blue
}
in alloy.js I have the following:
function pxToDp(px){
return px / (Titanium.Platform.displayCaps.dpi / 160);
}
var screenWidth;
var screenHeight;
if (Ti.Platform.name == "android") {
screenWidth = pxToDp(Ti.Platform.displayCaps.platformWidth);
screenHeight = pxToDp(Ti.Platform.displayCaps.platformHeight);
} else {
screenWidth = Ti.Platform.displayCaps.platformWidth;
screenHidth = Ti.Platform.displayCaps.platformHeight;
}
Alloy.Globals.isSmallHeight= screenHeight <= 545;
This basically means the background colour of #foo is blue if the screen height is less than or equal to 545dp, and red otherwise.
This usually works fine. However, there are times when the height of the screen can change during run-time. For example:
Screen Orientation Change
Multi window (on Android)
Adjusting the splitter position while in multi window mode (Android)
The issue with this is that the styles are not re-applied to take into account the new screen width and screen height.
For example, let's say there is a screen of size 600dp x 300dp in the portrait position. #foo will correctly have a background colour of red.
However, if the orientation changes, the screen size is now: 300dp x 600dp, but the #foo does not re-check the height a background colour, and thus is still red instead of blue.
A similar issue occurs when going into split screen.
Therefore my question is, how can I reapply styles when the screen dimensions changes?
Have a look at the dynamic styles section in the documentation
http://docs.appcelerator.com/platform/latest/#!/guide/Dynamic_Styling
http://docs.appcelerator.com/platform/latest/#!/guide/Dynamic_Styles
it will give you a basic idea on how to create style at runtime and apply them. You could do this inside the orientation change event
In my Cocos2d js android app i have a UI button as
var button=new ccui.Button();
button.loadTextures(res.play_png,res.play_png);
button.setAnchorPoint(cc.p(0,0));
button.x=size.width/2;
button.y=size.height/2;
this.addChild(button);
The button loads and i am able to set it at any position on the screen.
But i am unable to change its width and height to specified number of pixels.
I want something like
button.width=100;
button.height=100;
I did not find any method to do that...
Any help how to accomplish this would be greatful.
Thanks.
The buttons i use are options like play,how to play,share etc..
So i have to position them in such a way that they dont overlap or get distorted for various screen resolutions...
How can i use the setScale method in this context???
We can make a call to a function that can scale the size of the UIButton by performing an action. In this case, the following code might help:
performScaleAction : function()
{
var duration = 0; // duration in seconds for performing this action
var scaleX = 0.5; // scale factor for X-axis
var scaleY = 0.5; // scale factor for Y-axis
var scaleAction = new cc.ScaleTo(duration , scaleX , scaleY );
this.runAction(scaleAction);
}
Now, call the above function in the following way:
this.performScaleAction();
this.addChild(button);
So, if the actual height of the image used for creating the UIButton is h, in this case, the new size as displayed on the screen would be h times scaleX . Same for the width of the UIButton.
Hope this helps!
I have an image area in my application with width:530px and height:510px. I want to place images in that area but the images comes in different different sizes. how to crop or scale the image without losing aspect ratio to fill that area. Is there any native methods available in winjs?
You have a few options for that.
One is to use the ViewBox control that WinJS provides you. The ViewBox can only have a single child (so you would have your tag as its only child perhaps or a div that contains your img) and it will scale that child (using CSS transforms) up to fit into its container (without changing the aspect ratio). It's pretty slick.
Another option is to set the image as the CSS background-image property of a container (such as a div) and set the background-size property to contain. This will stretch the image up to the size of the container.
A final option that you have to resort to if your case is a bit special is not such a bad option after all. In the updateLayout method of your page, you can refer to the element and explicitly set its CSS properties to fit. At that point you'll know all about the layout and can easily do the math to figure out what size image should be. Here's some code from one of my projects where I do this. Notice that I'm comparing the aspect ratio of the screen and the image to determine whether I should size to the width or the height. Unlike your situation (I'm guessing), my code makes sure the image fills the screen and excess is clipped. I'm guessing you want your img to be contained.
function setImagePosition() {
var img = q(".viewCamera #viewport img");
if (outerWidth/outerHeight > img.naturalWidth/img.naturalHeight) {
img.style.width = format("{0}px", outerWidth);
img.style.height = "";
img.style.top = format("{0}px", (outerHeight - img.clientHeight) / 2);
img.style.left = "0px";
} else {
img.style.width = "";
img.style.height = format("{0}px", outerHeight);
img.style.top = "0px";
img.style.left = format("{0}px", (outerWidth - img.clientWidth) / 2);
}
}
Hope that helps!
Are you referring to scaling HTML images? If so, you can set either one, width or height. Whichever you set, the other will scale and keep image aspect ratio.