10yroの開発日記

株式会社10yro(トイロ)のエンジニアが書いています

【Android】ScrollViewでスクロールが表示されているかを判定する方法

こんばんは。

最近、「悪魔城ドラキュラx 血の輪廻」という横スクロールゲームをやったのですが、めちゃくちゃ難しかったです。

昔のゲームって鬼畜仕様多いですよね。

ってことでスクロールについての話題を。。

ScrollViewでスクロールが表示されているかを判定する方法

ScrollViewでスクロールが出ていた場合、メッセージ表示やレイアウト変更したいというケースが稀にあります。

例えば、下記のように文字追加を行いスクロールが出たら画面下部に「続きがあります」と表示したい場合など。。

方法としてはScrollViewとScrollViewの内側にいるLayoutViewの高さを比較し、LayoutViewの方が大きければスクロールが出ていると判断出来ます。

下の例だとscrollViewとscrollLayoutを比較。

f:id:toyo0110:20211110190658p:plain

具体的なソースはこちら

public class MainActivity extends AppCompatActivity {
    private TextView txtInfo;
    private ScrollView scrollView;
    private LinearLayout scrollLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final Button btnAdd = findViewById(R.id.btnAdd);
        final Button btnClear = findViewById(R.id.btnClear);
        final TextView textView = findViewById(R.id.textView1);
        txtInfo = findViewById(R.id.txtInfo);
        scrollView = findViewById(R.id.scrollView);
        scrollLayout = findViewById(R.id.scrollLayout);

        for (int i = 0; i < 10; i++) {
            textView.append("aaaaaa\n");
        }

        // scrollLayoutがの可視状態が変わった場合または、変更があったい場合に動く
        scrollLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                setInfo();
                // 初回描画時だけ判定したいのであれば、下記でイベントを削除する。
                //  scrollLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });

        btnAdd.setOnClickListener(v -> {
                    textView.append("aaaaaa\n");
                }
        );
        
        btnClear.setOnClickListener(v -> {
            textView.setText("");
        });
    }

    private void setInfo() {

       // ScrollViewとScrollViewの内側にいるLayoutViewの高さを比較
        if (scrollView.getHeight() <= scrollLayout.getHeight()) {
            txtInfo.setText("続きがあります。");
        } else {
            txtInfo.setText("");
        }
    }
}

ポイントとしては「.getHeight()で高さをとっていますが描画後でないと正しい値が取れません。

onCreatenの中で「.getHeight()」で高さを取得してもScrollViewで描画が完了してない為、高さが0で返ってきてしまいます。

文字列追記後も描画反映まで少しラグがあるため、文字追加前の高さが取れます。

描画後に確実に判定するには、ViewTreeObserver.OnGlobalLayoutListenerで可視状態及び変更を監視し、LayoutViewの状態が変わったタイミングで判定する必要があります。

C# WPFでOxyPlotを使って折れ線グラフを描画する

今回はC#WPFで、OxyPlotを使って折れ線グラフを描画したいと思います。
折れ線グラフの横軸に時間(0時~23時)、縦軸は0~10のランダムな値を設定します。

事前準備

WPFのプロジェクトを作成し、NuGetでOxyPlot.Wpfをインストールします。
.NET.Core 3.1を使います。

PlotModelの生成

今回はOxyPlotのPlotViewを使います。
MainWindow.xaml.csにOxyPlot.PlotModelのプロパティを定義し、このプロパティに折れ線グラフのデータを設定します。

プロパティを追加。

/// <summary>
/// PlotModel
/// </summary>
public OxyPlot.PlotModel PlotModel { get; set; } = new OxyPlot.PlotModel();

折れ線グラフのデータを作成し、PlotModelプロパティにセット。

横軸(時間)の定義

13行目~22行目で、横軸(時間)の定義をしてPlotModelのAxesに設定しています。

縦軸の定義

25行目~35行目で、縦軸の定義をしてPlotModelのAxesに設定しています。

グラフの線の定義

38行目~47行目で、グラフの線の定義をしてPlotModelのSeriesに設定しています。 ItemsSourceに10行目で生成しているグラフのデータを設定します。

グラフの更新

最後に50行目でグラフの更新を行っています。 AxesやSeriesの定義を変更した場合はInvalidatePlotを行います。

Xaml

MainWindow.xamlでPlotViewを使えるようにWindowタグに以下を追加します。

xmlns:oxy="http://oxyplot.org/wpf"

PlotViewタグを追加し、csに追加したPlotModelのプロパティをBindingします。
ここに折れ線グラフが描画されます。

<oxy:PlotView Model="{Binding PlotModel}"></oxy:PlotView>

画面表示

上記コードを実行すると、以下のようなグラフが描画されました。 f:id:nack10:20211107093331p:plain

参考

いろいろなグラフが描画できるみたいです。
OxyPlotのドキュメントは以下となります。

oxyplot.readthedocs.io

ソースコード

以下に置いています。

github.com

C#でJsonを使う

C#Json.NETを使用して、クラスをJsonに変換したり(シリアライズ)、Jsonをクラスに変換したり(デシリアライズ)する方法です。

www.newtonsoft.com

事前準備

Visual StudioでNewtonsoft.Jsonというパッケージをインストールしてください。
パッケージのインストールについては以下を参照してください。
docs.microsoft.com

クラスをJson形式に変換する

まずはクラスをJson形式に変換する(シリアライズ)の方法です。
今回はユーザー情報を格納するUserModelというクラスを作って、そのクラスをJsonに変換します。

画面

以下のようなボタンが2つある画面を作りました。

f:id:nakahara-10yro:20210329091622p:plain

UserModel

シリアライズ、デシリアライズ対象のUserModelです。
Jsonに含めたくないプロパティには以下のアノテーションを付与しておきます。

[Newtonsoft.Json.JsonIgnore]

using System;
using System.Collections.Generic;
using System.Text;

namespace WpfJsonConverterSample
{
    /// <summary>
    /// User Model
    /// </summary>
    public class UserModel
    {
        /// <summary>
        /// CreatedDateTime
        /// </summary>
        [Newtonsoft.Json.JsonIgnore]
        public DateTime CreatedDateTime { get; set; }

        /// <summary>
        /// User Id
        /// </summary>
        public int UserId { get; set; }

        /// <summary>
        /// User Name
        /// </summary>
        public string UserName { get; set; }

        /// <summary>
        /// Birth Day
        /// </summary>
        public DateTime BirthDay { get; set; }

        /// <summary>
        /// Family
        /// </summary>
        public List<UserModel> Family { get; set; } = new List<UserModel>();

        /// <summary>
        /// FamilyCount
        /// </summary>
        [Newtonsoft.Json.JsonIgnore]
        public int FamilyCount
        {
            get
            {
                return this.Family.Count;
            }
        }
    }
}

クラスをJsonに変換する(シリアライズ)

// 出力ファイルパス
private const string FilePath = @"c:\temp\user.json";

/// <summary>
/// Serialize
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnSerialize_Click(object sender, RoutedEventArgs e)
{
    // UserModelを生成
    var user = this.CreateUserModel();
    // 生成したUserModelをJsonに変換し、ファイルに出力する
    var json = Newtonsoft.Json.JsonConvert.SerializeObject(user);
    using (var sw = new StreamWriter(FilePath, false, Encoding.UTF8))
    {
        sw.Write(json);
    }
}

/// <summary>
/// CreateUserModel
/// </summary>
/// <returns></returns>
private UserModel CreateUserModel()
{
    var tanaka = new UserModel();
    tanaka.CreatedDateTime = DateTime.Now;
    tanaka.UserId = 1;
    tanaka.UserName = "田中 太郎";
    tanaka.BirthDay = new DateTime(1990, 12, 23);

    var mama = new UserModel();
    mama.CreatedDateTime = DateTime.Now;
    mama.UserId = 2;
    mama.UserName = "田中 花子";
    mama.BirthDay = new DateTime(1991, 3, 4);
    tanaka.Family.Add(mama);

    var child = new UserModel();
    child.CreatedDateTime = DateTime.Now;
    child.UserId = 3;
    child.UserName = "田中 一郎";
    child.BirthDay = new DateTime(2018, 11, 1);
    tanaka.Family.Add(child);

    return tanaka;
}

出力されたJsonは以下です。

{
    "UserId": 1,
    "UserName": "田中 太郎",
    "BirthDay": "1990-12-23T00:00:00",
    "Family": [
        {
            "UserId": 2,
            "UserName": "田中 花子",
            "BirthDay": "1991-03-04T00:00:00",
            "Family": []
        },
        {
            "UserId": 3,
            "UserName": "田中 一郎",
            "BirthDay": "2018-11-01T00:00:00",
            "Family": []
        }
    ]
}

以下のメソッドでクラスがJsonに変換されます。かんたんですね。

Newtonsoft.Json.JsonConvert.SerializeObject(user);

今回はJson(string変数)をファイルに出力しています。
データベースを利用したアプリケーションであれば、この文字列をDBに保存しておきます。

Jsonをクラスに変換する(デシリアライズ)

逆にJsonからUserModelを生成します。(デシリアライズ)

/// <summary>
/// Deserialize
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnDeserialize_Click(object sender, RoutedEventArgs e)
{
    var json = File.ReadAllText(FilePath);
    var userModel = Newtonsoft.Json.JsonConvert.DeserializeObject<UserModel>(json);

    var result = Newtonsoft.Json.JsonConvert.SerializeObject(json);
    Debug.WriteLine(result);
}

ファイルの内容をすべて読み込み、読み込んだ内容を以下のメソッドに渡しています。
今回はGenericsメソッドを使って変換したいクラスを指定しておきます。

Newtonsoft.Json.JsonConvert.DeserializeObject<UserModel>(json);

まとめ

C#でクラスからJsonへの変換、Jsonからクラスへの変換はJson.NETを使うと楽です。
ソースコード一式は以下に置いていますので良かったら参考にしてください。

github.com