Xamarin.FormsでListViewコントロールを使用するには?
BuildInsiderで連載されている「Xamarin逆引きTips」に寄稿させて頂きました。
使用したコードは、下記にあります。
furuya02/XamarinTips.ListViewSample · GitHub
Xamarin.Forms 機械翻訳
今回は、機械翻訳を実装してみました。
WebAPIでかなり高速な翻訳や読み上げが可能なので、色々面白そうです。
図は、Webページの英文をコピペして翻訳しているようす。
Microsoft Translator
現時点での翻訳サービスは、googleとMicrosoftの2択のようです。googleの方は有料になってしまったようなので、今回は、Microsoftのサービスを使用しました。
Microsoftのオンライン機械翻訳サービスはエンドユーザー向けに「Bing Translator」という名前で提供されており、そのバックで動作しているのが「Microsoft Translator」のようです。
「Microsoft Translator」は、1 ヶ月あたり200万文字まで無料で利用可能です。
利用手順
「Microsoft Translator」の利用するための手順は、下記のとおりです。
Live ID 取得
Windows Live IDが必要です。既に取得している場合は、この作業は必要ありません。サブスクリプション登録
Microsoft Translator データに対して、サブスクリプション登録をします。Microsoft Translator | Microsoft Azure Marketplace
アプリケーション登録
https://datamarket.azure.com/developer/applications/
「顧客の秘密(シークレットキー)」は自由に決定することができるようです。また、クライアントアプリを作成する場合「リダイレクトURL」には、無効なURLを指定します。
ここで設定した、「クライアントID(ClientId)」と「顧客の秘密(ClientSecret)」が、実装時に必要になります。
翻訳クラスの実装
「Microsoft Translator」には、読み上げや、辞書引きなど、いくつかの機能がありますが、今回は「テキスト翻訳」のみを実装しています。
Xamarin.FormsのPCLから利用するために、HTTP通信用のHttpClientとJSON処理用のJson.NETをインストールしました。
※HttpClientはデフォルト非同期対応で、非常にシンプルに記述できるので惚れます。
PM> Install-Package Microsoft.Net.Http
PM> Install-Package Newtonsoft.Json
実装したコードは、下記のとおりです。ざっくり言ってしまうと、アクセストークを取得して、WebAPIをコールしているだけです。
詳しくは、下記のリンクをご参照ください。(最後の方にサンプルコードもあります)
https://msdn.microsoft.com/ja-jp/library/ff512421.aspx
class Translator { private const string ClientId = "TranslatorSample001"; private const string ClientSecret = "iYbcPHxxxxxxxxxxxx7IVE="; private string _token; private async Task<bool> InitializeToken() { if (_token == null) { var client = new HttpClient(); const string url = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13"; var content = new FormUrlEncodedContent(new Dictionary<string, string> { {"client_id", ClientId}, {"client_secret", ClientSecret}, {"scope", "http://api.microsofttranslator.com"}, {"grant_type", "client_credentials"}, }); var response = await client.PostAsync(url, content); //JSON形式のレスポンスからaccess_toneを取得する var adm = JsonConvert.DeserializeObject<AdmAccessToken>(await response.Content.ReadAsStringAsync()); _token = adm.access_token; return true; } return false; } //JSON形式のレスポンスをデシリアライズするためのクラス [DataContract] public class AdmAccessToken { [DataMember] public string access_token { get; set; } //access_token以外は、取得しないので省略 } public async Task<string> Get(string str) { //初回のみaccess_tokenを取得する if (_token == null) { await InitializeToken(); } var client = new HttpClient(); var url = string.Format("http://api.microsofttranslator.com/v2/Http.svc/Translate?text={0}&to=ja" , WebUtility.UrlEncode(str)); client.DefaultRequestHeaders.Add("Authorization", "Bearer " + _token); var response = await client.GetStringAsync(url); //XML解釈 var doc = XDocument.Parse(response); return doc.Root.FirstNode.ToString(); } }
メイン画面
先の翻訳機能を使用するメインの画面は、入力欄(Editor)と出力欄(Label)と翻訳開始のボタン(Button)を配置しただけです。
namespace Translator { public class App : Application { public App() { MainPage = new MyPage(); } // ・・・省略・・・ } internal class MyPage : ContentPage { private readonly Translator _translator = new Translator(); public MyPage() { //翻訳前のテキスト入力 var editor = new Editor { HeightRequest = 200, Text = "Hello world" }; //WindowsPhoneの場合だけ、フォーカスが無いときに文字が見えないので背景色を指定する if (Device.OS == TargetPlatform.WinPhone) { editor.BackgroundColor = Color.Teal; } //翻訳後のテキスト表示 var label = new Label(); //翻訳開始のボタン var button = new Button { Text = "Translate", Command = new Command(async () => { label.Text = await _translator.Get(editor.Text); }) }; Content = new StackLayout() { Padding = new Thickness(10, Device.OnPlatform(30, 10, 10), 10, 0), Children = {editor, button, label} }; } } }
図は、WindowsPhoneとAndroidで動作しているようすです。
コードはGithubに置きました。
furuya02/Xamarin.Forms.Translator · GitHub
参考資料
Microsoft Translator - Web localization
第38回 使ってみようMicrosoft Translator:使ってみよう! Windows Live SDK/API|gihyo.jp … 技術評論社
Translate Method
Xamarin.Formsでツールバーアイテムによるメニューを設置するには?
BuildInsiderで連載されている「Xamarin逆引きTips」に寄稿させて頂きました。
Xamarin.Formsでツールバーアイテムによるメニューを設置するには? - Build Insider
使用したコードは、下記にあります。
furuya02/XamarinTips.ToolbarItemSample · GitHub
Xamarin.Formsでタッチイベントを処理するには?(iOS/Androidの各種ジェスチャー対応)
BuildInsiderで連載されている「Xamarin逆引きTips」に寄稿させて頂きました。
使用したコードは、下記にあります。
furuya02/XamarinTips.GestureSample · GitHub
Xamarin.Forms ビイヘイビア
Behaviors
Xamarin.Forms 1.3において新しく追加された機能の一つに、ビイヘイビアがありあます。ビイヘイビアを使用すると、UIコントロールをサブクラス化することなく、そのコントロールに機能を追加することができます。
ビヘイビアを実装するには、Behaviorクラスを継承した独自のビヘイビアクラスを定義します。この時、Behaviorクラスには対象とするUIコントロール(ここではEntry)の型を指定します。
ビヘイビアクラスの中では、OnAttachedTo及びOnDetachingFromをオーバーライドし、そこで、追加する機能メソッド(サンプルではCheckEmail)を追加・削除するだけです。
最後に、このビヘイビアクラス(EmailBehavior)を、対象となるEntryのBehaviorsプロパティに追加しています。
public class App : Application{ public App(){ MainPage = new MyPage(); } //・・・省略・・・ } class MyPage : ContentPage{ public MyPage(){ var button = new Button{ Text = "Send", }; var entry = new Entry{ WidthRequest = 200, Placeholder = "user@exsample.com", Behaviors = { new EmailBehavior() }//ビヘイビアの追加 }; Content = new StackLayout{ Padding = 50, Children = { entry, button } }; } } public class EmailBehavior : Behavior<Entry> { private readonly Regex _regex = new Regex(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*"); protected override void OnAttachedTo(Entry bindable) { //ハンドラの追加 bindable.TextChanged += CheckEmail; } protected override void OnDetachingFrom(Entry bindable) { //ハンドラの削除 bindable.TextChanged -= CheckEmail; } private void CheckEmail(object sender, TextChangedEventArgs e) { //文字列がEmailとして正しいかどうかのチェック var m = _regex.Match(e.NewTextValue); //正かどうかによってテキストの色を変化させる ((Entry) sender).TextColor = m.Success ? Color.Default : Color.Red; } }
別コントロールとの連携
ビヘイビアクラスでの挙動追加は、対象コントロールに閉じた動作になります。
追加機能(ここでは検証結果)を別のコントロール(ここではSendボタン)に連動させるには、ビヘイビアクラス側で、外部に状態を伝える何かが必要です。
次のコードは、ビヘイビアクラスにIsValidというプロパティを追加して、それにより、Sendボタンの有効無効を制御しています。
//・・・省略・・・ class MyPage : ContentPage { public MyPage() { var button = new Button { Text = "Send", IsEnabled = false //デフォルトで無効にした }; //ビヘイビアクラスを変数として保持した var emailBehavior = new EmailBehavior(); var entry = new Entry { WidthRequest = 200, Placeholder = "user@exsample.com", Behaviors = {emailBehavior} }; entry.TextChanged += (sender, args) => { button.IsEnabled = emailBehavior.IsValid; //ビヘイビアクラスの検証結果を使用してボタンを状態を変更する }; //・・・省略・・・ } } public class EmailBehavior : Behavior<Entry> { //クラス外から検証結果が分かるようにプロパティを追加 public bool IsValid { get; private set; } //・・・省略・・・ private void CheckEmail(object sender, TextChangedEventArgs e) { //文字列がEmailとして正しいかどうかのチェック var m = _regex.Match(e.NewTextValue); //正かどうかによってテキストの色を変化させる ((Entry) sender).TextColor = m.Success ? Color.Default : Color.Red; IsValid = m.Success;//プロパティ値をセット } }
Xamarin.Forms ListViewのコンテキストアクション
Context Actions
Xamarin.Forms 1.3において、ListViewにコンテキストアクションの機能が追加されました。Android及びWindowsPhoneにおいては、長押し、iOSの場合は、左にスライドすることによって、メニューが出現します。
iOS | Android | WindowsPhone |
※外観は、プラットフォームごとかなり違います。
public class App : Application{ public App(){ MainPage = new MyPage(); } //・・・省略・・・ } class MyPage : ContentPage { public MyPage() { var listView = new ListView() { ItemsSource = Enumerable.Range(0,10).Select(n=>"item-"+n), ItemTemplate = new DataTemplate(() => new MyCell(this)) }; Padding = new Thickness(0,Device.OnPlatform(20,0,0),0,0); Content = listView; } public async void Action(string str) { await DisplayAlert("Action", str, "OK"); } }
セルのテンプレート
Context Actionsを使用するには、必ず、DataTemplateを自前で用意する必要があります。ViewCellを継承したテンプレートを作成し、ContextActionsプロパティにMenuItemを追加することでメニューが生成されます。
MenuItemは、Text若しくはIconが表示可能なように見えますが、今のところIconの表示はできませんでした。
iOSにおいて、スライドした部分の背景色は固定(グレー)であり、IsDestructiveをtrueにした時、赤色になります。
ItemTemplate = new DataTemplate(typedef(MyCell))
class MyCell : ViewCell { public MyCell(MyPage myPage) { //テキスト表示用のラベル var label = new Label { VerticalOptions = LayoutOptions.CenterAndExpand }; label.SetBinding(Label.TextProperty,new Binding("."));//ItemSourceのテキストを表示する //1つ目のメニュー var deleteAction = new MenuItem { Text = "Delete", Command = new Command( p=>myPage.Action("delete "+ p)),//Commandにセットすることでイベントを取得 IsDestructive = true, //背景赤色 }; deleteAction.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));//コマンド実行時のパラメータに表示テキストを使用する ContextActions.Add(deleteAction); //2つ目のメニュー var moreAction = new MenuItem{ Text = "More", }; moreAction.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));//コマンド実行時のパラメータに表示テキストを使用する moreAction.Clicked += (s, e) => { //Clickedによりイベントを取得 myPage.Action("more " + ((MenuItem)s).CommandParameter); }; ContextActions.Add(moreAction); View = new StackLayout{ Padding = 10, Children = {label} }; } }
アクション
MenuItemのCommandプロパティ、若しくは、Clickedイベントを処理することで、アクションが記述できます。
先のサンプルコードでは、1つ目が、Commandによるもの、そして2つ目が、Clickedを使用した場合の例になっています。
Wi-Fi付きのArduino互換機 Spark Core (その5) Xamarin.Formsクライアントによるマトリックス操作
1 ダイナミック点灯
ダイナミック点灯とは、1行ごとに点灯の処理を行い、それを高速で繰り返すことで、残像で全行が表示されているように見せることです。
ここで、1行の表示を処理しているのは、void disp(int r,String dat) です。第1パラメータは、表示対象の行です。そして、第2パラメータは、列のOn/OFFを指定するデータです。例えば、datに"f0"を指定すると、ビットに直して"11110000"なので、左から●●●●○○○○のように点灯します。
以下は、Arduinoのコードですが、このdisp()をloop()の中で、全行分繰り返しています。
int row[8]={A7,A6,A5,A4,A3,A2,A1,A0}; int col[8]={D7,D6,D5,D4,D3,D2,D1,D0}; //点灯対象の文字列データ String datStr="f0f0f0f08f8f8f8f"; void setup() { for(int i=0;i<8;i++){ //D0..D7の初期化 pinMode(col[i], OUTPUT); digitalWrite(col[i],1);//HIGHで初期化 //A0..A7の初期化 pinMode(row[i], OUTPUT); digitalWrite(row[i],0); //LOWで初期化 } } void loop() { for(int i=0;i<8;i++){ //2文字づつ切り取って、列ごと点灯する disp(i,datStr.substring(i*2,i*2 + 2)); } } //文字列(2文字)を数値化する //"ff" -> 0xff "1c" -> 0x1c int toHex(String str){ char c = str[0]; int h = ('a' <= c && c <= 'f') ? (c - 'a') + 10 : (c - '0'); c = str[1]; int l = ('a' <= c && c <= 'f') ? (c - 'a') + 10 : (c - '0'); return (h<<4 | l)&0xff; } void disp(int r,String dat){ int d = toHex(dat);//文字列の数値化 digitalWrite(row[r],0); //当該行の消灯 for(int c=0;c<8;c++){ int sw = 1;//HIGHで初期化 if (((d >> c) & 1)==1) { sw = 0;//LOWで点灯 } digitalWrite(col[7-c],sw); } digitalWrite(row[r],1); //当該行の点灯 delay(3); digitalWrite(row[r],0); //当該行の消灯 }
元となるデータが、String datStr="f0f0f0f08f8f8f8f" であるため、LEDは次のように表示されます。
●●●●○○○○ <- f0
●●●●○○○○ <- f0
●●●●○○○○ <- f0
●●●●○○○○ <- f0
●○○○●●●● <- 8f
●○○○●●●● <- 8f
●○○○●●●● <- 8f
●○○○●●●● <- 8f
実際に点灯しているようすです。
2 WebAPIの公開
続いて、(その2) で紹介したWebAPIを使用して、この元データであるdatStrを外部から変更できるようにします。追加したArduinoのコードは、下記のとおりです。
int func(String sw); //WebAPIの型宣 void setup() { //・・・省略・・・ Spark.function("func", func); //WebAPIの公開 } //公開されたAPI int func(String s){ str = s; return 1; }
3 LEDボタン
WebAPIをコールするクライアントは、iPhone6用として決め打ちなのですが、今後の拡張が容易なように、Xamarin.Formsを使用しました。
クライアントでは、LED(赤い丸)をタッチすると、LEDがON/OFFするようになっていますが、この赤い丸(LEDクラス)は、BoxViewを継承して作成しました。
LEDクラスは、レンダラーを記述することで、プラットホーム側で、丸く描画し、併せて影をおきました。
BoxViewを継承したLEDクラスは次のとおりです。
public class Led:BoxView{ bool _sw; public bool Sw { set { _sw = value; Color = _sw ? Color.FromRgba(224, 33, 0, 255) : Color.FromRgba(255, 100, 100, 100); OnPropertyChanged(propertyName: "Color"); } get { return _sw; } } public Led() { WidthRequest = 45; HeightRequest = 45; Sw = false; } }
LEDクラスには、Swというプロパティがあり、これがON/OFFを表現しています。
Swプロパティをセットした時は、同時にColorプロパティが変更され、レンダラークラスにも通知されるようになっています。
レンダラー側のコードは、下記のとおりです。
[assembly: ExportRenderer(typeof(Led), typeof(LedRenderer))] namespace MatrixLed.iOS { class LedRenderer : BoxRenderer{ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) { base.OnElementPropertyChanged(sender, e); if (e.PropertyName == "Color"){ SetNeedsDisplay(); } } public override void Draw(CGRect rect) { //base.Draw(rect); var led = (Led)Element; using (var context = UIGraphics.GetCurrentContext()){ var shadowSize = 3; var blur = shadowSize; var radius = 20; context.SetFillColor(led.Color.ToCGColor()); var bounds = Bounds.Inset(2,2); context.AddPath(CGPath.FromRoundedRect(bounds, radius, radius)); context.SetShadow(new SizeF(shadowSize, shadowSize), blur); context.DrawPath(CGPathDrawingMode.Fill); } } } }
デフォルトの描画を無効とし、「影」及び「丸」を自前で描画しています。
また、再描画は、プロパティ「Color」が変更されたときに、 SetNeedsDisplay()をコールし、間接的にDraw()を呼び出しています。
4 メイン処理
続いて、メインの処理です。
LEDクラスは、16個生成しAbsoluteLayoutレイアウトで配置しています。また、タッチの検出は、TapGestureRecognizerクラスを LEDクラス(BoxView)のGestureRecognizersに追加することで行っています。
public class App : Application { public App() { MainPage = new MyPage(); } //・・・省略・・・ } class MyPage : ContentPage { public MyPage() { var ar = new List<Led>(); BackgroundImage = "back.png";//背景画像 var absoluteLayout = new AbsoluteLayout(); //送信文字の表示用ラベル var label = new Label{ Text="0x0000000000000000", FontFamily = "HelveticaNeue-Thin", FontSize = 30, TextColor = Color.White, BackgroundColor = Color.FromRgba(0,0,0,70), WidthRequest = 375,//iPhone6 HeightRequest = 100, XAlign = TextAlignment.Center, YAlign = TextAlignment.Center }; absoluteLayout.Children.Add(label, new Point(0, 667-90)); //タッチイベントの処理 var gr = new TapGestureRecognizer(); gr.Tapped += (s, e) => { var led = (Led) s; //ON/OFFの反転 led.Sw = !led.Sw; //LEDのON/OFFデータをを文字列として取得する var dataStr = GetDataStr(ar); //ラベルへの表示 label.Text = "0x" + dataStr; //WebAPIのコール Api(dataStr); }; //LEDの生成と表示 const int top = 100; const int left = 3; const int width = 46; for (var y = 0; y < 8; y++){ for (var x = 0; x < 8; x++) { var led = new Led(); ar.Add(led); led.GestureRecognizers.Add(gr); //タッチイベントの検知を追加 absoluteLayout.Children.Add(led, new Point(left + x*width, top + y*width)); } } Content = absoluteLayout; } // ・・・省略・・・ }
5 WebAPI
最後にWebAPIをコールするコードです。
トリガーは、タッチ時ですが、コールの要領は、(その2)で紹介したものと同じです。
//タッチイベントの処理 gr.Tapped += (s, e) => { var led = (Led) s; //LEDのON/OFFデータをを文字列として取得する var dataStr = GetDataStr(ar); //WebAPIのコール Api(dataStr); }; //WebAPIのコール async void Api(String daataStr){ var httpClient = new HttpClient(); const string deviceId = "53ff6xxxxxxxxxxxxxx2567";//デバイスID const string accessToken = "50179axxxxxxxxxxxxxxxxxxxxxxxxxxx5413e";//アクセストークン const string funcName = "func";//WebAPIのファンクション名 var data = new FormUrlEncodedContent(new Dictionary<string, string>{ {"access_token", accessToken}, {"params", daataStr} }); //URLはデバイスIDとファンクション名で決まる var url = string.Format("https://api.spark.io/v1/devices/{0}/{1}", deviceId, funcName); await httpClient.PostAsync(url, data); } //LEDのON/OFFデータをを文字列として取得する private string GetDataStr(List<Led> ar) { var sb = new StringBuilder(); for (var r = 0; r < 8; r++) { var dat = 0; for (var c = 0; c < 8; c++) { var index = r*8 + c; if (ar[index].Sw) { dat |= 1 << (7 - c); } } sb.Append(Convert.ToString(dat, 16)); } return sb.ToString(); } }
Githubにおいた全コードです。
furuya02/Xamarin.Forms.MatrixLed · GitHub
iPhoneで操作しているようすです。
Wi-Fi付きのArduino互換機 Spark Core (その1) - SIN@SAPPOROWORKSの覚書
Wi-Fi付きのArduino互換機 Spark Core (その2) - SIN@SAPPOROWORKSの覚書