I am developing a chat app, when list loaded and when a new item added to list I need to scroll to bottom of list. I can do that with this.
scrollToBottom() {
let lv = <ListView>frame.topmost().getViewById('messageList');
lv.scrollToIndex(this.store.items.getValue().length - 1)
}
But it showing bottom of list instant
There is a guide to do that on IOS but not on Android
private srollListView(position: number) {
if (this._listView.ios) {
this._listView.ios.scrollToRowAtIndexPathAtScrollPositionAnimated(
NSIndexPath.indexPathForItemInSection(position, 0),
UITableViewScrollPosition.UITableViewScrollPositionTop,
true
);
}
else {
this._listView.scrollToIndex(position);
}
}
link to guide: http://nuvious.com/Blog/2016/4/4/how-to-make-the-nativescript-listview-scrolltoindex-animated-on-ios
Is there any way to do that on Android?
You could use smoothScrollToPosition android method, which provides smooth scroll for the ListView, which you need. I am providing sample code.
main-page.xml
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo">
<GridLayout>
<ListView items="{{ source }}" id="lvid" loaded="onLoaded" itemLoading="onItemLoading" itemTap="onItemTap">
<ListView.itemTemplate>
<StackLayout>
<Label text="{{title}}" textWrap="true" />
</StackLayout>
</ListView.itemTemplate>
</ListView>
</GridLayout>
</Page>
main-page.ts
import { EventData } from 'data/observable';
import { Page } from 'ui/page';
import { HelloWorldModel } from './main-view-model';
import {ListView} from "ui/list-view"
// Event handler for Page "navigatingTo" event attached in main-page.xml
export function navigatingTo(args: EventData) {
let page = <Page>args.object;
var array=[];
for(var i=0;i<100;i++){
array.push({title:"title"+i});
}
page.bindingContext = {source:array};
setTimeout(function(){
var listview:ListView =<ListView> page.getViewById("lvid");
listview.android.smoothScrollToPosition(60);
}, 4000)
}
Just to let everyone who are still looking for this know, they have added the scrollToIndexAnimated method to ListView since v4.2.0
https://github.com/NativeScript/NativeScript/blob/master/CHANGELOG.md#420-2018-08-08
Related
We've got a ScrollView which I've set with a VerticalOptions of "End", so that when we add content to it at runtime it 'grows' from the bottom.
We're scrolling to the end when adding content, with animation. This looks good when the ScrollView is full and is actually scrolling.
However, when content is added to the ScrollView, the new content appears immediately with no animation.
Any thoughts on how to animate the growth of the ScrollView as the new content is added? Ideally I'd like it to slide up, like the animated scroll when it's full.
We're using a RepeaterView as the content of the ScrollView, if that's relevant.
Relevant existing code below (we're using Forms with MvvmCross - hence an MVVM pattern):
ViewModel
private async Task NextClick()
{
var claimFlowQuestion = await GetClaimFlowQuestion(_currentIndexQuestion);
Questions.Add(ClaimFlowExtendFromClaimFlow(claimFlowQuestion));
// Trigger PropertyChanged so the Repeater updates
await RaisePropertyChanged(nameof(Questions));
// Trigger the QuestionAdded event so the ScrollView can scroll to the bottom (initiated in the xaml.cs code behind)
QuestionAdded?.Invoke(this, new EventArgs());
}
XAML
<ScrollView Grid.Row="1" x:Name="QuestionScrollView">
<StackLayout VerticalOptions="End"
Padding ="10,0,10,0"
IsVisible="{Binding Busy, Converter={StaticResource InvertedBooleanConvertor}}}">
<controls:RepeaterView
x:Name="QuestionRepeater"
Margin ="10"
AutomationId="IdQuestions"
Direction ="Column"
ItemsSource ="{Binding Questions}"
ClearChild ="false">
<controls:RepeaterView.ItemTemplate>
<DataTemplate>
<ViewCell>
<controlsClaimFlowControls:QuestionBlock
Margin ="0,20,0,20"
QuestionNumber ="{Binding Index}"
QuestionText ="{Binding QuestionText}"
QuestionDescription="{Binding QuestionDescription}"
ItemsSource ="{Binding Source}"
DisplayMemberPath ="{Binding DisplayPaths}"
QuestionType ="{Binding QuestionType}"
SelectedItem ="{Binding Value}"
IsEnabledBlock ="{Binding IsEnabled}" />
</ViewCell>
</DataTemplate>
</controls:RepeaterView.ItemTemplate>
</controls:RepeaterView>
</StackLayout>
</ScrollView>
XAML.cs
protected override void OnAppearing()
{
if (BindingContext != null)
{
MedicalClaimConditionPageModel model = (MedicalClaimConditionPageModel)this.BindingContext.DataContext;
model.QuestionAdded += Model_QuestionAdded;
}
base.OnAppearing();
}
protected override void OnDisappearing()
{
MedicalClaimConditionPageModel model = (MedicalClaimConditionPageModel)this.BindingContext.DataContext;
model.QuestionAdded -= Model_QuestionAdded;
base.OnDisappearing();
}
void Model_QuestionAdded(object sender, EventArgs e)
{
Device.BeginInvokeOnMainThread(async () =>
{
if (Device.RuntimePlatform == Device.iOS)
{
// We need a Task.Delay to allow the contained controls to repaint and have their new sizes
// Ultimately we should come up with a better resolution - the delay value can vary depending on device and OS version.
await Task.Delay(50);
}
await QuestionScrollView.ScrollToAsync(QuestionRepeater, ScrollToPosition.End, true);
});
}
A reasonable answer has been posted by user yelinzh on the Xamarin forums:
How about trying to use Custom Renderer such as the fading effect.
[assembly: ExportRenderer(typeof(ScrollView_), typeof(ScrollViewR))]
namespace App3.Droid
{
public class ScrollViewR : ScrollViewRenderer
{
public ScrollViewR(Context context) : base(context)
{
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
this.SetFadingEdgeLength(300);
this.VerticalFadingEdgeEnabled = true;
}
}
}
This would probably work. In fact we've got for a different approach
(after feedback from the customer), where we're keeping the current question at the top of the screen and scrolling off upwards, so the content is no longer expanding.
How is it possible to achieve item shadows in a RadListView? To have a shadow of each item and the shadow is not stripped between the items or on the side paddings.
For Android:
XML:
<lv:RadListView xmlns:lv="nativescript-ui-listview" loaded="listLoaded">
<lv:RadListView.itemTemplate>
<StackLayout loaded="itemLoaded">
...
</StackLayout>
</lv:RadListView.itemTemplate>
</lv:RadListView>
JS:
function listLoaded(args) {
var list = args.object;
list.androidListView.setClipToPadding(false);
list.androidListView.setClipChildren(false);
}
function itemLoaded(args) {
var item = args.object;
item.nativeView.setOutlineProvider(android.view.ViewOutlineProvider.BOUNDS);
item.nativeView.setClipToOutline(false);
}
exports.listLoaded = listLoaded;
exports.itemloaded = itemLoaded;
This creates a nics unstripped shadow to every direction:
I'm using the WebView and building my own html. I want to bind the webview to changes elsewhere on the form. Here is my xaml:
<WebView HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
Margin="0">
<WebView.Source>
<HtmlWebViewSource x:Name="WebViewSoruce1" Html="{Binding Description}"/>
</WebView.Source>
</WebView>
Here is my model code for the Description:
public string Description
{
get {
return _description;
}
set
{
_description = value;
RaisePropertyChanged();
}
}
This works fine on Android but not for iOS. Any suggestions would be greatly appreciated.
So if anyone else runs into this problem. This is what finally worked for me on both iOS and Android.
I had to bind as a WebViewSource to its Source attribute and not to the HTML. Here is my XAML:
<WebView HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
Margin="0" Source="{Binding WebViewSource}"/>
In my view model I have two properites. One to bind the HTML changes, I named it as Description. The other was to bind to the WebViewSource.
Here is the code-behind:
public HtmlWebViewSource WebViewSource
{
get
{
return new HtmlWebViewSource { Html = _description };
}
}
public string Description
{
get {
return _description;
}
set
{
_description = value;
RaisePropertyChanged();
RaisePropertyChanged("WebViewSource");
}
}
This worked for me.
On my nativescript app - I have a button at the bottom of a screen. On the screen there is a Text area. When the user taps in the Text Area, a virtual keyboard appears. At this point, I want the button at the bottom to move up and appear just on top of the virtual keyboard. Any suggestions on how I can achieve this in both android and iOS?
Code
<GridLayout>
<ActionBar title="" backgroundColor="#f82462" top="0" left="0">
<NavigationButton (tap)="goBack()"></NavigationButton>
</ActionBar>
<GridLayout rows="*, auto">
<GridLayout row ='0' rows="auto *" columns="">
<GridLayout row="0" rows="" columns="">
<Button text="Top Button" (tap)="goNext()"></Button>
</GridLayout>
<GridLayout row="1" backgroundColor="#f82462">
<TextView [(ngModel)]="xyz" class="input" hint="Write your question as a complete sentence.Click on camera to add images if required." returnkeyType="done" id="questionText"></TextView>
</GridLayout>
</GridLayout>
<StackLayout row='1'>
<Button text="Next" (tap)="goNext()"></Button>
</StackLayout>
</GridLayout>
I am not able to test this right now, but have you tried to wrap everything inside the main GridLayout in <ScrollView> ... </ScrollView>
I also encountered this problem for my instant chat application, here is the solution : https://gist.github.com/elvticc/0c789d08d57b1f4d9273f7d93a7083ec
// Also use IQKeyboardManager to customize the iOS keyboard
// See https://github.com/tjvantoll/nativescript-IQKeyboardManager
// let iqKeyboard: IQKeyboardManager = IQKeyboardManager.sharedManager();
// iqKeyboard.toolbarDoneBarButtonItemText = "OK";
// iqKeyboard.canAdjustAdditionalSafeAreaInsets = true;
// iqKeyboard.shouldFixInteractivePopGestureRecognizer = true;
// Angular
[...]
import { OnInit, OnDestroy, ElementRef, ViewChild } from "#angular/core";
[...]
// NativeScript
[...]
import { ios as iosApp } from "tns-core-modules/application/application";
[...]
#ViewChild("element") private _element: ElementRef<StackLayout>; // FlexboxLayout, GridLayout, etc.
private _keyboardHeight: number = 0;
private _elementHeight: number = 0;
private _observerIDs: Array<object> = new Array();
// Start events when the component is ready
ngOnInit(): void {
// iOS keyboard events
if (iosApp) {
let eventNames: Array<string> = [
UIKeyboardWillShowNotification,
UIKeyboardDidShowNotification,
UIKeyboardWillHideNotification
];
// Catch the keyboard height before it appears
this._observerIDs.push({
event: eventNames[0],
id: iosApp.addNotificationObserver(eventNames[0], (event) => {
let currHeight: number = this._keyboardHeight,
newHeight: number = event.userInfo.valueForKey(UIKeyboardFrameEndUserInfoKey).CGRectValue.size.height;
if (currHeight != newHeight) {
this._keyboardHeight = newHeight;
}
})
});
// Position the element according to the height of the keyboard
this._observerIDs.push({
event: eventNames[1],
id: iosApp.addNotificationObserver(eventNames[1], (event) => {
if (this._elementHeight == 0) {
this._elementHeight = this._element.nativeElement.getActualSize().height;
}
this._element.nativeElement.height = this._keyboardHeight + this._elementHeight;
})
});
// Reposition the element according to its starting height
this._observerIDs.push({
event: eventNames[2],
id: iosApp.addNotificationObserver(eventNames[2], () => {
this._element.nativeElement.height = this._elementHeight; // or "auto";
})
});
}
}
// Stop events to avoid a memory leak
ngOnDestroy(): void {
if (iosApp) {
let index: number = 0;
for (index; index < this._observerIDs.length; index++) {
let observerId: number = this._observerIDs[index]['id'],
eventName: string = this._observerIDs[index]['event'];
iosApp.removeNotificationObserver(observerId, eventName);
}
}
}
Marcel Ploch's original : https://gist.github.com/marcel-ploch/bf914dd62355049a0e5efb4885ca4c6e
I'm using Xamarin.Forms, currently trying to make a TableView without a section header. Right now on iOS this looks fine as the section header is not visible or clickable, but on Android the header is blank, visible, and clickable.
I've tried this http://forums.xamarin.com/discussion/18037/tablesection-w-out-header
Code in xaml -
<TableView>
<TableView.Root>
<TableSection>
<TextCell Text="Login" Command="{Binding Login}" />
<TextCell Text="Sign Up" Command="{Binding SignUp}" />
<TextCell Text="About" Command="{Binding About}"/>
</TableSection>
</TableView.Root>
</TableView>
Code in c#
Content = new TableView
{
Root = new TableRoot
{
new TableSection ()
{
new TextCell { Text="Login", Command = Login },
new TextCell { Text="Sign Up", Command = SignUp },
new TextCell { Text="About", Command = About },
},
},
};
To suppress the header on Android, we use a custom renderer. If Text is empty, it hides the cell by removing all children, reducing the height and removing all padding.
[assembly: ExportRenderer(typeof(TextCell), typeof(ImprovedTextCellRenderer))]
namespace YourSolution.Android
{
public class ImprovedTextCellRenderer : TextCellRenderer
{
protected override global::Android.Views.View GetCellCore(Cell item, global::Android.Views.View convertView, ViewGroup parent, Context context)
{
var view = base.GetCellCore(item, convertView, parent, context) as ViewGroup;
if (String.IsNullOrEmpty((item as TextCell).Text)) {
view.Visibility = ViewStates.Gone;
while (view.ChildCount > 0)
view.RemoveViewAt(0);
view.SetMinimumHeight(0);
view.SetPadding(0, 0, 0, 0);
}
return view;
}
}
}
Just copy this class somewhere into your Android project and you should be fine.