Workaround for custom UIViewController animations in landscape? - uiviewcontroller

I have a custom animated UIViewController transition, and it seems that there is a bug in iOS that screws up the layout in landscape orientation. In the main animation method, i'm given a mix of landscape and portrait views. (In portrait the views are all portrait, so no problem.)
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *containerView = [transitionContext containerView];
// fromViewController.view => landscape, transform
// toViewController.view => portrait, transform
// containerView => portrait, no transform
[containerView addSubview:toViewController.view];
// ...animation... //
}
I know that the frame property is not reliable when a view has a transform - so I'm guessing this is the root of the problem. In landscape mode, the to/from viewControllers views have a 90 deg clockwise transform [0 -1 1 0]. I've tried using bounds/center to size and position the view, as well removing the transform and then reapplying it, but UIKit fights me and insists on displaying the view as portrait. Annoying!
In the screenshot, the dark grey is the UIWindow background, and the red is the added modal view controller which should cover the whole screen.
Anyone found a workaround?

Ok, the fix is surprisingly simple:
Set the toViewController frame to the container before adding the view to the container.
toViewController.view.frame = containerView.frame;
[containerView addSubview:toViewController.view];
Update: There is still a limitation in that you don't know the orientation of the frame. It is portrait initially, but stretched into landscape when it is displayed on screen. If you wanted to slide in the view from the right, in landscape it might slide in from the "top" (or the bottom if viewing the other landscape!)

I came across this issue and I just don't feel that the above solutions do this any justice. I propose a solution that doesn't require hacky code and hard coded frames.
UIView has an awesome function to convert a CGRect into the coordinate space of another (namely; +[UIView convertRect:fromView:]). So I want to detail a far simpler way one can achieve this effect in any orientation without any hardcoded values. In this example lets say we want a simple animation that slides a view in from the right of the screen.
So in our animator's animateTransition(:) we could simply perform the following:
Swift
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toView = toViewController.view
let fromView = fromViewController.view
let containerView = transitionContext.containerView()
if(isPresenting) {
//now we want to slide in from the right
let startingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0)
toView.frame = containerView.convertRect(startingRect, fromView:fromView);
containerView.addSubview(toView)
let destinationRect = containerView.convertRect(fromView.bounds, fromView: fromView)
UIView.animateWithDuration(transitionDuration(transitionContext),
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.7,
options: .BeginFromCurrentState,
animations: { () -> Void in
toView.frame = destinationRect
}, completion: { (complete) -> Void in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
})
} else {
//we want to slide out to the right
let endingRect = containerView.convertRect(CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0), fromView: fromView)
UIView.animateWithDuration(transitionDuration(transitionContext),
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.7,
options: .BeginFromCurrentState,
animations: { () -> Void in
fromView.frame = endingRect
}, completion: { (complete) -> Void in
if !transitionContext.transitionWasCancelled() {
fromView.removeFromSuperview()
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
})
}
}
Objective-C
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *toView = toViewController.view;
UIView *fromView = fromViewController.view;
UIView *containerView = [transitionContext containerView];
if(self.isPresenting) {
//now we want to slide in from the right
CGRect startingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0);
toView.frame = [containerView convertRect:startingRect fromView:fromView];
[containerView addSubview:toView];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
toView.frame = [containerView convertRect:fromView.bounds
fromView:fromView];
}
completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
} else {
//we want to slide out to the right
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
CGRect endingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0);
fromView.frame = [containerView convertRect:endingRect fromView:fromView];
}
completion:^(BOOL finished) {
[fromView removeFromSuperview];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
I hope this helps someone else who came here in the same boat (if it does, an up-vote won't hurt :) )

The existing answer goes part way but not all the way (we want proper frames and rotation handling on both devices, all orientations, for both animated and interactive transitions).
This blog post helps:
http://www.brightec.co.uk/blog/ios-7-custom-view-controller-transitions-and-rotation-making-it-all-work
And it quotes an Apple Support person stating the true nature of the problem:
"For custom presentation transitions we setup an intermediate view between the window and the windows rootViewController's view. This view is the containerView that you perform your animation within. Due to an implementation detail of auto-rotation on iOS, when the interface rotates we apply an affine transform to the windows rootViewController's view and modify its bounds accordingly. Because the containerView inherits its dimensions from the window instead of the root view controller's view, it is always in the portrait orientation."
"If your presentation animation depends upon the orientation of the presenting view controller, you will need to detect the presenting view controller's orientation and modify your animation appropriately. The system will apply the correct transform to the incoming view controller but you're animator need to configure the frame of the incoming view controller."
But it doesn't address interactive transitions.
I worked out a complete solution to the problem here:
https://github.com/alfiehanssen/Cards
Essentially, you need to calculate the frames of your viewControllers based on the orientation of one of the viewControllers (toViewController or fromViewController) rather than the bounds of the transitionContext's containerView.

I was stumped with this issue as well. I didn't like the switch/case solution too much. I ended up creating this function instead:
#implementation UIView (Extras)
- (CGRect)orientationCorrectedRect:(CGRect)rect {
CGAffineTransform ct = self.transform;
if (!CGAffineTransformIsIdentity(ct)) {
CGRect superFrame = self.superview.frame;
CGPoint transOrigin = rect.origin;
transOrigin = CGPointApplyAffineTransform(transOrigin, ct);
rect.origin = CGPointZero;
rect = CGRectApplyAffineTransform(rect, ct);
if (rect.origin.x < 0.0) {
transOrigin.x = superFrame.size.width + rect.origin.x + transOrigin.x;
}
if (rect.origin.y < 0.0) {
transOrigin.y = superFrame.size.height + rect.origin.y + transOrigin.y;
}
rect.origin = transOrigin;
}
return rect;
}
- (CGRect)orientationCorrectedRectInvert:(CGRect)rect {
CGAffineTransform ct = self.transform;
if (!CGAffineTransformIsIdentity(ct)) {
ct = CGAffineTransformInvert(ct);
CGRect superFrame = self.superview.frame;
superFrame = CGRectApplyAffineTransform(superFrame, ct);
CGPoint transOrigin = rect.origin;
transOrigin = CGPointApplyAffineTransform(transOrigin, ct);
rect.origin = CGPointZero;
rect = CGRectApplyAffineTransform(rect, ct);
if (rect.origin.x < 0.0) {
transOrigin.x = superFrame.size.width + rect.origin.x + transOrigin.x;
}
if (rect.origin.y < 0.0) {
transOrigin.y = superFrame.size.height + rect.origin.y + transOrigin.y;
}
rect.origin = transOrigin;
}
return rect;
}
Basically, you can create your frame rects using the portrait or landscape coordinates but run it through the function with the view's transform before applying it to the view. With this method, you can use bounds to get correct view size.
CGRect endFrame = toViewController.view.frame;
CGRect startFrame = endFrame;
startFrame.origin.y = fromViewController.view.bounds.size.height;
endFrame = [fromViewController.view orientationCorrectedRect:endFrame];
startFrame = [fromViewController.view orientationCorrectedRect:startFrame];
toViewController.view.frame = startFrame;

One solution is to have a very short (or zero-second) transition, then once the transition is finished and your view controller is presented, it will have the correct transforms applied to it. You then perform your animations from within the presented view controller itself.

Related

Gesture Zoom and Pan in a VCL Windows application

I'm trying to zoom and pan an image (TImage) with an object (TShape) on it in a VCL Windows application running on an all-in-one pc with touch display.
I put the image in a panel (TPanel) so when I zoom it, it remains always inside the panel.
Then I put a shape (TShape) on the image.
What I would like to get is to zoom and pan the image and the shape should zoom and move with the image.
I started from the Embarcadero sample "Mobile Snippets - InteractiveGestures - ImageZoom" and with the following code I managed to do image zoom and pan with two fingers:
void __fastcall TForm1::Image1Gesture(TObject *Sender, const TGestureEventInfo &EventInfo, bool &Handled) {
TPointF LImageCenter;
double expance;
TPointF movement;
try {
if (EventInfo.GestureID == static_cast<short>(Vcl::Controls::igiZoom)) {
if (!EventInfo.Flags.Contains(TInteractiveGestureFlag::gfBegin) &&
!EventInfo.Flags.Contains(TInteractiveGestureFlag::gfEnd)) {
// zoom the image
TImage * LImage = dynamic_cast<TImage*>(Image1);
LImageCenter.x = LImage->Left + (LImage->Width / 2);
LImageCenter.y = LImage->Top + (LImage->Height / 2);
expance = EventInfo.Distance - FLastDistance;
if (((LImage->Width + (expance * AR))> MIN_DIM) &&
((LImage->Height + (expance))> MIN_DIM)) {
LImage->Width = (int)(LImage->Width + (expance * AR));
LImage->Height = (int)(LImage->Width / AR );
}
LImage->Left = LImageCenter.X - LImage->Width / 2;
LImage->Top = LImageCenter.Y - LImage->Height / 2;
}
FLastDistance = EventInfo.Distance;
}
else if (EventInfo.GestureID == static_cast<short>(Vcl::Controls::igiPan)) {
if (!EventInfo.Flags.Contains(TInteractiveGestureFlag::gfBegin) &&
!EventInfo.Flags.Contains(TInteractiveGestureFlag::gfEnd)) {
// move the image
TImage * LImage = dynamic_cast<TImage*>(Image1);
movement.x = EventInfo.Location.X - FLastLocation.x;
movement.y = EventInfo.Location.Y - FLastLocation.y;
LImage->Left += movement.x;
LImage->Top += movement.y;
}
FLastLocation.x = EventInfo.Location.X;
FLastLocation.y = EventInfo.Location.Y;
}
} catch (Exception &e) {
ShowMessage(e.Message);
} }
AR is the image aspect ratio and MIN_DIM is a define to set the minimum image dimensions for the zoom.
The pan movement is not so great...
I don't know how to make the shape to behave in accordance with the image zoom and move.
The shape parent is the panel under the image, not the image, so the shape is completely independent from the image changes.
I can't set the image as the shape parent.
Whatever suggestion to get the desired result will be appreciated.
The pan movement is not smooth because EventInfo.Location.X and EventInfo.Location.Y do not vary in a smooth way.
If I start the pan movement and I stop, even if fingers are still EventInfo.Location continues to change.
Is there a way to avoid this behavior?

Vertical paper-slider element

I like the Polymer paper elements and I want to use a paper-slider element.
However, i would like it to be vertical. I've tried to apply css to rotate it;
transform: rotate(90deg);
This rotates the slider, but not the "input"; one must still click and drag the mouse horizontally in order to get the "knob" to move up and down.
This is very annoying and any help is rely appreciated!
tk-vslider is a tweaked paper-slider with rotate functionality. Use this instead of paper-slider to solve this issue. Install using bower "tkvslider": "0.5.5"
<tk-vslider rotate="true"></tk-vslider>
Tweaks.
if rotate == true then
div#sliderContainer is css rotated by 90deg
Event on-trackx of the div#sliderKnob is replaced by on-track
In the method tracktake e.dy instead of e.dx.
This might be a pretty hacky way to do it, but we're coming up a year since you requested this as feature and it doesn't look like it's much of a priority yet. I figure this will (for the most part) allow for continued updates of paper-slider from the Polymer team without depending on third parties that might not continue support (from what I can tell, the custom element tk-vslider mentioned here hasn't been updated to support Polymer 1.0).
First, the css. I found that if I rotated 90 degrees, the smaller values were at the top of the slider, which I found to be counter-intuitive. So instead I rotate by -90. Some weird stuff happens with the margins but this is what finally did it for me:
paper-slider {
width: 20vh;
margin: 10vh -10vh;
--webkit-transform: rotate(-90deg);
transform: rotate(-90deg);
}
I had my paper-slider inside my own custom element, so I put the following code in the ready callback, but I imagine you could put it anywhere you needed, as long as you can select the slider element. We're basically just going to override the method that paper-slider uses to respond to drag events. Here's how:
var slider = this.$.slider;
slider._trackX = function(e) {
if (!slider.dragging) {
slider._trackStart(e);
}
var dx = Math.min(slider._maxx, Math.max(slider._minx, (-1 * e.detail.dy)));
slider._x = slider._startx + dx;
var immediateValue = slider._calcStep(slider._calcKnobPosition(slider._x / slider._w));
slider._setImmediateValue(immediateValue);
// update knob's position
var translateY = ((slider._calcRatio(immediateValue) * slider._w) - slider._startx);
slider.translate3d(translateY + 'px', 0, 0, slider.$.sliderKnob);
};
Almost the entirety of this method is copied over from paper-slider source code, the only real change is that instead of grabbing the x coordinate in e.detail.dx we grab the y in e.detail.dy. The negative multiplier is only necessary if you want smaller values at the bottom of your slider, and you rotated your paper-slider by -90 degrees. Note that if the Polymer team ever changes the name of the method _trackX, it'll break this solution. I know this is a bit late but hopefully it'll help anyone else finding themselves in a similar situation (as I did).
UPDATE: Probably should have tested this solution a bit more, turns out there's another function that needs to be overwritten to handle click events (the other one only handles drag). I got it to work by adding this below my other method:
slider._bardown = function(event) {
slider._w = slider.$.sliderBar.offsetWidth;
var rect = slider.$.sliderBar.getBoundingClientRect();
var ratio = ((rect.bottom - event.detail.y) / slider._w);
var prevRatio = slider.ratio;
slider._setTransiting(true);
slider._positionKnob(ratio);
slider.debounce('expandKnob', slider._expandKnob, 60);
// if the ratio doesn't change, sliderKnob's animation won't start
// and `_knobTransitionEnd` won't be called
// Therefore, we need to manually update the `transiting` state
if (prevRatio === slider.ratio) {
slider._setTransiting(false);
}
slider.async(function() {
slider.fire('change');
});
// cancel selection
event.preventDefault();
}
The main change to this method is the line that calculates the ratio. Before it was var ratio = (event.detail.x - rect.left) / this._w;
<s-slider> has vertical property for vertical orientation https://github.com/StartPolymer/s-slider
zberry's answer didnt work for me anymore. However, I took his answer and updated it. The Polymer3 solution to make the paper-slider vertically responsive would look like this (given the same CSS zberry used):
let slider = this.shadowRoot.querySelector('paper-slider');
slider._trackX = function(event) {
if (!slider.dragging) {
slider._trackStart(event);
}
var dx =
Math.min(slider._maxx, Math.max(slider._minx, -1 * event.detail.dy));
slider._x = slider._startx + dx;
var immediateValue =
slider._calcStep(slider._calcKnobPosition(slider._x / slider._w * 100));
slider._setImmediateValue(immediateValue);
// update knob's position
var translateX =
((slider._calcRatio(slider.immediateValue) * slider._w) -
slider._knobstartx);
slider.translate3d(translateX + 'px', 0, 0, slider.$.sliderKnob);
};
slider._barclick = function(event) {
slider._w = slider.$.sliderBar.offsetWidth;
var rect = slider.$.sliderBar.getBoundingClientRect();
var ratio = (rect.bottom - event.detail.y) / slider._w * 100;
if (slider._isRTL) {
ratio = 100 - ratio;
}
var prevRatio = slider.ratio;
slider._setTransiting(true);
slider._positionKnob(ratio);
// if the ratio doesn't change, sliderKnob's animation won't start
// and `_knobTransitionEnd` won't be called
// Therefore, we need to manually update the `transiting` state
if (prevRatio === slider.ratio) {
slider._setTransiting(false);
}
slider.async(function() {
slider.fire('change', {composed: true});
});
// cancel selection
event.preventDefault();
// set the focus manually because we will called prevent default
slider.focus();
};
If you want to slide from top to down you have to fiddle with the directions

Multiple Resolution Support in Cocos2d-x v2.2.5 in Portrait mode

I am working on a game in cocos2d-x which is in portrait mode.
Now, for a long time now, I've been working on how to properly achieve multi resolution in cocos2d-x but failed. I followed this great tutorial on forum, but it wasn't enough, I also searched a lot but couldn't find a solution.
I also tried with different-different policies which are available with cocos-x.
I went through all the following links & tutorials
Using these links I could achieve for all ios resolutions but not for all android screens.
http://becomingindiedev.blogspot.in/2014/05/multi-resolution-support-in-ios-with.html
http://discuss.cocos2d-x.org/t/porting-ios-game-to-android-multi-resolution-suppor/5260/5
https://www.youtube.com/watch?v=CH9Ct4R0nBM
https://github.com/SonarSystems/Cocos2d-x-v3-C---Tutorial-4---Multi-Resolution-Support
I even tried with newer version of cocos2d-x, but they also not providing anything which can support both ios and android screens.
I use the following bit of code in my AppDelegate:
void AppDelegate::multiresolutionSupport()
{
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
cocos2d::Size designSize = cocos2d::Size(320, 480);
cocos2d::Size resourceSize = cocos2d::Size(320, 480);
cocos2d::Size screenSize = glview->getFrameSize();
float margin1 = (320 + 640) / 2;
float margin2 = (768 + 1536) / 2;
if (screenSize.width < margin1) {
FileUtils::getInstance()->addSearchResolutionsOrder("SD");
} else if (480 <= screenSize.width && screenSize.width < margin2) {
FileUtils::getInstance()->addSearchResolutionsOrder("HD");
designSize = cocos2d::Size(screenSize.width / 2, screenSize.height / 2);
} else {
FileUtils::getInstance()->addSearchResolutionsOrder("HDR");
designSize = cocos2d::Size(screenSize.width / 4, screenSize.height / 4);
}
resourceSize = screenSize;
director->setContentScaleFactor(resourceSize.width / designSize.width);
glview->setDesignResolutionSize(designSize.width, designSize.height, ResolutionPolicy::NO_BORDER);
}
Call it before loading any assets and you should have it working properly. I call it right after my glview is created in AppDelegate::applicationDidFinishLaunching().
How it works:
it uses a bunch of magic numbers to determine (roughly) what texture
resolution should we use: SD(#1), HD(#2) or HDR(#4) and then adjusts
design size accordingly, by dividing it on the content scale factor
it adds appropriate search paths to FileUtils it sets design
resolution and content scale factor for the engine
We are the team who made the multi resolution tutorial for the github link, it does support Android but the folders are just named using iOS naming conventions thats all.
Hope this helps.
Regards
Sonar Systems
It's working for me this way for Portrait mode:
Below the header :
typedef struct tagResource
{
cocos2d::CCSize size;
char directory[100];
} Resource;
static Resource smallResource = { cocos2d::CCSizeMake(640, 960),"iPhone" };
static Resource mediumResource = { cocos2d::CCSizeMake(768, 1024),"iPad"};
static Resource largeResource = { cocos2d::CCSizeMake(1536, 2048),"iPadhd" };
static cocos2d::CCSize designResolutionSize = cocos2d::CCSizeMake(768, 1024);
in applicationDidFinishLaunching() method :
// initialize director
CCDirector* pDirector = CCDirector::sharedDirector();
CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();
pDirector->setOpenGLView(pEGLView);
CCSize frameSize = pEGLView->getFrameSize();
std::vector<std::string> searchPaths;
// Set the design resolution
pEGLView->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, kResolutionExactFit);
if (frameSize.height <= smallResource.size.height)
{
searchPaths.push_back(mediumResource.directory);
CCFileUtils::sharedFileUtils()->setSearchPaths(searchPaths);
pDirector->setContentScaleFactor(mediumResource.size.height/designResolutionSize.height);
}
else if (frameSize.height <= mediumResource.size.height)
{
searchPaths.push_back(mediumResource.directory);
CCFileUtils::sharedFileUtils()->setSearchPaths(searchPaths);
pDirector->setContentScaleFactor(mediumResource.size.height/designResolutionSize.height);
}
else
{
searchPaths.push_back(largeResource.directory);
CCFileUtils::sharedFileUtils()->setSearchPaths(searchPaths);
pDirector->setContentScaleFactor(largeResource.size.height/designResolutionSize.height);
}

UITextView and contentScaleFactor

I have a number of text contols in a scrollView that can be zoomed. In order to redraw the controls at a higher resolution to avoid blurry text, I set each view's contentScaleFactor in the view hierarchy as explained here. Everything works fine for labels and textfields but textViews do not redraw at the higher scale factor. I noticed that the only other subview for textViews that may make a difference if set is a private class UIWebDocumentView which implements content like UIWebView ( ie WebKit) but the new scale factor is ignored if set at either level ( UITextView or UIWebDocumentView ).
Any ideas how to reset the scale factor ( resolution ) for TextViews specifically ?
Setting the contentScaleFactor and contentsScale is in fact the key, as #dbotha pointed out, however you have to walk the view and layer hierarchies separately in order to reach every internal CATiledLayer that actually does the text rendering. You also need to account for the screen scale.
The implementation would be something like this:
- (void)updateForZoomScale:(CGFloat)zoomScale {
CGFloat screenAndZoomScale = zoomScale * [UIScreen mainScreen].scale;
// Walk the layer and view hierarchies separately. We need to reach all tiled layers.
[self applyScale:(zoomScale * [UIScreen mainScreen].scale) toView:self.textView];
[self applyScale:(zoomScale * [UIScreen mainScreen].scale) toLayer:self.textView.layer];
}
- (void)applyScale:(CGFloat)scale toView:(UIView *)view {
view.contentScaleFactor = scale;
for (UIView *subview in view.subviews) {
[self applyScale:scale toView:subview];
}
}
- (void)applyScale:(CGFloat)scale toLayer:(CALayer *)layer {
layer.contentsScale = scale;
for (CALayer *sublayer in layer.sublayers) {
[self applyScale:scale toLayer:sublayer];
}
}
You can than call this when the zoom scale changes (part of UIScrollViewDelegate):
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
[self updateForZoomScale:scale];
}
I filed an enhancement request here: rdar://21443666 (http://www.openradar.me/21443666). There's also a sample project with the workaround attached.
Be sure to apply the contentScaleFactor to all subviews of the UITextView. I've just tested the following with a UITextView and found it to work:
- (void)applyScale:(CGFloat)scale toView:(UIView *)view {
view.contentScaleFactor = scale;
view.layer.contentsScale = scale;
for (UIView *subview in view.subviews) {
[self applyScale:scale toView:subview];
}
}

How to draw smooth text in libgdx?

I try to draw simple text in my android game on libgdx, but it's look sharp. How to make text look smooth in different resolutions? My Code:
private BitmapFont font;
font = new BitmapFont();
font.scale((ppuX*0.02f));
font.draw(spb, "Score:", width/2-ppuX*2f, height-0.5f*ppuY);
font.getRegion().getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
This gets the texture used in a BitmapFont and changes its filtering to bilinear, allowing higher resulting image quality while both up- and downscaling it at the cost of slightly slower (the difference is usually not noticeable) GPU rendering.
One solution is to use the FreeType extension to libgdx, as described here. This allows you to generate a bitmap font on the fly from a .ttf font. Typically you would do this at startup time once you know the target resolution.
Here's an example:
int viewportHeight;
BitmapFont titleFont;
BitmapFont textFont;
private void createFonts() {
FileHandle fontFile = Gdx.files.internal("data/Roboto-Bold.ttf");
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(fontFile);
FreeTypeFontParameter parameter = new FreeTypeFontParameter();
parameter.size = 12;
textFont = generator.generateFont(parameter);
parameter.size = 24;
titleFont = generator.generateFont(parameter);
generator.dispose();
}
You should definitly have a quick look on custom font shaders and/or DistanceField-Fonts. They're easy to understand and similarly easy to implement:
https://github.com/libgdx/libgdx/wiki/Distance-field-fonts
DistanceFieldFonts stay smooth, even when you upscale them:
Create a .fnt file using hiero which is provided by libgdx website.
Set the size of font to 150; it will create a .fnt file and a .png file.
Copy both files into your assets folder.
Now declare the font:
BitmapFont font;
Now in create method:
font = new BitmapFont(Gdx.files.internal("data/100.fnt"), false); // 100 is the font name you can give your font any name
In render:
font.setscale(.2f);
font.draw(batch, "whatever you want to write", x,y);
In general you don't get sharp text because you are designing your game for a certain resolution and when you move to a different device, Libgdx scales everything to match the new resolution. Even with linear filtering scaling is bad on text because round corners are easily distorted. In a perfect world you would create the content dynamically at runtime according to the number of pixels available to you and not a single automatic scale would be used.
This is the approach I'm using: Building everything for small screen (480 x 320), and when you open it on a bigger resolution, I load the BitmapFont with a higher size and apply and inverse scale to the one that Libgdx will later do automatically.
Here's an example to make things clearer:
public static float SCALE;
public static final int VIRTUAL_WIDTH = 320;
public static final int VIRTUAL_HEIGHT = 480;
public void loadFont(){
// how much bigger is the real device screen, compared to the defined viewport
Screen.SCALE = 1.0f * Gdx.graphics.getWidth() / Screen.VIRTUAL_WIDTH ;
// prevents unwanted downscale on devices with resolution SMALLER than 320x480
if (Screen.SCALE<1)
Screen.SCALE = 1;
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("data/Roboto-Regular.ttf"));
// 12 is the size i want to give for the font on all devices
// bigger font textures = better results
labelFont = generator.generateFont((int) (12 * SCALE));
// aplly the inverse scale of what Libgdx will do at runtime
labelFont.setScale((float) (1.0 / SCALE));
// the resulting font scale is: 1.0 / SCALE * SCALE = 1
//Apply Linear filtering; best choice to keep everything looking sharp
labelFont.getRegion().getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
}
Bitmap fonts are textures and if you want to make smaller textures look smoother when you are resizing them to bigger sizes you need to make sure you use the right texture filter.
This blog post deals with such issues
With many things deprecated after the update, this is what's working for me:
public void regenerateFonts(OrthographicCamera cam, Game game) {
int size = 18;
if (cam != null && game != null) {
// camera and game are provided, recalculate sizes
float ratioX = cam.viewportWidth / game.getW();
float ratioY = cam.viewportHeight / game.getH();
System.out.println("Ratio: [" + ratioX + ":" + ratioY + "]");
size *= ratioY;
}
// font parameters for this size
FreeTypeFontParameter params = new FreeTypeFontParameter();
params.flip = true; // if your cam is flipped
params.characters = LETTERS; // your String containing all letters you need
params.size = size;
params.magFilter = TextureFilter.Linear; // used for resizing quality
params.minFilter = TextureFilter.Linear; // also
// Lato Light generator
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/Lato-Light.ttf"));
// make the font
fontLatoLight = generator.generateFont(params);
generator.dispose(); // dispose to avoid memory leaks
}
And when you want to render it on the screen:
// text rendering
fontLatoLight.setColor(Color.WHITE); // set color here (has other overloads too)
fontLatoLight.draw(batch, "Hello World!", xCoord, yCoord);
My Solution for smooth text with Libgdx
I use BitmapFont and I generate 3 different size same fonts using Hiero tool
example Arial 16 , Arial 32, Arial 64
I put them in my assets file and use (load) only one of them depeding on the size of screen
if(Gdx.graphics.getWidth() < (480*3)/2)
{
textGametFont = BitmapFont(Gdx.files.internal(nameFont+16+".fnt"),
Gdx.files.internal(nameFont+16+".png"), false);
}else
{
if(Gdx.graphics.getWidth() < (3*920)/2)
{
textGametFont = new BitmapFont(Gdx.files.internal(nameFont+32+".fnt"),
Gdx.files.internal(nameFont+32+".png"), false);
}else
{
textGametFont = new BitmapFont(Gdx.files.internal(nameFont+64+".fnt"),
Gdx.files.internal(nameFont+64+".png"), false);
}
}
then I use this line of code to higher result quality of down and up Scaling
textGametFont.getRegion().getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
scale the image
to handle the size of the font for all type of resolution of device I use those two functions
public static float xTrans(float x)
{
return x*Gdx.graphics.width/(YourModel.SCREEN_WIDTH);
}
public static float yTrans(float y)
{
return y*Gdx.graphics.height/YourModel.SCREEN_Height;
}
the model screen resolution that i use is
SCREEN_WIDTH = 480
SCREEN_HEIGHT = 320
Set the scale to the font
textGametFont.setScale((xtrans(yourScale)+ ytrans(yourScale))/2f);
and finally draw your text
textGametFont.draw(batch, "WINNER !!", xTrans(250), yTrans(236));
Hope this was clear and helpful !!!
private BitmapFont font;
font = new BitmapFont();
font.scale((ppuX*0.02f));
font.draw(spb, "Score:", width/2-ppuX*2f, height-0.5f*ppuY);
Check out [this](http://www.badlogicgames.com/wordpress/?p=2300) blog post.
??? This just explains how to use the .scale() method which I'm stating is deprecated in the current release.
In scene2d, if you want apply antialiasing to all your labels, put this on constructor of your first screen:
skin.getFont("default-font").getRegion().getTexture().setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
This is the first screen in my game:
...
public class MainMenuScreen implements Screen {
public MainMenuScreen() {
...
skin.getFont("default-font").getRegion().getTexture().setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
}
}
Font name is in ui.json file, check for BitmapFont and Label$LabelStyle section:
"com.badlogic.gdx.graphics.g2d.BitmapFont": {
"default-font": {
"file": "default.fnt"
}
},
"com.badlogic.gdx.scenes.scene2d.ui.Label$LabelStyle": {
"default": {
"font": "default-font",
"fontColor": "white",
}
},