Viewを分割して高低図の領域を作る(アンドロイド地図アプリの開発 その22)

前回は、ちょっと一休みで軽い話題でしたが、いよいよ、高低図の表示に取りかかります。
alasixosaka.hatenablog.com

Viewを分割する

高低図を表示するために、まず地図の画面の下に高低図を表示する領域を作成する。まあ、なくても重ね書きすることもできるけど、見辛いので、専用の領域を作成することにしました。

レイアウトを変更

地図を表示しているレイアウトファイルは、rotatemapviewer.xmlなので、これを修正する。Viewを分割するには、LinearLayoutにViewを並べて、weightを設定するのがやり易そうだ。もとのViewはRelativeLayoutで作成されているので、こいつの外側にLinearLayoutを配置する。2つのViewは縦に配置するので、orientationはverticalにする。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="fill_parent"
    android:layout_width="fill_parent"
    android:orientation="vertical">
    <RelativeLayout
        android:id="@+id/mainView"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="4">

そして、下側にcanvasを配置する。
高低図を書くやり方もいろいろ考えたがcanvasで書くことにした。mapsforgeのサンプルプログラムにdualmapviewerというのがあって2つの地図を表示できるみたいだ、こいつを使うという方法も考えた。だが、地図上にプロットしようとすると、経度、緯度で座標を入力する必要があり、計算が面倒になる。メリットとしては現在地を表示するのが楽になるという点。だが結局canvasを使う方法にすることにした。
2つの画面の大きさの比率は4:1にした。比率の設定はweightに値を入力するだけなので、楽に設定できる。
下の画面にcanvasを書くために、Paintviewという専用のクラスを作成し、レイアウトファイルを書き換える。mpf_rotaitonCはこのプロジェクトの名称。

    </RelativeLayout>
    <com.example.mpf_rotationC.PaintView
        android:id="@+id/height"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>
</LinearLayout>

rotatemapviewerが呼ばれるとpaintviewのonDrawが実行される。onDrawにグラフの枠と縦軸を書くようにした。まず、Paint paint = new Paint() でPaintをnewし、paint.setColor(Color.BLACK)で線の色を黒に指定。
paint.setStyle(Paint.Style.STROKE)で線を実線にし、paint.setStrokeWidth(3)で線の幅を指定する。
Canvasの幅と高さはそれぞれ、canvas.getWidth()、canvas.getHeight()で得ることができる。これを4分割して、ループでcanvas.drawLine() コマンドで線を引いた。また、外枠はcanvas.drawRect() コマンドで描いている。

public class PaintView extends View {
    
    public PaintView(Context context, AttributeSet attribute){
        super(context,attribute);
    }
    @Override
    public void onDraw(Canvas canvas){
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(3);
        int cheight = canvas.getHeight();
        int cwidth = canvas.getWidth();
        
        try {
            for (int i=0;i<4;i++){
                canvas.drawLine(i*cwidth/4,0,i*cwidth/4,cheight,paint);
            }
            canvas.drawRect(new Rect(0, 0, cwidth, cheight), paint);
            
        }catch (Exception e){
            Log.e("PaintView","error");
        }        
    }
}

Canvasの外枠が画面の淵と重なってよくわからなくなっているが、画面はこんな感じになった。

f:id:alasixOsaka:20200405183128j:plain
画面を分割し、地図の下にCanvasを貼りつけた

ここに高低図と現在地を表示すれば完成になるが、書くのは簡単だが、そう簡単ではない。

高低図を書くやり方は次回にして、今回はレイアウトの勉強をしたついでに、メニュー画面のレイアウトもconstraintLayoutを使って書き直した。
ConstraintLayoutの詳細は参考サイトに詳しく書いてあるのでそちらを見て下さい。
今までは見映えより、実際の機能を搭載することを重視したので、メニュー画面についてはお世辞にも見映えが良い画面とは言えなかった。今回は、各ボタンが縦に均等に、かつ真ん中に配置するようにした。また、高低図を表示するかしないかを選択するラジオボタンを追加した。さらに、紛らわしい表現だった、GPSOn/OffのラジオボタンとRotationalViewのボタンの名称も変更し、それぞれRotateOn/OffとMap Viewとした。

ConstraintLayoutはそれぞれの部品の配置を相対的な制約によって記述するので、RelativeLayoutに似ている。違いは更に柔軟に記述ができるということだが、詳細に説明できるほど理解していないので、やったことだけを書いておく。
まず、一番上の"Map File Select"のボタンを基準点にした。幅は各ボタンのサイズを揃えるために200dpとしている。高さは”wrap content"で揃えた。
画面の一番上からのマージンを50dpとしている。左右は均等になるように(つまりセンタリングするために)、 app:layout_constraintEnd_toEndOf="parent"と app:layout_constraintStart_toStartOf="parent"としている。これは、画面の右端と左端とボタンをそれぞれ制約するということで、これでセンタリングになる。片側だけに制約をつけるとどちらかに寄った表示(つまり左寄せや右寄せ)になる。
また、各パーツを均等に配置するためにこの基準パーツにだけ、app:layout_constraintVertical_chainStyle="spread"としている。これで、各パーツが上下に均等に配置される。
2番目のパーツ”GPX File Select”ボタンは、上を先ほどのパーツ”Map File Select”で制約し、下を次のパーツ”POI File Select”で規定している。このようにして、各パーツを数珠つなぎにして記述すると、きれいに均等配置になった。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerHorizontal="true">

    <Button
        android:id="@+id/btMapFileSelect"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:text="@string/map_file_select"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="spread" />

    <Button
        android:id="@+id/btGPXSelect"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:text="@string/gpx_file_select"
        app:layout_constraintBottom_toTopOf="@id/btPOISelect"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btMapFileSelect" />

    <Button
        android:id="@+id/btPOISelect"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:text="@string/poi_file_select"
        app:layout_constraintBottom_toTopOf="@id/btRotationalView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btGPXSelect" />

    <Button
        android:id="@+id/btRotationalView"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:text="@string/RotationalView"
        app:layout_constraintBottom_toTopOf="@id/radiogroup"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btPOISelect" />

    <Button
        android:id="@+id/btSelectTheme"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:text="@string/select_theme"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/radiogroup" />

    <RadioGroup
        android:id="@+id/radiogroup"
        android:layout_width="350dp"
        android:layout_height="wrap_content"
        android:background="#df7401"
        android:orientation="horizontal"
        android:paddingLeft="10dp"
        android:paddingTop="10dp"

        android:paddingRight="10dp"
        android:paddingBottom="10dp"
        app:layout_constraintBottom_toTopOf="@id/radiogroup2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btRotationalView">

        <RadioButton
            android:id="@+id/rbOn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginLeft="25dp"
            android:layout_marginRight="25dp"
            android:background="#ffffff"
            android:text="@string/onGPS" />

        <RadioButton
            android:id="@+id/rbOff"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_marginLeft="25dp"
            android:layout_marginRight="25dp"
            android:background="#ffffff"
            android:text="@string/offGPS" />

    </RadioGroup>

    <RadioGroup
        android:id="@+id/radiogroup2"
        android:layout_width="350dp"
        android:layout_height="80dp"
        android:background="#df7401"
        android:orientation="horizontal"
        android:paddingLeft="10dp"
        android:paddingTop="15dp"

        android:paddingRight="10dp"
        android:paddingBottom="0dp"
        app:layout_constraintBottom_toTopOf="@id/btSelectTheme"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/radiogroup">

        <RadioButton
            android:id="@+id/rb2On"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginLeft="25dp"
            android:layout_marginRight="25dp"
            android:layout_gravity="center_vertical"
            android:background="#ffffff"
            android:text="@string/on" />

        <RadioButton
            android:id="@+id/rb2Off"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="25dp"
            android:layout_marginRight="25dp"
            android:background="#ffffff"
            android:text="@string/off" />

    </RadioGroup>

    <TextView
        android:id="@+id/heightMap"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/heightMap"
        android:textColor="#3F51B5"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="@id/radiogroup2"
        app:layout_constraintTop_toTopOf="@id/radiogroup2"
        tools:ignore="MissingConstraints" />

</android.support.constraint.ConstraintLayout>

f:id:alasixOsaka:20200405183648j:plain
高低図表示のラジオボタンを追加し、全体の配置を整えた


参考にしたサイト
比率で幅や高さを指定する方法 - レイアウトの weight - ユーザーインターフェイス - Android 開発入門

AndroidのCanvasを使いこなす! – 基本的な描画 – PSYENCE:MEDIA
[Android] Custom Canvas をレイアウトに挿入する

XMLで始めるConstraintLayout - Qiita
[Android] ConstraintLayout レイアウト逆引きまとめ - Qiita