I'm using Nativescript Vue and trying to rotate text in TextView tag via Nativescript-Gesture Rotation. Text rotates, but it isn't smooth, it jumps from one direction to another. And that's my question
Why this happens? What is the reason that NS-Gesture Rotation acts so strange? And how to make it work?
I'll post my sample example here and in the NS Playground too.
<template>
<Page class="page">
<ActionBar title="Home" class="action-bar" />
<StackLayout class="home-panel">
<TextView #rotation="onRotation" id="liveLabel" textWrap="true"
:rotate="textRotation" editable="false">
<FormattedString id="formString">
<Span id="spanText" text="Test text" fontSize="20"
color="red" />
</FormattedString>
</TextView>
</StackLayout>
</Page>
</template>
<script>
export default {
data() {
return {
textRotation: 0
};
},
methods: {
onRotation(args) {
console.log(
"Rotate angle: " + args.rotation + " state: " + args.state
);
this.textRotation = Math.floor(args.rotation);
}
}
};
</script>
Actually what you are seeing is totally expected and you are making it happen.
Imagine you are calculating position on an object and moving it at the same time, so the rotation event on TextView is giving the right position based on your finger movements once and then gives another position based on the new rotate value you applied on the TextView.
In order to solve this, you should have 2 copy of the object (TextView here). One to listen for finger movements and another to animate, something like this.
<template>
<Page class="page">
<ActionBar title="Home" class="action-bar" />
<StackLayout class="home-panel">
<GridLayout>
<TextView ref="animatedLbl" textWrap="true" :rotate="textRotation"
editable="false" visibility="hidden" verticalAlignment="top">
<FormattedString>
<Span text="Test text" fontSize="20" color="red" />
</FormattedString>
</TextView>
<TextView ref="hostLbl" #rotation="onRotation" textWrap="true"
editable="false" verticalAlignment="top">
<FormattedString>
<Span text="Test text" fontSize="20" color="red" />
</FormattedString>
</TextView>
</GridLayout>
</StackLayout>
</Page>
</template>
<script>
import * as GestureModule from "tns-core-modules/ui/gestures";
export default {
data() {
return {
textRotation: 0
};
},
methods: {
onRotation(args) {
if (args.state === GestureModule.GestureStateTypes.began) {
this.$refs.hostLbl.nativeView.visibility = "hidden";
this.$refs.animatedLbl.nativeView.visibility = "visible";
}
this.textRotation = Math.floor(args.rotation);
if (
args.state === GestureModule.GestureStateTypes.cancelled ||
args.state === GestureModule.GestureStateTypes.ended
) {
this.$refs.hostLbl.nativeView.rotate = this.textRotation;
this.$refs.hostLbl.nativeView.visibility = "visible";
this.$refs.animatedLbl.nativeView.visibility = "hidden";
}
}
}
};
</script>
Playground Sample
Related
I would like to know how can I have multiple fullscreen in scrollview with nativescript please ?
I tried this :
<Page actionBarHidden="true" class="page">
<ScrollView orientation="vertical">
<StackLayout>
<StackLayout height="100%" backgroundColor="red">
<Label text="Fullscreen 1"></Label>
</StackLayout>
<StackLayout height="100%" backgroundColor="blue">
<Label text="Fullscreen 2"></Label>
</StackLayout>
</StackLayout>
</ScrollView>
</Page>
But the stack aren't fullscreen.
I fixed with this :
Template
<Page actionBarHidden="true">
<ScrollView orientation="vertical">
<StackLayout>
<StackLayout :height="screenHeight" backgroundColor="red">
...
</StackLayout>
<StackLayout :height="screenHeight" backgroundColor="blue">
...
</StackLayout>
</StackLayout>
</ScrollView>
</Page>
And add this :
import { screen } from "tns-core-modules/platform";
...
data() {
return {
screenHeight: 0
};
},
...
created() {
this.screenHeight = screen.mainScreen.heightDIPs
}
In my Nativescript-Vue application, I have a ListView within a ScrollView, defined as such:
<Page>
<ActionBar :title="friend.name">
<NavigationButton text="Go Back" android.systemIcon="ic_menu_back" #tap="$navigateBack"/>
</ActionBar>
<ActivityIndicator busy="true" v-if="isLoading" />
<StackLayout v-else orientation="vertical" >
<ScrollView ref="scrollLayout" class="scrollLayout" height="90%" orientation="vertical">
<ListView minHeight="90%" ref="listLayout" class="listLayout" for="item in conversations">
<v-template>
<GridLayout columns="*" rows="auto" class="msg">
<StackLayout :class="currentUser.id == item.user_id? 'me' : 'them'" orientation="horizontal" :horizontalAlignment="currentUser.id == item.user_id? 'right' : 'left'">
<Label :text="item.chat" class="msg_text" textWrap="true" verticalAlignment="top" />
</StackLayout>
</GridLayout>
</v-template>
</ListView>
</ScrollView>
<StackLayout height="10%" class="chat-box">
<GridLayout columns="*,auto" style="padding: 5" verticalAlignment="center">
<TextField hint="Enter message..." class="chatTextField" row="0" col="0" v-model="message"></TextField>
<Button row="0" col="1" text="send" #tap="scollToBottom"></Button>
</GridLayout>
</StackLayout>
</StackLayout>
</Page>
I already looked into this question by adding minHeight but it does not work on my case.
This is the method code to scroll to bottom but the scrollableHeight is always 0
scollToBottom() {
this.scrollLayout.nativeView.scrollToVerticalOffset(this.scrollLayout.nativeView.scrollableHeight, false);
console.log(this.$refs.scrollLayout.nativeView.scrollableHeight)
},
Hello I found a solution for this. I use ListView to scroll to the latest chat by using this code: Based on this [link]: https://docs.nativescript.org/vuejs/ns-ui/ListView/scrolling
scollToBottom () {
// in order to avoid race conditions (only on iOS),
// in which the UI may not be completely updated here
// we use this.$nextTick call
this.$nextTick(() => {
const indexToScroll = this.conversations.length - 1;
console.log('Programmatic scrolling to ' + this.conversations[indexToScroll].chat + '... ');
this.$refs.listLayout.nativeView.scrollToIndex(indexToScroll, false);
});
},
That's what I've implemented in my app which is not ideal but it works. I had to add a setTimeout for it to scroll. The messages are again wrapped in a Scroll View:
scrollToBottom() {
setTimeout(() => {
let scroll = this.$refs.scrollLayout.nativeView.scrollableHeight //get the height of the scroll area
console.log("Height changed " + scroll)
this.$refs.scrollLayout.nativeView.scrollToVerticalOffset(scroll, false) //scroll to the last message
}, 1000)
}
I want to trigger the *ngIf function through my home.component.ts.
html
<GridLayout class="page">
<GridLayout row="0" rows="*, 2*, *">
<GridLayout width="57%" row="0" horizontalAlignment="center" verticalAlignment="center">
<button (click)="test()" text="testbutton"></button>
</GridLayout>
<GridLayout class="carousel-item-circle" row="1" horizontalAlignment="center" verticalAlignment="center">
<label class="fa carousel-item-icon" text="" textWrap="true"></label>
</GridLayout>
<GridLayout *ngIf="testi" width="49%" row="2" horizontalAlignment="center" verticalAlignment="center">
<Label class="text-color-blue opensans-regular carousel-item-desc" text="Mikrofon zum Sprechen antippen." textWrap="true"></Label>
</GridLayout>
</GridLayout>
ts
public vibrator = new Vibrate();
public testi = true;
constructor(private page: Page) {
// Use the component constructor to inject providers.
}
ngOnInit(): void {
this.page.actionBarHidden = true;
this.page.addCss(".page-content { background: #000000");
}
vibrate(){
this.vibrator.vibrate(2000);
}
test(){
this.testi = !this.testi;
}
It seems so simple but it doesnt work.
Is there something i missed?
You need to make the variable true/false on click
Write in Your HTML file
<button (click)="test()">Click</button>
<span *ngIf="testi">NG IF</span>
Write in Your TS file
First Declare testi as
testi = false
test(){
testi = true;
}
Using *ngIf you can show the element whenever it is true or not empty, if it is false or empty then it will not display element in browser
Hope this will solve your problem, Thanks
NativeScript Button uses tap not click. So in your code test() is never called.
<button (tap)="test()" text="testbutton"></button>
Refer the getting started guide and go through the available UI components to understand the core differences.
I want to position an element at the bottom of the screen in Absolute Layout in NativeScript.
I have this code:
<AbsoluteLayout>
<maps:mapView
left="0"
top="0"
width="100%"
height="100%"
latitude="{{ map.latitude }}"
longitude="{{ map.longitude }}"
zoom="{{ map.zoom }}"
padding="{{ map.padding }}"
mapReady="onMapReady"
coordinateTapped="onCoordinateTapped"
markerSelect="onMarkerSelect"
shapeSelect="onShapeSelect"
cameraChanged="onMapCameraChanged"/>
<ScrollView
left="0"
top="0"
width="100%"
orientation="horizontal">
<!-- More XML -->
</ScrollView>
<StackLayout
left="0"
bottom="0"
width="100%"
visibility="visible"
orientation="horizontal"
style="background-color: red;">
<Label text="TITLE"></Label>
</StackLayout>
</AbsoluteLayout>
I figured out that there is no bottom attribute for AbsoluteLayout... Here is the picture of what I want to create:
So how to arange items like in the picture, especially the bottom one?
EDIT: I should note that dimensions of this bottom rectangle may not be always same....
I did something similar one day, programmatically & with Angular, maybe this can help.
If you don't want to use a GridLayout you can try to get height of your bottom element and of the screen, then place your element from the top with a simple calcul : screen's height - bottom element's height (- more if you want some padding). You can use two types of values : DIPs and pixels. If you're using pixels, you need to convert your values into DIPs by using the screen scale.
Something like this (I didn't test the code I'm giving you, it's just an example) :
1] add an id to your bottom element so you can access it inside your component :
<StackLayout #bottomElt></StackLayout>
2] update your component to set element position inside your absolute layout
// you need ElementRef, OnInit and ViewChild
import { Component, ElementRef, OnInit, ViewChild, ViewContainerRef } from "#angular/core";
import { AbsoluteLayout } from "ui/layouts/absolute-layout";
import { StackLayout } from "ui/layouts/stack-layout";
// you need access to screen properties
import { screen } from "tns-core-modules/platform";
[...]
export class YourComponent implements OnInit {
// add access to element inside your component
#ViewChild("bottomElt") bottomElt: ElementRef;
// create variable to access bottom element properties
bottomContainer: StackLayout;
// set bottom element position after view init
// example : inside ngOnInit function (for Angular version)
ngOnInit(): void {
this.bottomContainer = <StackLayout>this.bottomElt.nativeElement;
// using DIPs values only
AbsoluteLayout.setTop(this.bottomContainer, (screen.mainScreen.heightDIPs - Number(this.bottomContainer.height)));
// using pixels and screen scale
// this way you can get height without knowing it
AbsoluteLayout.setTop(this.bottomContainer, (screen.mainScreen.heightDIPs - (Number(this.bottomContainer.getMeasuredHeight()) / screen.mainScreen.scale)));
}
More information about screen values : https://docs.nativescript.org/api-reference/interfaces/platform.screenmetrics.html
Alternative way
Instead of using AbsoluteLayout, you can use a GridLayout to set a bottom bar, with two rows : one with a wildcard size and the other with auto size so it can fit your bottom bar height everytime it changes. I did it this way in a mobile application to get a menu at the bottom in Android and IOS :
<GridLayout rows="*, auto" width="100%">
<AbsoluteLayout row="0" orientation="vertical">
<!-- YOUR CONTENT (maps & ScrollView) -->
</AbsoluteLayout>
<!-- YOUR BOTTOM BAR (StackLayout). Don't forget to add row="1" -->
<StackLayout #bottomElt row="1">[...]</StackLayout>
</GridLayout>
Another option is using FlexboxLayout in your AbsoluteLayout container like this:
<FlexboxLayout flexDirection="column" justifyContent="space-between" height="100%">
<ScrollView
width="100%"
orientation="horizontal">
<!-- More XML -->
</ScrollView>
<StackLayout
width="100%"
visibility="visible"
orientation="horizontal"
style="background-color: red;">
<Label text="TITLE"></Label>
</StackLayout>
</FlexboxLayout>
This is the absolute best solution, got it from one of the devs: https://github.com/NativeScript/NativeScript/issues/5591#issuecomment-482640921
<GridLayout rows="*,auto">
<ItemTakingFullScreen rowSpan="2"/>
<ItemShownUnder row="1"/>
<ItemShownAbove row="1">
</GridLayout>
Basically, you can use grid layout and have a item take up multiple grid spaces, sharing them with some other item.
here is the best solution
wrapper all the elements in an absolutelayout with width and hieght to 100% and maybe add a gridlayout to hold the main content .
<AbsoluteLayout width='100%' height='100%'>
<StackLayout width='100%' hieght='100%' left='0' top='0'>
//add you structure here
</StackLayout>
add your fixed element here
<image src='add the float item'/>
</AbsoluteLayout>
It can be done also with GridLayout:
<GridLayout rows="16,*,16" columns="16,*,16" width="100%" backgroundColor="red">
<GridLayout row="1" col="1" rows="auto, auto, auto" columns="auto" horizontalAlignment="right" verticalAlignment="bottom" backgroundColor="blue">
<!-- Your content at bottom right corner -->
<Label row="0" text="Your content" textAlignment="center" textWrap="true"></Label>
<Label row="1" text="at" textAlignment="center" textWrap="true"></Label>
<Label row="2" text="bottom right corner" textAlignment="center"></Label>
</GridLayout>
</GridLayout>
This is the easy way
<DockLayout backgroundColor="lightgray" stretchLastChild="true">
<Label text="top" dock="top" height="60" backgroundColor="green">
</Label>
<Label text="bottom" dock="bottom" height="60" backgroundColor="yellow"></Label>
<Label text="center" backgroundColor="red"></Label>
</DockLayout>
This is what you want!
<DockLayout backgroundColor="lightgray" stretchLastChild="false">
<Label text="top" dock="top" height="60" backgroundColor="green">
</Label>
<Label text="bottom" dock="bottom" height="60" backgroundColor="yellow"></Label>
</DockLayout>
there is no error in console, I've tried many things but cannot understand the actual problem with code. Can anybody help me in this.. and emulator snippets and code below:
home.ts:
enter code here
import {EventData,Observable} from "data/observable";
import {Page} from "ui/page";
var page : Page;
var tempSessions = [
{
id:"0",
title:"session 0"
},{
id:"1",
title:"session 1"
},{
id:"2",
title:"session 2"
}];
export function pageLoaded(args:EventData) {
page = <Page>args.object;
page.bindingContext = new Observable({
sessions:tempSessions
});
}
home.xml:
<Page xmlns="http://schemas.nativescript.org/tns.xsd" load="pageLoaded">
<GridLayout rows="auto,*">
<!--row 0-->
<StackLayout></StackLayout>
<!--row 1-->
<GridLayout rows="auto,*" row="1">
<ListView items="{{ sessions }}">
<ListView.itemTemplate>
<Label text="item"/>
</ListView.itemTemplate>
</ListView>
</GridLayout>
</GridLayout>
</Page>
emulator
I found several issues in your code. The first one is that the page event is called loaded instead load. The second one is that you could use ObservableArray Module, which would auto update the ListView when you push new item in the array. You could review the NativeScript Getting Started Guide
main-page.xml
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded">
<GridLayout rows="auto,*">
<!--row 0-->
<StackLayout row="0">
<Label text="sample text" textWrap="true" />
</StackLayout>
<!--row 1-->
<GridLayout rows="*" row="1">
<ListView row="0" items="{{ sessions }}">
<ListView.itemTemplate>
<StackLayout>
<Label text="{{title}}"/>
</StackLayout>
</ListView.itemTemplate>
</ListView>
</GridLayout>
</GridLayout>
</Page>
main-page.ts
import {EventData,Observable} from "data/observable";
import {ObservableArray} from "data/observable-array";
import {Page} from "ui/page";
var page : Page;
var tempSessions = new ObservableArray();
export function pageLoaded(args:EventData) {
tempSessions.push({
id:"0",
title:"session 0"
});
tempSessions.push({
id:"1",
title:"session 1"
});
tempSessions.push({
id:"2",
title:"session 2"
});
page = <Page>args.object;
page.bindingContext = new Observable({
sessions:tempSessions
});
}