how to implement tap gesture in webview to display html not website - xamarin.forms

I need my web view to be tappable and scrolable. Once I implement on touch the scroll doesnt work. This way i managed to get it working however now i dont know how to make the web view tappable? the ButtonPress does nothing and if i use Move then i am just scrolling
This my my render in mu droid project
class ExtendedWebViewClient : WebViewClient
{
WebView _webView;
public async override void OnPageFinished(WebView view, string url)
{
try
{
_webView = view;
if (_xwebView != null)
{
view.Settings.JavaScriptEnabled = true;
await Task.Delay(100);
string result = await _xwebView.EvaluateJavaScriptAsync("(function(){return document.body.scrollHeight;})()");
_xwebView.HeightRequest = Convert.ToDouble(result);
}
base.OnPageFinished(view, url);
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}");
}
}
public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)
{
return true;
}
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
_xwebView = e.NewElement as ExtendedWebView;
_webView = Control;
if (e.OldElement == null)
{
_webView.SetWebViewClient(new ExtendedWebViewClient());
}
if (e.OldElement != null)
{
Control.Touch -= ControlOnTouch;
Control.ScrollChange -= ControlOnScrollChange;
}
if (e.NewElement != null)
{
Control.Touch += ControlOnTouch;
Control.ScrollChange += ControlOnScrollChange;
}
}
private void ControlOnScrollChange(object sender, ScrollChangeEventArgs scrollChangeEventArgs)
{
if (scrollChangeEventArgs.ScrollY > 0 && scrollChangeEventArgs.OldScrollY == 0)
{
Control.Parent.RequestDisallowInterceptTouchEvent(true);
}
}
private void ControlOnTouch(object sender, Android.Views.View.TouchEventArgs e)
{
// Executing this will prevent the Scrolling to be intercepted by parent views
switch (e.Event.Action)
{
case MotionEventActions.Down:
Control.Parent.RequestDisallowInterceptTouchEvent(true);
break;
case MotionEventActions.Up:
Control.Parent.RequestDisallowInterceptTouchEvent(false);
break;
case MotionEventActions.ButtonPress:
Console.WriteLine("press");
break;
case MotionEventActions.Mask:
Console.WriteLine("mask");
break;
}
// Calling this will allow the scrolling event to be executed in the WebView
Control.OnTouchEvent(e.Event);
}

Instead of using the gesture recognizer on your webview, you can use the Focused event like following . It will been invoked when you tap the WebView .
var wb = new WebView
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
Source = "xxx",
};
wb.Focused += (sender, event) =>
{
//Handle your logic here!
wb.Unfocus();
};
Unfocus() is used if you want to implement your logic everytime the webview is tapped.

Related

How to use MediaManager plugin and play and pause icon in Xamarin.Forms project

Update:
It's playing well, but when finished playing it's not showing the play button. I have tried I couldn't get it well. Maybe I spiked something else were.
How can I show the play button when a player finished playing?
HomePage.xaml
<Button ImageSource="{Binding PlayIcon}"
Command="{Binding PlayCommand}"
HorizontalOptions="End"
VerticalOptions="End"/>
HomePage.xaml.cs
public HomePage()
{
InitializeComponent();
isPlaying = true;
}
private bool isPlaying;
public bool IsPlaying
{
get { return isPlaying; }
set
{
isPlaying = value;
OnPropertyChanged(nameof(PlayIcon));
}
}
public string PlayIcon { get => isPlaying ? "play.png" : "pause.png"; }
public ICommand PlayCommand => new Command(Play);
private async void Play()
{
if (isPlaying)
{
await CrossMediaManager.Current.Play("file:///android_asset/running.mp3");
IsPlaying = true; ;
}
else
{
await CrossMediaManager.Current.Pause();
IsPlaying = false; ;
}
}
Thank you for your contribution.
to play an asset, use this syntax
private async void PlayButtonClicked(object sender, EventArgs e)
{
// update the button's image
((Button)sender).ImageSource = ImageSource.FromFile("pause.png");
wait CrossMediaManager.Current.Play("file:///android_asset/long-test.mp3");
}
ref: https://github.com/Baseflow/XamarinMediaManager/issues/840
the docs contains a list of events supported by the control, including a MediaItemFinished event

Set DarkMode on the first page status bar xamarin forms android

Using xamarin forms and we are adding ability to switch between Dark-Light mode. All is good however the first page of the app in android whatever I do the status bar color wont change.
I guess that in the android project I have to call SetTheme(...) before OnCreate.
Or Am I missing something here?
Question
How do you set the status bar color depending on theme? code below does not change once the android has loaded
public void SetStatusBarColor(System.Drawing.Color color, bool darkStatusBarTint)
{
var activity = Platform.CurrentActivity;
var window = activity.Window;
window?.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
window?.ClearFlags(WindowManagerFlags.TranslucentStatus);
window?.SetStatusBarColor(color.ToPlatformColor());
var flag = (StatusBarVisibility)SystemUiFlags.LightStatusBar;
if (window != null)
{
window.DecorView.SystemUiVisibility = darkStatusBarTint ? flag : 0;
}
}
Suggestions?
thanks
Try this:
private void SetStatusBarColor(System.Drawing.Color color, bool darkStatusBarTint)
{
var activity = Platform.CurrentActivity;
var window = activity.Window;
if (window != null)
{
window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
window.ClearFlags(WindowManagerFlags.TranslucentStatus);
window.SetStatusBarColor(color.ToPlatformColor());
StatusBarVisibility flags = default;
if (darkStatusBarTint)
flags |= (StatusBarVisibility)SystemUiFlags.LightStatusBar;
else
flags &= ~(StatusBarVisibility)SystemUiFlags.LightStatusBar;
window.DecorView.SystemUiVisibility = flags;
}
}
Or
private void SetStatusBarColor(System.Drawing.Color color, bool darkStatusBarTint)
{
var activity = Platform.CurrentActivity;
var window = activity.Window;
if (window != null)
{
window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
window.ClearFlags(WindowManagerFlags.TranslucentStatus);
window.SetStatusBarColor(color.ToPlatformColor());
window.DecorView.SystemUiVisibility = darkStatusBarTint
? (StatusBarVisibility)SystemUiFlags.LightStatusBar
: StatusBarVisibility.Visible;
}
}
Both functions work.
You can update the StatusBar color in the MainActivity.OnCreate method and also listen to the App.Current.RequestedThemeChanged event.
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
ApplyStatusBarColor(App.Current.RequestedTheme);
App.Current.RequestedThemeChanged += (s, e) => ApplyStatusBarColor(e.RequestedTheme);
}
private void ApplyStatusBarColor(Xamarin.Forms.OSAppTheme osAppTheme)
{
if (osAppTheme == Xamarin.Forms.OSAppTheme.Dark)
SetStatusBarColor(Xamarin.Forms.Color.Blue, false);
else
SetStatusBarColor(Xamarin.Forms.Color.Yellow, true);
}
}

iOS 14 DatePicker renderer

I am having some difficulty figuring out how to properly update my existing custom renderer I have for my DatePicker on iOS to display the DatePicker with a different preferred style as is mentioned in this article here (albeit it is for swift) https://medium.com/better-programming/introducing-swifts-new-modern-date-picker-37bb5e0a106
My renderer is as follows:
public class BorderlessDatePickerRenderer : DatePickerRenderer
{
public override void LayoutSubviews()
{
base.LayoutSubviews();
var element = Element as BorderlessDatePicker;
Control.BorderStyle = UITextBorderStyle.None;
if (element.Date.Year == 1900) {
Control.Text = "";
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var element = Element as BorderlessDatePicker;
if (Control != null && element.Date.Year == 1900) {
Control.Text = "";
}
}
The BorderlessDatePicker itself is just an empty class that extends the DatePicker Xamarin.Forms control. The root of my woes is that I am not sure how to properly set a PreferredDatePickerStyle on my Control object given that Control is a UITextField under the hood instead of a UIDatePicker. In essence what I would like to do is instead of displaying the Date picker using the compact style that seems to be default for iOS 14, I would like for it to be displayed as wheels instead by being to do something like:
PreferredDatePickerStyle = UIDatePickerStyle.Wheels;
After some more researching, and browsing the xamarin github, I've come across this solution:
protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
{
base.OnElementChanged(e);
if(e.NewElement != null && this.Control != null)
{
try
{
if (UIDevice.CurrentDevice.CheckSystemVersion(14, 0))
{
UIDatePicker picker = (UIDatePicker)Control.InputView;
picker.PreferredDatePickerStyle = UIDatePickerStyle.Wheels;
}
} catch (Exception ex)
{
Log.Error(ex, "Failed to set PreferredDatePickerStyle to be UIDatePickerStyle.Wheels for iOS 14.0+");
}
}
}

Entry render not adding underline in iOS Xamarin Forms

I have an Entry in Xamarin Forms and for Android, its displayed with an underline by default and when I test it for iOS it's always with a rectangle like this:
And I want it to be like just like in Android:
I used Custom render where I specified that I want an underline but its still displayed in a rectangle. This code doesn't work:
class MyEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BorderStyle = UITextBorderStyle.Line;
}
}
}
I have solved this problem with the next code:
class MyEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BorderStyle = UITextBorderStyle.None;
//Create borders(bottom only)
CALayer border = new CALayer();
float width = 2.0f;
border.BorderColor = Color.FromHex(ColorConstants.BlueHex).ToCGColor();
border.Frame = new CGRect( 0, 40, 400, 2.0f);
border.BorderWidth = width;
Control.Layer.AddSublayer(border);
Control.Layer.MasksToBounds = true;
}
}
}
But still, don't understand why the previous code doesn't work, it should be simple as that. Any ideas?
https://developer.xamarin.com/api/type/UIKit.UITextBorderStyle/
The enum values for UITextBorderStyle are Bezel, Line, None, and RoundedRect. All of them (except for None) simply describe the style of the border that goes around the entire UITextView, so the Line value doesn't mean a single line, it means a solid line rectangle around the entire view, as opposed to a Bezel or rounded rectangle.
the implementation witch has solved the user question didn't work for me in these days, but this one has worked: https://social.msdn.microsoft.com/Forums/en-US/7ae2e5ad-3cc4-4761-ac39-06e8d8842cf4/entry-bottom-line?forum=xamarinforms
as jmoerdyk gently suggested this link explains how to write a custom renderer for iOS to render an entry with a bottom line instead of a complete border. the color of the line will change when the entry has focus.
below the piece of code with some null checks:
public class IOSBottomLineEntryRenderer : EntryRenderer
{
private CALayer _borderLayer;
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
Control.BorderStyle = UITextBorderStyle.None;
if (Element == null)
return;
DrawBorder(UIColor.FromRGB(156, 156, 156));
e.NewElement.Unfocused += (_, _) =>
{
DrawBorder(UIColor.FromRGB(156, 156, 156));
};
e.NewElement.Focused += (_, _) =>
{
DrawBorder(UIColor.FromRGB(245, 0, 47));
};
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control == null)
return;
if (Element == null)
return;
DrawBorder(UIColor.FromRGB(156, 156, 156));
}
public override CGRect Frame
{
get { return base.Frame; }
set
{
base.Frame = value;
if (Control == null)
return;
if (Element == null)
return;
DrawBorder(UIColor.FromRGB(156, 156, 156));
}
}
private void DrawBorder(UIColor borderColor)
{
if (Frame.Height <= 0 || Frame.Width <= 0)
return;
if (_borderLayer == null)
{
_borderLayer = new CALayer
{
MasksToBounds = true,
Frame = new CGRect(0f, Frame.Height - 1, Frame.Width, 1f),
BorderColor = borderColor.CGColor,
BorderWidth = 1.0f
};
Control.Layer.AddSublayer(_borderLayer);
Control.BorderStyle = UITextBorderStyle.None;
}
else
{
_borderLayer.BorderColor = borderColor.CGColor;
_borderLayer.Frame = new CGRect(0f, Frame.Height - 1, Frame.Width, 1f);
}
}
}

Navigation drawer backstack, how to get the actionbar title to change with fragment on back click

so i managed to get a stock navigation drawer to work and the action bar title to change with the fragments selected. I've also managed to get the backstack working easy peasy.
What i can't figure out how to do is get the action bar title to change back with the back click. google documentations says to add a onBackStackChangedListener:
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// Update your UI here.
}
});
but i'm at a lost where to place it? they say when i .commit to changes so i assumed it was placed after
if (id == R.id.nav_spatial_awareness) {
setTitle("Spatial Awareness");
SpatialAwareness spatialAwarenessFragment = new SpatialAwareness();
android.support.v4.app.FragmentManager spatialAwarenessManager = getSupportFragmentManager();
spatialAwarenessManager.beginTransaction()
.addToBackStack(null)
.replace(R.id.main_content_layout, spatialAwarenessFragment, spatialAwarenessFragment.getTag())
.commit();
but that didn't work, this is what i tried and all i get is red squigglies
if (id == R.id.nav_spatial_awareness) {
setTitle("Spatial Awareness");
SpatialAwareness spatialAwarenessFragment = new SpatialAwareness();
android.support.v4.app.FragmentManager spatialAwarenessManager = getSupportFragmentManager();
spatialAwarenessManager.beginTransaction()
.addToBackStack(null)
.replace(R.id.main_content_layout, spatialAwarenessFragment, spatialAwarenessFragment.getTag())
.commit();
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
setTitle("Spatial Awareness");
}
});
please help me noob
so i tried this
if (id == R.id.nav_spatial_awareness) {
setTitle("Spatial Awareness");
final SpatialAwareness spatialAwarenessFragment = new SpatialAwareness();
android.support.v4.app.FragmentManager spatialAwarenessManager = getSupportFragmentManager();
spatialAwarenessManager.beginTransaction()
.addToBackStack(null)
.replace(R.id.main_content_layout, spatialAwarenessFragment, "spatialAwarenessFragmentTag")
.commit();
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
android.app.Fragment currentBackStackFragment = getFragmentManager().findFragmentByTag("spatialAwarenessFragmentTag");
if(currentBackStackFragment instanceof SpatialAwareness){
setTitle("Spatial");
}
}
});
i gave my fragment a tag and then tried matching the instance and then changing the title, still no good :(
Yes, the solution as mentioned here is to add a FragmentManager.OnBackStackChangedListener to your activity's FragmentManager.
Here is an example from a project I worked on:
(I have a navigation drawer with 7 Fragments and the OverviewFragment is the initial one that opens when the MainActivity opens)
My MainActivity:
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, FragmentManager.OnBackStackChangedListener {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
if (savedInstanceState == null) {
// open the default fragment
OverviewFragment fragment = new OverviewFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment).commit();
setTitle(R.string.title_fragment_overview);
}
getSupportFragmentManager().addOnBackStackChangedListener(this);
}
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
Fragment fragment = null;
switch (item.getItemId()) {
case R.id.nav_overview:
fragment = new OverviewFragment();
break;
case R.id.nav_schedule:
fragment = new ScheduleFragment();
break;
case R.id.nav_all_tasks:
fragment = new AllTasksFragment();
break;
case R.id.nav_announcements:
fragment = new AnnouncementFragment();
break;
case R.id.nav_my_courses:
fragment = new MyCoursesFragment();
break;
case R.id.nav_map:
fragment = new MapFragment();
break;
case R.id.nav_settings:
fragment = new SettingsFragment();
break;
default:
fragment = new OverviewFragment();
}
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, fragment)
.addToBackStack(null)
.commit();
drawer.closeDrawer(GravityCompat.START);
return true;
}
#Override
public void onBackStackChanged() {
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment currentFragment =
fragmentManager.findFragmentById(R.id.fragment_container);
if (currentFragment instanceof OverviewFragment) {
setTitle(R.string.title_fragment_overview);
navigationView.getMenu().findItem(R.id.nav_overview).setChecked(true);
}
else if (currentFragment instanceof ScheduleFragment) {
setTitle(R.string.title_fragment_schedule);
navigationView.getMenu().findItem(R.id.nav_schedule).setChecked(true);
}
else if (currentFragment instanceof AllTasksFragment) {
setTitle(R.string.title_fragment_all_tasks);
navigationView.getMenu().findItem(R.id.nav_all_tasks).setChecked(true);
}
else if (currentFragment instanceof MyCoursesFragment) {
setTitle(R.string.title_fragment_my_courses);
navigationView.getMenu().findItem(R.id.nav_my_courses).setChecked(true);
}
else if (currentFragment instanceof AnnouncementFragment) {
setTitle(R.string.title_fragment_announcements);
navigationView.getMenu().findItem(R.id.nav_announcements).setChecked(true);
}
else if (currentFragment instanceof MapFragment) {
setTitle(R.string.title_fragment_map);
navigationView.getMenu().findItem(R.id.nav_map).setChecked(true);
}
else if (currentFragment instanceof SettingsFragment) {
setTitle(R.string.title_fragment_settings);
navigationView.getMenu().findItem(R.id.nav_settings).setChecked(true);
}
}
}
Here's the entire project: https://github.com/FCI-E-campus/fci-e-campus-android

Resources