Reputation: 223
I am creating a custom control that has a custom keyboard. I have faced several issues.
I am using Visual Studio Community 2019, and I'm using Android_Accelerated_x86_Oreo (Android 8.1 API-27) as my Android emulator.
In summary,
I need to style Entry differently, hence I create a derived class from it, called TextBoxEntry.
I create all the controls programmatically, in MainPage.cs, hence, MainPage.xaml is blank (well, almost).
Android specific codes, namely the renderer, are crammed into MainActivity.cs.
And lastly, the custom keyboard, is defined under Resources/xml/keyboard.xml.
I have not been able to work out these issues (3 of 5 solved):
Why does the custom keyboard of the custom renderer appear underneath the controls?
When the keyboard appears, I need to get its height, so that I could modify the scrollview dimension accordingly. Unfortunately, doing this inside ControlOnFocusChanged() [EditText.FocusChange event handler] just doesn't work, because at that point in time, the keyboard still isn't displayed yet, hence, its height is 0. Or should I do it in some other event? Should use ViewTreeObserver.IOnGlobalLayoutListener instead.
The keys in the keyboard are not pressable. Sometimes, the keyboard goes into hiding. I guess when I press the keys, the actual entry loses focus. Anyway, where do I actually go wrong?
The last column of the scrollview, as well as the the last row, have their TextBoxEntries clipped off. Again, I couldn't figure out what goes wrong. THIS IS SOLVED BY SETTING MARGIN TO 1.
Inside ControlOnFocusChange(), a_height gives 1280, while b_height gives 342. The scrollview takes more than half of the entire screen, but its height is far from half of the entire screen. Are these two shown in different units? According to Find our the default height of the controls in Xamarin Forms, it seems that both Heights use different units... OK, this can be solved by some conversion.
So, here is all the relevant files' content:
MainPage.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CustomKeyboard.MainPage"/>
MainPage.xaml.cs:
using Xamarin.Forms;
namespace CustomKeyboard
{
public class TextBoxEntry : Entry
{
public ScrollView ScrollView { get; set; } = null; // So that I could easily get its reference and work on it...
}
public partial class MainPage : ContentPage
{
AbsoluteLayout layout = null;
ScrollView scrollview = null;
Grid grid = null;
public MainPage()
{
InitializeComponent();
BuildControls();
BuildTextBoxes();
}
private void BuildControls()
{
layout = new AbsoluteLayout();
scrollview = new ScrollView();
grid = new Grid();
layout.Children.Add(scrollview, new Rectangle(0.5d, 0d, 0.95d, 0.85d), AbsoluteLayoutFlags.All);
scrollview.BackgroundColor = Color.Yellow;
scrollview.Padding = 5;
scrollview.Content = grid;
Content = layout;
}
private void BuildTextBoxes()
{
for (var i = 0; i < 50; ++i)
{
if (i % 5 == 0)
{
var l = new Label()
{
Text = (i / 5).ToString(),
HorizontalTextAlignment = TextAlignment.Center,
VerticalTextAlignment = TextAlignment.Center,
};
grid.Children.Add(l, 0, i / 5);
}
else
{
var r = i / 5;
var c = i % 5;
var e = new TextBoxEntry();
e.Margin = 1; // THIS SOLVES ISSUE 4
e.ScrollView = scrollview;
grid.Children.Add(e, c, r);
}
}
}
}
}
MainActivity.cs:
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.InputMethodServices;
using Android.OS;
using Android.Support.Design.Widget;
using Android.Views;
using Android.Widget;
using Java.Lang;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Essentials;
using CustomKeyboard;
using CustomKeyboard.Droid;
[assembly: ExportRenderer(typeof(TextBoxEntry), typeof(TextBoxEntryRenderer))]
namespace CustomKeyboard.Droid
{
[Activity(
Label = "CustomKeyboard",
Icon = "@mipmap/icon",
Theme = "@style/MainTheme",
MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation
)]
public class MainActivity : FormsAppCompatActivity
{
internal static MainActivity Instance { get; private set; }
protected override void OnCreate(Bundle savedInstanceState)
{
Instance = this;
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
}
public class TextBoxEntryRenderer : ViewRenderer<TextBoxEntry, TextInputLayout>
{
public KeyboardView mKeyboardView;
ViewGroup activityRootView;
protected EditText editText => Control.EditText;
public TextBoxEntryRenderer(Context context) : base(context) { }
protected override TextInputLayout CreateNativeControl()
{
#region Add keyboard
var activity = MainActivity.Instance as Activity;
activity.Window.SetSoftInputMode(SoftInput.StateAlwaysHidden);
var rootView = activity.Window.DecorView.FindViewById(Android.Resource.Id.Content);
activityRootView = ((ViewGroup)rootView).GetChildAt(0) as ViewGroup;
mKeyboardView = new KeyboardView(MainActivity.Instance, null);
mKeyboardView.PreviewEnabled = false; //Removes magnified key popups on key press
mKeyboardView.Visibility = ViewStates.Gone;
mKeyboardView.Keyboard = new Android.InputMethodServices.Keyboard(Context, Resource.Xml.keyboard);
mKeyboardView.Key += (sender, e) =>
{
long eventTime = JavaSystem.CurrentTimeMillis();
DispatchKeyEvent(new KeyEvent(eventTime, eventTime, KeyEventActions.Down, e.PrimaryCode, 0, 0, 0, 0, KeyEventFlags.SoftKeyboard | KeyEventFlags.KeepTouchMode));
Task.Delay(1);
};
Android.Widget.RelativeLayout.LayoutParams layoutParams =
new Android.Widget.RelativeLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.WrapContent); // or wrap_content
layoutParams.AddRule(LayoutRules.AlignParentBottom);
activityRootView.AddView(mKeyboardView, layoutParams);
#endregion
var _editText = new EditText(Context);
_editText.SetTextColor(Android.Graphics.Color.Black);
var textInputLayout = new TextInputLayout(Context);
textInputLayout.AddView(_editText);
return textInputLayout;
}
protected override void OnElementChanged(ElementChangedEventArgs<TextBoxEntry> e)
{
base.OnElementChanged(e);
if (e.OldElement != null && Control != null) editText.FocusChange -= ControlOnFocusChange;
if (e.NewElement != null)
{
SetNativeControl(CreateNativeControl());
editText.ShowSoftInputOnFocus = false;
editText.FocusChange += ControlOnFocusChange;
editText.SetPadding(20, 0, 20, 0);
editText.SetTextSize(Android.Util.ComplexUnitType.Px, 32);
editText.TextChanged += (source, args) => { };
}
if (Control != null)
{
GradientDrawable gd = new GradientDrawable();
gd.SetColor(Android.Graphics.Color.Transparent);
Control.SetBackground(gd);
editText.Background = null; // Remove underline.
var shape = new ShapeDrawable(new Android.Graphics.Drawables.Shapes.RectShape());
shape.Paint.Color = Xamarin.Forms.Color.Gray.ToAndroid();
shape.Paint.SetStyle(Paint.Style.Stroke);
Control.Background = shape;
}
}
private void ControlOnFocusChange(object sender, FocusChangeEventArgs args)
{
if (args.HasFocus)
{
editText.Post(() =>
{
editText.RequestFocus();
if (mKeyboardView.Visibility == ViewStates.Gone) mKeyboardView.Visibility = ViewStates.Visible;
// Wanna resize scrollview, but couldn't get necessary info...!
var deviceDisplayInfo = DeviceDisplay.MainDisplayInfo;
var a_height = deviceDisplayInfo.Height;
var b_height = Element.ScrollView.Height;
var c_height = mKeyboardView.Height;
});
}
else
{
// Hide keyboard
mKeyboardView.Visibility = ViewStates.Gone;
}
}
}
}
And lastly, Resources/xml/keyboard.xml:
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="25%p" android:horizontalGap="0px"
android:verticalGap="0px" android:keyHeight="27dip">
<Row>
<Key android:codes="8" android:keyLabel="1" android:keyHeight="54dip" android:keyEdgeFlags="left" />
<Key android:codes="9" android:keyLabel="2" android:keyHeight="54dip" />
<Key android:codes="10" android:keyLabel="3" android:keyHeight="54dip" />
<Key android:codes="81" android:keyLabel="+" android:keyHeight="81dip" android:keyEdgeFlags="right" />
</Row>
<Row></Row>
<Row>
<Key android:codes="11" android:keyLabel="4" android:keyHeight="54dip" android:keyEdgeFlags="left" />
<Key android:codes="12" android:keyLabel="5" android:keyHeight="54dip" />
<Key android:codes="13" android:keyLabel="6" android:keyHeight="54dip" />
</Row>
<Row>
<Key android:codes="66" android:keyLabel="RET" android:keyHeight="81dip" android:horizontalGap="75%p" android:keyEdgeFlags="right" />
</Row>
<Row>
<Key android:codes="14" android:keyLabel="7" android:keyHeight="54dip" android:keyEdgeFlags="left" />
<Key android:codes="15" android:keyLabel="8" android:keyHeight="54dip" />
<Key android:codes="16" android:keyLabel="9" android:keyHeight="54dip" />
</Row>
<Row></Row>
<Row>
<Key android:codes="17" android:keyLabel="*" android:keyHeight="54dip" android:keyEdgeFlags="left" />
<Key android:codes="7" android:keyLabel="0" android:keyHeight="54dip" />
<Key android:codes="18" android:keyLabel="#" android:keyHeight="54dip" />
<Key android:codes="67" android:keyLabel="DEL" android:keyHeight="54dip" android:keyEdgeFlags="right" />
</Row>
<Row></Row>
</Keyboard>
Upvotes: 0
Views: 717
Reputation: 10958
Why does the custom keyboard of the custom renderer appear underneath the controls?
Set the alignParentBottom
property to true to make our keyboard to be visible from the bottom side of the screen.
android:layout_alignParentBottom="true"
The keys in the keyboard are not pressable. Sometimes, the keyboard goes into hiding. I guess when I press the keys, the actual entry loses focus. Anyway, where do I actually go wrong?
Define a state selector for the two states our keys can have: normal (without pressing) and pressed with true or false.
Upvotes: 1