kamulog

xamarin.formsのネタなど

AiForms.EffectsのAddCommandをテコ入れしてもらいタップしたときにSEを鳴らせるようになりました。ついでにCanExecute判定も追加。

ゆ〜かさんよりPullRequestを頂き、AddCommandでタップしたときにシステムサウンドを鳴らせるようになりました。
他にもcsprojがおかしなことになっているのを修正して頂きました。
ゆ〜かさん、本当にありがとうございました!!
(プロパティ名は他と合わせたものに変更させていただきました<( *)>)

ついでにIssuesに上がっていたCanExecute判定をしていない手抜き実装をちゃんと判定するように変更しました。

リポジトリ

github.com

nuget

www.nuget.org

Install-Package AiForms.Effects

nuget経由で使う場合はiOSのAppDelegate.csの global::Xamarin.Forms.Forms.Init(); の後に AiForms.Effects.iOS.Effects.Init(); の記述が必要です。

追加したパラメータ

  • EnableSound
    • タップした時にシステムサウンドを再生するかどうか(デフォルト false)
    • サウンドの種類は固定です。変更する方法は後述。
    • またAndroidの場合、サウンドに対応したViewのみ再生されます。
  • SyncCanExecute
    • CommandのCanExcecuteとFormsのViewのIsEnabledを同期するかどうか(デフォルト false)
    • trueの場合、CanExecuteがfalseの間は対象のViewのIsEnabledがfalseになり、ViewにOpacityが設定され見た目がDisabledっぽくなります。
    • falseの場合でもCanExecuteの判定は行われます。

サウンドの変更方法

単純にPlatformのクラスでstaticで持たせるようにしたので、もし変更したい場合は以下のようにします。

iOS

AppDelegate

public override bool FinishedLaunching(UIApplication app, NSDictionary options) {
    global::Xamarin.Forms.Forms.Init();

    AiForms.Effects.iOS.Effects.Init();
    //here specify sound number
    AiForms.Effects.iOS.AddCommandPlatformEffect.PlaySoundNo = 1104;
    ...
}

Android

MainActivity

protected override void OnCreate(Bundle bundle) {
    
    base.OnCreate(bundle);
    ...
    
    global::Xamarin.Forms.Forms.Init(this, bundle);
    
    //here specify SE
    AiForms.Effects.Droid.AddCommandPlatformEffect.PlaySoundEffect = Android.Media.SoundEffect.Spacebar;
    
    ...
}

コードのとおり、値を丸ごと変えるだけなので、個別に音を変更するといったことはできません。

CanExecuteまわりの挙動

SyncCanExecuteがfalseでもtrueでも、ICommandのCanExecuteがfalseだったときはExecuteしません。

SyncCanExecuteがtrueの場合は以下のようになります。

  • CommandもLongCommandもnull
    • 変化しない
  • CommandがnullではなくてLongCommandがnull
    • CommandのCanExecuteがfalseでIsEnalbedもfalse
  • CommandがnullでLongCommandがnullではない
    • LongCommandのCanExecuteがfalseでIsEnalbedもfalse
  • CommandもLongCommandもnullではない
    • CommandとLongCommandのCanExecuteがどちらもfalseの時のみIsEnableがfalse

同じviewに2つのコマンドが共存している形なので両方設定されている場合は、両方のCanExecuteがfalseの場合にのみDisabled状態にするようにしています。

SyncCanExecuteをtrueにした時の注意事項としては、PCL側でIsEnabledを設定していてもCanExecuteでの判定が勝つようになるので、その場合は意図しないタイミングでIsEnabledが変更されることになります。そういう場合は併用しないほうが良いです。

IsEnabled = false で見た目がそれっぽくなるのとそうでないのがある問題

一律Opacityを設定すると、もともとDisalbed状態っぽくなるものが、より薄くなって存在感がなくなりすぎるので、地道に型を見て、Disalbe状態にならないviewの場合のみOpacityを設定するようにしました。

Androidの例

bool DisableEffectCheck()
{
    if (Element is Label) {
        var label = Element as Label;
        return label.TextColor != Xamarin.Forms.Color.Default ||
                    label.BackgroundColor != Xamarin.Forms.Color.Default;
    }

    return DisableEffectTargetType.Any(x => x == Element.GetType());
}

AndroidのLabelはTextColorとBackgroundColorがデフォルトの場合にのみDisable状態になるので、特殊な判定をして、その後に型のListに一致するかどうかの判定をしています。

CanExecuteでDisabled状態にする処理は最初はNative側でやろうとしてたんですが、Layoutの場合は子にも同じ処理しないとで、ややこしすぎたんで、その辺はFormsのIsEnabledに任せることにしました。困った時はFormsにお返しするというのも大事だと思います。

終わりに

いつのまにかTapGestureRecognizerにCommandが設定できるようになって、それ以降このEffectはあんまり使わなくなってなんですが、今回のテコ入れで延命できたかなと思います。 PullRequestは本当にありがたいですね。