20200928のAndroidに関する記事は8件です。

【Android】EditTextの下線を消し方

【Android】EditTextの下線を消す

  • EditTextの下線を消す方法
Activity.xml
<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    // backgroundに、以下を設定するだけ
    android:background="#00000000"/>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityAndroidプラグインのIFメモ

Java

package com.android.gl2jni;

// Wrapper for native library
public class GL2JNILib {

     static {
         System.loadLibrary("gl2jni");
     }

    /**
     * @param width the current view width
     * @param height the current view height
     */
     public static native void init(int width, int height);
     public static native void step();
}

C++

extern "C" {
    JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_init(JNIEnv * env, jobject obj,  jint width, jint height);
    JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_step(JNIEnv * env, jobject obj);
};

JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_init(JNIEnv * env, jobject obj,  jint width, jint height)
{
    setupGraphics(width, height);
}

JNIEXPORT void JNICALL Java_com_android_gl2jni_GL2JNILib_step(JNIEnv * env, jobject obj)
{
    renderFrame();
}

c#

using System.Runtime.InteropServices; // for dll import
String PluginPackage = "com.ex.test1";
AndroidJavaObject mImageLoader = null;

[DllImport("PluginName")]
protected static extern int InitNativeLib();

public void init_lib() 
{
    mImageLoader = new AndroidJavaObject(PluginPackage + "ClasName");
    tex_sizeX = mImageLoader.CallStatic<Int32>("getX");

    InitNativeLib()
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

低レベルネイティブプラグインインターフェースのメモ

元ネタ

https://github.com/Unity-Technologies/NativeRenderingPlugin
https://qiita.com/fukaken5050/items/63940b65ec79a59161eb

環境

OS : win10pro
IDE : AndroidStudio 4.0.1
Unity : 2019.4.5f1
Unity/VS : VS2019
確認した実機: Galaxys8

要点の箇条書き

低レベルネイティブプラグインインターフェース(Low-Level Native Plugin Interface)を使ってUnityAndroid/Pluginで動画を再生する。

・AndroidStudio/C++で次を#includeする。Unityインストールしたフォルダからコピペする。

#include "IUnityInterface.h"
#include "IUnityGraphics.h"  // UnityRenderingEvent

・Unityプラグインの更新はUnity再起動が必要。
  → ホットリロード検索、名前を変えて追加するなど

・今回はOpenGLESを使う。多分Vulkanでもできると思う。

基本知識

◆ 低レベルネイティブプラグインインターフェース
Unityのレンダリングは、マルチスレッドです。

レンダリング APIは、MonoBehaviourスレッドとは別のスレッド上で行われます。プラグインはUnityレンダースレッドと干渉する可能性があります。そのため、GL.IssuePluginEvent でメインスレッドからプラグインを呼びます。

Unityカメラの MonoBehaviour.OnPostRender から GL.IssuePluginEvent を呼び出す場合、プラグインはカメラのレンダリング終了後すぐにコールバックを取得します。UnityRenderingEventを使うには IUnityGraphics.h をincludeします。

◆OpenGL グラフィックス API を使用したプラグイン

2種類の OpenGL オブジェクトがあります。
1.OpenGL コンテキストをまたいで共有されるオブジェクト (texture, renderBuffer,shader等)
2.OpenGL コンテキストごとのオブジェクト(頂点配列,program pipeline等)

Unity は複数の OpenGL コンテキストを使用します。

エディターとプレイヤーの初期化と終了のときは
マスターコンテキストに依存しますが、レンダリングには専門のコンテキストを使用します。
したがって、kUnityGfxDeviceEventInitialize と kUnityGfxDeviceEventShutdown イベントの間、
コンテキストごとのオブジェクトを作成することはできません。

例えば、ネイティブのプラグインは kUnityGfxDeviceEventInitialize イベント中に頂点配列オブジェクトを作成できず、
UnityRenderingEvent コールバックでそれを使用することはできません。
これは、アクティブなコンテキストが頂点配列オブジェクトの作成時に使用されるものでないからです。

◆ Unity c# からnative plugin(C++)を呼ぶ方法(1) 

    [DllImport("nativerender")]
    private static extern bool SetupNativeTextureRender(IntPtr textureId, int width, int height);


Javaのように"com.example.hoge"は意識しなくても良いです。
プラグイン配置場所はUnityApp\Assets\Plugins\Androidです(Androidの場合)
プラグインの名前が重要です。”libnativerender.so”の場合はlib/.soを除去して[DllImport("nativerender")]です。
プラグインの更新後はUnity再起動が必要です。
void*の代わりにIntPtrを使います。

メモ

◆初期化

Unityのprivate void Start() でプラグインの初期化をします。
UnityのTexture2Dを生成して、テクスチャアドレスtexture.GetNativeTexturePtr()をプラグインに渡します。
_rawImage.textureをUnity-RawImage等にセットして描画結果を確認します。

var texture = new Texture2D(_width, _height, TextureFormat.ARGB32, false);
_rawImage.texture = texture;

SetupNativeTextureRender(texture.GetNativeTexturePtr(), texture.width,texture.height) ;
StartCoroutine(NativeTextureRenderLoop()); //コルーチン起床

◆コルーチン/描画

    private IEnumerator NativeTextureRenderLoop() {
        while (true) {
            yield return new WaitForEndOfFrame();
            GL.IssuePluginEvent(GetRenderEventFunc(), 1);
        }
    }

コード C++ (拝借してます)

#include "IUnityInterface.h"
#include "IUnityGraphics.h"  // UnityRenderingEvent
#include <math.h>
#include <stdio.h>
#include <string>
#include <assert.h>
#include <GLES2/gl2.h>
#include <jni.h>

static GLuint   g_textureId = NULL;
static int      g_texWidth;
static int      g_texHeight;
static u_char* g_pBytes = NULL;

#define LOG_PRINTF printf

#if 1
extern "C" bool SetupNativeTextureRender( void* textureId, int width, int height )
{
    g_textureId = (GLuint)(size_t)textureId;
    g_texWidth = width;
    g_texHeight = height;
    LOG_PRINTF( "SetupNativeTextureRender:%d, %d, %d", g_textureId, g_texWidth, g_texHeight );

    g_pBytes = new u_char[ g_texWidth * g_texHeight * 4 ];
    return true;
}

extern "C" void FinishNativeTextureRender()
{
    if( g_pBytes != NULL )
        delete[] g_pBytes;
    g_pBytes = NULL;
}

static void UNITY_INTERFACE_API
OnRenderEvent( int eventID )
{
    glBindTexture( GL_TEXTURE_2D, g_textureId );

    static u_char s_r = 0;
    u_char* bytes = g_pBytes;
    for( int y = 0; y < g_texHeight; y++ )
    {
        for( int x = 0; x < g_texWidth; x++ )
        {
            int offset = ( ( y * g_texWidth ) + x ) * 4;
            bytes[ offset + 0 ] = s_r;
            bytes[ offset + 1 ] = 0;
            bytes[ offset + 2 ] = 0;
            bytes[ offset + 3 ] = 255;
        }
    }
    glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, g_texWidth, g_texHeight, GL_RGBA, GL_UNSIGNED_BYTE, bytes );
    s_r ++;
    s_r %= 255;
}

extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
GetRenderEventFunc()
{
    return OnRenderEvent;
}

//com.ore.unityplugin
extern "C" JNIEXPORT jstring JNICALL
Java_com_ore_unityplugin_Calc_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "hoge from C++";
    return env->NewStringUTF(hello.c_str());
}

#endif


Unity

#define USE_ANDROID_PLUGIN

using System;
using System.Collections;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;



public class TestNativeCppRender : MonoBehaviour {
    [SerializeField] private RawImage _rawImage = null;
    [SerializeField] private int _width = 512;
    [SerializeField] private int _height = 512;

    public Text m_text;

    //PluginFunction
                //nativerender
    [DllImport("nativerender")]
    private static extern bool SetupNativeTextureRender(IntPtr textureId, int width, int height);
    [DllImport("nativerender")]
    private static extern void FinishNativeTextureRender();
    [DllImport("nativerender")]
    private static extern IntPtr GetRenderEventFunc();

    private void Start() {


        var texture = new Texture2D(_width, _height, TextureFormat.ARGB32, false);
        _rawImage.texture = texture;
#if USE_ANDROID_PLUGIN

        if (SetupNativeTextureRender(texture.GetNativeTexturePtr(), texture.width, texture.height) == false) {
            m_text.text = "fail SetupNativeTextureRender";
            return;
        }

        StartCoroutine(NativeTextureRenderLoop());
        m_text.text = "after StartCoroutine";
#else
        m_text.text = " editor";
        StartCoroutine(NativeTextureRenderLoop());

#endif
    }

    private void OnDestroy() {
        FinishNativeTextureRender();
    }

    private IEnumerator NativeTextureRenderLoop() {
        int cnt = 0;
        while (true) {
            yield return new WaitForEndOfFrame();
            GL.IssuePluginEvent(GetRenderEventFunc(), 1);
            m_text.text = String.Format("cnt = {0}", cnt);
            cnt++;
        }
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

低レベルネイティブプラグインインターフェースのメモ1

元ネタ

https://github.com/Unity-Technologies/NativeRenderingPlugin
https://qiita.com/fukaken5050/items/63940b65ec79a59161eb

環境

OS : win10pro
IDE : AndroidStudio 4.0.1
Unity : 2019.4.5f1
Unity/VS : VS2019
確認した実機: Galaxys8

要点の箇条書き

低レベルネイティブプラグインインターフェース(Low-Level Native Plugin Interface)を使ってUnityAndroid/Pluginで動画を再生する。

・AndroidStudio/C++で次を#includeする。Unityインストールしたフォルダからコピペする。

#include "IUnityInterface.h"
#include "IUnityGraphics.h"  // UnityRenderingEvent

・Unityプラグインの更新はUnity再起動が必要。
  → ホットリロード検索、名前を変えて追加するなど

・今回はOpenGLESを使う。多分Vulkanでもできると思う。

基本知識

◆ 低レベルネイティブプラグインインターフェース
Unityのレンダリングは、マルチスレッドです。

レンダリング APIは、MonoBehaviourスレッドとは別のスレッド上で行われます。プラグインはUnityレンダースレッドと干渉する可能性があります。そのため、GL.IssuePluginEvent でメインスレッドからプラグインを呼びます。

Unityカメラの MonoBehaviour.OnPostRender から GL.IssuePluginEvent を呼び出す場合、プラグインはカメラのレンダリング終了後すぐにコールバックを取得します。UnityRenderingEventを使うには IUnityGraphics.h をincludeします。

◆OpenGL グラフィックス API を使用したプラグイン

2種類の OpenGL オブジェクトがあります。
1.OpenGL コンテキストをまたいで共有されるオブジェクト (texture, renderBuffer,shader等)
2.OpenGL コンテキストごとのオブジェクト(頂点配列,program pipeline等)

Unity は複数の OpenGL コンテキストを使用します。

エディターとプレイヤーの初期化と終了のときは
マスターコンテキストに依存しますが、レンダリングには専門のコンテキストを使用します。
したがって、kUnityGfxDeviceEventInitialize と kUnityGfxDeviceEventShutdown イベントの間、
コンテキストごとのオブジェクトを作成することはできません。

例えば、ネイティブのプラグインは kUnityGfxDeviceEventInitialize イベント中に頂点配列オブジェクトを作成できず、
UnityRenderingEvent コールバックでそれを使用することはできません。
これは、アクティブなコンテキストが頂点配列オブジェクトの作成時に使用されるものでないからです。

◆ Unity c# からnative plugin(C++)を呼ぶ方法(1) 

using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;

    [DllImport("nativerender")]
    private static extern bool SetupNativeTextureRender(IntPtr textureId, int width, int height);


Javaのように"com.example.hoge"は意識しなくても良いです。
プラグイン配置場所はUnityApp\Assets\Plugins\Androidです(Androidの場合)
プラグインの名前が重要です。”libnativerender.so”の場合はlib/.soを除去して[DllImport("nativerender")]です。
プラグインの更新後はUnity再起動が必要です。
void*の代わりにIntPtrを使います。

メモ

◆初期化

Unityのprivate void Start() でプラグインの初期化をします。
UnityのTexture2Dを生成して、テクスチャアドレスtexture.GetNativeTexturePtr()をプラグインに渡します。
_rawImage.textureをUnity-RawImage等にセットして描画結果を確認します。

var texture = new Texture2D(_width, _height, TextureFormat.ARGB32, false);
_rawImage.texture = texture;

SetupNativeTextureRender(texture.GetNativeTexturePtr(), texture.width,texture.height) ;
StartCoroutine(NativeTextureRenderLoop()); //コルーチン起床

◆コルーチン/描画

    private IEnumerator NativeTextureRenderLoop() {
        while (true) {
            yield return new WaitForEndOfFrame();
            GL.IssuePluginEvent(GetRenderEventFunc(), 1);
        }
    }

メモ

SetupNativeTextureRender( unityで生成したテクスチャメモリ,w,h)をC++へ渡す。->g_pBytes,g_textureId
g_pBytesを更新する。
UnityRenderingEvent():
glBindTexture( GL_TEXTURE_2D, g_textureId );でg_textureId を有効にする。
  glTexSubImage2D( GL_TEXTURE_2D...) にg_pBytesを渡してOpenGLESテクスチャを更新する

コード C++ (拝借してます)

#include "IUnityInterface.h"
#include "IUnityGraphics.h"  // UnityRenderingEvent
#include <math.h>
#include <stdio.h>
#include <string>
#include <assert.h>
#include <GLES2/gl2.h>
#include <jni.h>

static GLuint   g_textureId = NULL;
static int      g_texWidth;
static int      g_texHeight;
static u_char* g_pBytes = NULL;

#define LOG_PRINTF printf

#if 1
extern "C" bool SetupNativeTextureRender( void* textureId, int width, int height )
{
    g_textureId = (GLuint)(size_t)textureId;
    g_texWidth = width;
    g_texHeight = height;
    LOG_PRINTF( "SetupNativeTextureRender:%d, %d, %d", g_textureId, g_texWidth, g_texHeight );

    g_pBytes = new u_char[ g_texWidth * g_texHeight * 4 ];
    return true;
}

extern "C" void FinishNativeTextureRender()
{
    if( g_pBytes != NULL )
        delete[] g_pBytes;
    g_pBytes = NULL;
}

static void UNITY_INTERFACE_API
OnRenderEvent( int eventID )
{
    glBindTexture( GL_TEXTURE_2D, g_textureId );

    static u_char s_r = 0;
    u_char* bytes = g_pBytes;
    for( int y = 0; y < g_texHeight; y++ )
    {
        for( int x = 0; x < g_texWidth; x++ )
        {
            int offset = ( ( y * g_texWidth ) + x ) * 4;
            bytes[ offset + 0 ] = s_r;
            bytes[ offset + 1 ] = 0;
            bytes[ offset + 2 ] = 0;
            bytes[ offset + 3 ] = 255;
        }
    }
    glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, g_texWidth, g_texHeight, GL_RGBA, GL_UNSIGNED_BYTE, bytes );
    s_r ++;
    s_r %= 255;
}

extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
GetRenderEventFunc()
{
    return OnRenderEvent;
}

//com.ore.unityplugin
extern "C" JNIEXPORT jstring JNICALL
Java_com_ore_unityplugin_Calc_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "hoge from C++";
    return env->NewStringUTF(hello.c_str());
}

#endif


Unity

#define USE_ANDROID_PLUGIN

using System;
using System.Collections;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;



public class TestNativeCppRender : MonoBehaviour {
    [SerializeField] private RawImage _rawImage = null;
    [SerializeField] private int _width = 512;
    [SerializeField] private int _height = 512;

    public Text m_text;

    //PluginFunction
                //nativerender
    [DllImport("nativerender")]
    private static extern bool SetupNativeTextureRender(IntPtr textureId, int width, int height);
    [DllImport("nativerender")]
    private static extern void FinishNativeTextureRender();
    [DllImport("nativerender")]
    private static extern IntPtr GetRenderEventFunc();

    private void Start() {


        var texture = new Texture2D(_width, _height, TextureFormat.ARGB32, false);
        _rawImage.texture = texture;
#if USE_ANDROID_PLUGIN

        if (SetupNativeTextureRender(texture.GetNativeTexturePtr(), texture.width, texture.height) == false) {
            m_text.text = "fail SetupNativeTextureRender";
            return;
        }

        StartCoroutine(NativeTextureRenderLoop());
        m_text.text = "after StartCoroutine";
#else
        m_text.text = " editor";
        StartCoroutine(NativeTextureRenderLoop());

#endif
    }

    private void OnDestroy() {
        FinishNativeTextureRender();
    }

    private IEnumerator NativeTextureRenderLoop() {
        int cnt = 0;
        while (true) {
            yield return new WaitForEndOfFrame();
            GL.IssuePluginEvent(GetRenderEventFunc(), 1);
            m_text.text = String.Format("cnt = {0}", cnt);
            cnt++;
        }
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

13. 【Android/Kotlin】Handler(画面遷移を遅延)

はじめに

DreamHanksのMOONです。

前回はCircleImageViewという外部ライブラリを使用しました。
12. 【Android/Kotlin】丸いイメージ(CircleImageView)

今回はHandlerについて説明し、Handlerで画面遷移を遅延してみます。

Handlerとは

ハンドラは簡単にサブスレッドからメインスレッドへメッセージを受け渡すためのクラスです。

アンドロイドでは、UIの作業をサブスレッドですることは禁止されています。

なので、サブスレッドにUIの作業をしたい場合はHandlerを使用します。

画面遷移を遅延

・ローディング画面のActivityを作成

LoadingActivity.kt
package com.example.practiceapplication

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler

class LoadingActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_loading)

        //ハンドラのメソッド呼び出す
        loadingDelay()
    }

    //画面の遷移を遅延するためのメソッド
    fun loadingDelay(){
        //ハンドラを生成し、遅延時間を2秒に設定
        Handler().postDelayed({
            //2秒以降に画面を遷移するためのIntent設定
            val nextIntent = Intent(this, MainActivity::class.java)
            startActivity(nextIntent)
        }, 2000)
    }
}

・ローディング画面のレイアウトのxmlを作成
1.PNG

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#2a5a4c"
    android:gravity="center">

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="DreamHanks"
        android:textColor="#FFFFFF"
        android:textSize="50sp" />

</LinearLayout>

・画面遷移させるActivity、xmlを作成
 下記のリンクで確認してください。
 7. 【Android/Kotlin】バリデーションチェック

アプリ起動

・アプリを実行する時の初期のローディング画面

・2秒以降は遷移された画面

終わりに

今回はHandlerについて説明し、Handlerで画面遷移を遅延しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Robolectric 4.4でデフォルトになった@LooperMode(PAUSED)についてメモ

Robolectricのアップデートについてのメモです :pencil:

公式の記事
http://robolectric.org/blog/2019/06/04/paused-looper/

RobolectricはUIとテストで同じスレッドを共有していて、以下のようなパターンでテストが失敗します。
理由はbefore, after, betweenになりpostを即座に同期的に実行するためです。
本当のHandlerであれば、postが後から実行されるため、うまく通るはずです。

http://robolectric.org/blog/2019/06/04/paused-looper/ より引用

List<String> events = new ArrayList<>();
events.add("before");
new Handler(Looper.getMainLooper()).post(() -> events.add("after"));
events.add("between");

assertThat(events).containsExactly("before", "between", "after").inOrder();

そこで @LooperMode(PAUSED)が追加されました。これはクラスやメソッドにつけます。Robolectric 4.4以降ではこちらがデフォルトになりました。

主に違うところはこちら

  • このモードだと自動的に同期的にHandlerにpostした処理が実行はされない。ShadowLooper APIを使って ShadowLooper.idle()など、明示的に実行する必要がある。その代わり以下の改善がされた。
  • 未実行のタスクでテストが失敗したらユーザーに教えてくれるようになった
  • AndroidXのいくつかのAPI(FragmentScenarioなど)では自動的にキューを空にしてくれる
  • バックグラウンドルーパーを使っている場合は本当にスレッドで実行される

他にもいくつかの違う点があるようです。
http://robolectric.org/javadoc/4.4/org/robolectric/annotation/LooperMode.Mode.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スマホアプリ向けお手軽グラフライブラリを作ってみた【MPAndroidChart改】

スマホアプリとグラフ

Androidアプリでグラフを表示させたい場合、
MPAndroidChart
というライブラリがよく使われます。 (iOS版のChartsライブラリも存在)

多機能かつUIも優れた素晴らしいライブラリですが、
日本語情報の不足もあり、実装難度は結構高いと感じます。
そこで、簡易にグラフを作成するための追加パッケージを作成してみました!
Github

下のようなグラフを簡単に作る事ができます

image.png
(題材が重い!(笑))

なお、本パッケージはKotlinでの実装を前提としておりますが、
希望があればJava・Swift(iOS)バージョンも作ろうと思います。
その他質問、メソッド追加要望等あれば、気軽にコメント頂けますとありがたいです!

MPAndroidChartをより使いやすくするには?

MPAndroidChartでグラフを含んだアプリを作成して、私が苦戦したのは以下の部分です。
1. 時系列グラフの作成
2. フォーマット指定(特に色)
3. ツールヒントの作成
4. 日本語ドキュメントが少ない

1. 時系列グラフの作成

詳しくは補足に書きますが、時系列グラフの作成にはデータの格納や軸のフォーマット等、相当な手間が掛かります。
image.png

本パッケージでの対応

時系列グラフ専用のメソッドを作成し、簡単な操作で作成できるようにしました。

また、下記のように時間が等間隔でない場合、

        val x = listOf<Date>(
            sdf.parse("2020/09/01 00:00:00"),
            sdf.parse("2020/09/01 06:00:00"),
            sdf.parse("2020/09/01 12:00:00"),
            sdf.parse("2020/09/01 18:00:00"),
            sdf.parse("2020/09/02 00:00:00"),
            sdf.parse("2020/09/02 06:00:00"),
            sdf.parse("2020/09/02 12:00:00"),
            sdf.parse("2020/09/03 18:00:00"),//ここのみ時間間隔が飛んでいる
        )

上記の方法ではインデックス情報のみに基づいて等間隔にプロットされてしまい、
時間の間隔が横軸上で正確に表現されません。
image.png

本パッケージでは、横軸が等間隔でない場合も正確にプロットされるような表示モードも準備しました
image.png
(ラベルは最初と最後のみ表示されます)

2. UIフォーマット指定

MPAndroidChartでは、グラフ表示のためにおおざっぱに
①データを入力する処理
②UIフォーマットを指定する処理
の2種類が必要となります。
コード上でもこの2種類の処理をまとめて別個に指定できることが、独立性の観点から望ましいです。

しかし折れ線グラフのように複数のY軸の値を指定する場合、
下のように①と②に処理が入り組んだ指定方法となってしまいます。

        //Entryにデータ格納 → ①データ入力処理
        var entryList1 = mutableListOf<Entry>()//1本目の線
        var entryList2 = mutableListOf<Entry>()//2本目の線
        for(i in x.indices){
            entryList1.add(
                Entry(x[i], y1[i])
            )
            entryList2.add(
                Entry(x[i], y2[i])
            )
        }

        //X軸の設定 → ②UIフォーマット指定処理
        lineChart.xAxis.apply {
            isEnabled = true
            textColor = Color.BLACK
        }
        //左Y軸の設定 → ②UIフォーマット指定処理
        lineChart.axisLeft.apply {
            isEnabled = true
            textColor = Color.BLACK
        }
        //右Y軸の設定 → ②UIフォーマット指定処理
        lineChart.axisLeft.apply {
            isEnabled = false
        }

        //LineDataSet(線1本ごとの)のリストを作成 → ①データ入力処理
        val lineDataSets = mutableListOf<ILineDataSet>()
        //線1本目のデータ格納 → ①データ入力処理
        val lineDataSet1 = LineDataSet(entryList1, "linear")
        //線1本目の色 → ②UIフォーマット指定処理
        lineDataSet1.color = Color.BLUE
        //リストに格納 → ①データ入力処理
        lineDataSets.add(lineDataSet1)
        //線2本目のデータ格納 → ①データ入力処理
        val lineDataSet2 = LineDataSet(entryList2, "square")
        //線2本目の色 → ②UIフォーマット指定処理
        lineDataSet2.color = Color.RED

        //LineDataにLineDataSetのリスト格納 → ①データ入力処理
        lineDataSets.add(lineDataSet2)
        //LineChartにData格納 → ①データ入力処理
        lineChart.data = LineData(lineDataSets)

本パッケージでの対応

上記のような指定法は独立性やコードの可読性の観点から好ましい状態ではないので、
・UIフォーマット指定用クラス(ChartとDataSetの2種類。両者の違いはこちら参照)
・データ入力用メソッド
・上記UI指定&データを基にグラフ描画するメソッド
の順で、独立して指定できるような構成としました。

また、フォーマット指定ようわからん!という方のために、
フォーマット指定をしなかった(コンストラクタに引数を入れない)場合も、
私の主観でいい感じ(抽象的な表現ですが‥笑)に設定してくれるような機能を加えています。

特に色設定は設定箇所が多く、手動設定が面倒なので、
・カラーユニバーサルデザインに基づき、線や棒の色を自動指定
image.png
・背景が黒(輝度が0.5以下)のときは、文字を白に自動変更
image.png

という機能を追加しました

3. ツールヒント作成

データ点をタップした際にデータの詳細を表示してくれる「ツールヒント」
image.png

があると、UIの見やすさが格段に向上します。

しかし公式ドキュメントにも実装法がまともに記載されておらず。
サンプルコードを見ながら手探りでの実装が求められます。

本パッケージでの対応

簡単にツールヒントを表示できるよう、フォーマット指定用クラスで下記の指定を可能としました
A. ツールヒント表示の有無
ツールヒントの表示有無を指定します

B. 表示するデータの軸方向(X、Y、XY両方)
下の図のように表示するデータの軸方向を選択します(左から表示なし、Xのみ、Yのみ、XY両方)
toolhint.png

C. 時系列グラフの場合、時刻表示のフォーマット(例:"M/d HH:mm")
X軸が時系列のとき、時刻表示のフォーマットを指定できるようにしました。
下の図では、"d日H時"というフォーマットを指定しています
image.png

D. データに付与する単位(例:℃、%、Paなど)
X軸、Y軸ともに、表示するデータに単位を付加できます。
下の図では、Y軸に"円"という単位を付加しています
image.png

4. 日本語ドキュメントが少ない

日本語で網羅的に解説した記事は皆無といっても良い状況です。
特にUIフォーマット指定系は公式英語ドキュメントに説明が記載されていないメソッド・プロパティも多いです。

本パッケージでの対応

本記事に、UIフォーマット指定プロパティの一覧と、指定による変化を図示したリンクを記載しました
(DataSetのUIフォーマット指定プロパティ一覧はこちら)

どのプロパティを設定すれば、どのようにグラフ形状が変わるかを、
日本語+画像で簡便に追えるかと思います

必要なもの

下記の開発環境を構築してください
・開発用のPC(今回はWindows10を使用)
・Android Studio(今回は4.0.1を使用、AndroidバージョンはAPI26以降推奨)
・動作させるAndroidスマホ(今回はPixel 3aを使用)

導入方法

下記のような手順となります
1. MPAndroidChartの導入
2. CustomMPAndroidChartパッケージの導入

1. MPAndroidChartの導入

プロジェクト内に、グラフ描画ライブラリであるMPAndroidChartを導入します

build.grandle(Project)に、

allprojects {
    repositories{
        :
        maven { url 'https://jitpack.io' }
        :

という記載を加えます。
9_mpandroidchart_build.gradle.png

build.grandle(Module:app)に、

dependencies {
    :
    implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
    :

という記載を加えます。
10_mpandroidchart_build.gradle_app.png

メニューバーの「File → Close Project」でプロジェクトを閉じて開き直すと、
ライブラリが反映されます。

2. CustomMPAndroidChartパッケージの導入

上記MPAndroidChartのグラフを簡易的に作成するためのメソッド・クラス集を、
「CustomMPAndroidChart」としてパッケージ化しました。
Githubにもアップロードしております

パッケージ概要

下記の6つのモジュールからなります
・LineChartMethods.kt:折れ線グラフ用メソッド集
・BarChartMethods.kt:棒グラフ用メソッド集
・CandleStickChartMethods.kt:ローソク足グラフ(株価のチャートのようなグラフ)用メソッド集
・PieChartMethods.kt:折れ線グラフ用メソッド集
・MarkerViews.kt:ツールヒント表示用クラス
・ChartFormats.kt:UIフォーマット指定用クラスを集めたモジュール

また、ツールヒントで使用するレイアウトファイル
simple_marker_view.xmlの導入も必要となります。

パッケージの導入方法

グラフを作成するプロジェクト内に、下記の手順で導入します。
※手動操作が多いので、より良い提供方法ご存知であればご教示いただけるとありがたいです

javaフォルダ直下のパッケージフォルダを右クリックし、New → Packageを選択し、"chart"と名前をつけます
11_chart.png

折れ線グラフ描画用モジュールの作成

上記作成したchartフォルダを右クリックし、New → Kotlin File/Classを選択し、"LineChartMethods"と名前を付け、GitHub上のLineChartMethods.ktをコピペします。
※コード上部の「プロジェクト構成に合わせ変更」とコメントしてある部分は、プロジェクト構成に合わせて適宜修正してください

棒グラフ描画用モジュールの作成

上記作成したchartフォルダを右クリックし、New → Kotlin File/Classを選択し、"BarChartMethods"と名前を付け、GitHub上のBarChartMethods.ktをコピペします。
※コード上部の「プロジェクト構成に合わせ変更」とコメントしてある部分は、プロジェクト構成に合わせて適宜修正してください

ローソク足グラフ描画用モジュールの作成

上記作成したchartフォルダを右クリックし、New → Kotlin File/Classを選択し、"CandleStickChartMethods"と名前を付け、GitHub上のCandleStickChartMethods.ktをコピペします。
※コード上部の「プロジェクト構成に合わせ変更」とコメントしてある部分は、プロジェクト構成に合わせて適宜修正してください

円グラフ描画用モジュールの作成

上記作成したchartフォルダを右クリックし、New → Kotlin File/Classを選択し、"PieChartMethods"と名前を付け、GitHub上のPieChartMethods.ktをコピペします。
※コード上部の「プロジェクト構成に合わせ変更」とコメントしてある部分は、プロジェクト構成に合わせて適宜修正してください

ツールヒント表示用クラスの作成

上記作成したchartフォルダを右クリックし、New → Kotlin File/Classを選択し、"MarkerViews"と名前を付け、GitHub上のMarkerViews.ktをコピペします。
※コード上部の「プロジェクト構成に合わせ変更」とコメントしてある部分は、プロジェクト構成に合わせて適宜修正してください

UIフォーマット指定用クラスの作成

上記作成したchartフォルダを右クリックし、New → Kotlin File/Classを選択し、"ChartFormats"と名前を付け、GitHub上のChartFormats.ktをコピペします。
※コード上部の「プロジェクト構成に合わせ変更」とコメントしてある部分は、プロジェクト構成に合わせて適宜修正してください

ツールヒント用レイアウトファイルの作成

クリック時のツールヒント用レイアウトファイルを、下記手順で作成します。

res/layoutを右クリックし、New → Layout Resource Fileを選択し、"simple_marker_view"と名前をつけます
24_make_fragment_xml1.png

作成されたxmlファイルを、下記内容に書き換えます

simple_marker_view.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="40dp"
    android:background="@color/toolTipBgColor"
    tools:ignore="Overdraw">

    <TextView
        android:id="@+id/tvSimple"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="7dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:text=""
        android:textSize="12sp"
        android:textColor="@color/toolTipTextColor"
        android:ellipsize="end"
        android:gravity="center_vertical|center_horizontal"
        android:textAppearance="?android:attr/textAppearanceSmall" />

</RelativeLayout>

res/values/colors.xmlに、下記内容を追記します。
(色コードはこちらを参考に適宜変更してください)

colors.xml
    :
    <color name="toolTipBgColor">#999999</color>//背景色コード
    <color name="toolTipTextColor">#ffffff</color>//テキスト色コード
    :

以上で、パッケージの導入が完了しました

使用方法

折れ線グラフ、棒グラフ、ローソク足グラフ、円グラフそれぞれに関して、
実装方法を解説します。

GitHubにサンプルコードをアップしていますので、こちらも参照頂けると分かりやすいかと思います

1. 折れ線グラフの実装方法

折れ線グラフの実装方法を、レイアウト(.xml)と処理部(.kt)にわけて解説します。

レイアウトの実装

下記のように、LineChart用のウィジェットをレイアウト(例:activity_main.xml)中に組み込みます。

activity_main.xml
  :
<com.github.mikephil.charting.charts.LineChart
    android:id="@+id/lineChartExample"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
  :

折れ線グラフ作成メソッド呼び出し処理の実装

折れ線グラフ作成メソッドを呼び出す処理を、Kotlinファイル(例:MainActivity.kt)内に実装します。
・線が1本のとき
・横軸を時系列にしたいとき
・複数のとき
で例を分けて解説します。

線が1本のとき

基本的な流れとしては
・Chartフォーマットの指定
・DataSetフォーマットの指定
・EntryにmakeLineChartDataメソッドでデータ格納
・setupLineChartメソッドでグラフ描画
となります。

        //表示用サンプルデータの作成//
        val x = listOf<Float>(1f, 2f, 3f, 5f, 8f, 13f, 21f, 34f)//X軸データ
        val y = x.map{it*it}//Y軸データ(X軸の2乗)

        //Chartフォーマット
        var lineChartFormat = LineChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var lineDataSetFormat =  mapOf(
            "linear" to LineDataSetFormat(/*ここでDataSetフォーマット指定*/)
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allLinesEntries: MutableMap<String, MutableList<Entry>> = mutableMapOf(
            "linear" to makeLineChartData(x, y)
        )

        //②~⑦グラフの作成
        setupLineChart(allLinesEntries, findViewById(R.id.lineChartExample), lineChartFormat, lineDataSetFormat, context)

ChartフォーマットやDataSetフォーマットはグラフのUIを指定します。詳しくは後述します
上記コードを実行すると、下図のようなグラフが表示されます
image.png

横軸を時系列にしたいとき

横軸を時系列にしたいときは、Entryにデータ格納するメソッドを、
makeLineChartData() → makeDateLineChartData()
に変更します

//表示用サンプルデータの作成//
        //X軸データ(時間)
        val sdf: SimpleDateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
        val x = listOf<Date>(
            sdf.parse("2020/09/01 00:00:00"),
            sdf.parse("2020/09/01 06:00:00"),
            sdf.parse("2020/09/01 12:00:00"),
            sdf.parse("2020/09/01 18:00:00"),
            sdf.parse("2020/09/02 00:00:00"),
            sdf.parse("2020/09/02 06:00:00"),
            sdf.parse("2020/09/02 12:00:00"),
            sdf.parse("2020/09/03 18:00:00"),
        )
        val y = listOf<Float>(1f, 2f, 3f, 5f, 8f, 13f, 21f, 34f)//Y軸データ(数値)

        //Chartフォーマット
        var lineChartFormat = LineChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var lineDataSetFormat =  mapOf(
            "linear" to LineDataSetFormat(/*ここでDataSetフォーマット指定*/)
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allLinesEntries: MutableMap<String, MutableList<Entry>> = mutableMapOf(
            "linear" to makeDateLineChartData(x, y, lineChartFormat.timeAccuracy)
        )

        //②~⑦グラフの作成
        setupLineChart(allLinesEntries, findViewById(R.id.lineChartExample), lineChartFormat, lineDataSetFormat, context)

image.png

非等間隔な時間を正確に表現したいとき

前述のように、上記の方法はデータ点がX方向に等間隔にプロットされるため、
データの取得間隔が一定でないときは、時間が横軸上で正確に表現されません。

このようなときに時間を正確に表現したい時は、Chartフォーマットで
timeAccuracy = true
と指定します。

        //Chartフォーマット
        var lineChartFormat = LineChartFormat(
        timeAccuracy = true,
        /*ここでその他のChartフォーマット指定*/
        )

image.png
※ラベルは最初と最後のみ表示されます

線が複数本のとき

1本の時との違いは、
・DataSetフォーマットを線の本数だけ指定
・Entryに線の本数だけデータ格納
となります。

        //表示用サンプルデータの作成//
        val x = listOf<Float>(1f, 2f, 3f, 5f, 8f, 13f, 21f, 34f)//X軸データ
        val y1 = x.map{it}//Y軸データ1(X軸の1乗)
        val y2 = x.map{it*it}//Y軸データ2(X軸の2乗)

        //Chartフォーマット
        var lineChartFormat = LineChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var lineDataSetFormat =  mapOf(
            "linear" to LineDataSetFormat(/*ここでDataSetフォーマット指定*/),
            "square" to LineDataSetFormat(/*ここでDataSetフォーマット指定*/)
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allLinesEntries: MutableMap<String, MutableList<Entry>> = mutableMapOf(
            "linear" to makeLineChartData(x, y1),
            "square" to makeLineChartData(x, y2)
        )

        //②~⑦グラフの作成
        setupLineChart(allLinesEntries, lineChart, lineChartFormat, lineDataSetFormat, context)

image.png
なお、データ格納時のメソッドをmakeDateLineChartDataにすれば、複数線かつ時系列のグラフも作成可能です

2. 棒グラフの実装方法

棒グラフの実装方法を、レイアウト(.xml)と処理部(.kt)にわけて解説します。

レイアウトの実装

LineChartのときと同様に、BarChart用のウィジェットをレイアウト(例:activity_main.xml)中に組み込みます。

activity_main.xml
  :
<com.github.mikephil.charting.charts.BarChart
    android:id="@+id/barChartExample"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
  :

実行コードの実装

棒グラフ作成メソッドを呼び出す処理を、Kotlinファイル(例:MainActivity.kt)内に実装します。
・棒が1本のとき
・横軸を時系列にしたいとき
・複数の棒を積み上げ表示するとき
・複数の棒を横に並べて表示するとき
で例を分けて解説します。

棒が1本のとき

折れ線グラフのときとほぼ同様です。
"Line~"という名前になっているクラス名を"Bar~"と変えるだけでいけるかと思います。

        //表示用サンプルデータの作成//
        val x = listOf<Float>(1f, 2f, 3f, 4f, 6f, 7f, 8f, 9f)//X軸データ
        val y = x.map{it*it}//Y軸データ(X軸の2乗)

        //Chartフォーマット
        var barChartFormat = BarChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var barDataSetFormat =  mapOf(
            "square" to BarDataSetFormat(/*ここでDataSetフォーマット指定*/)
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allBarsEntries: MutableMap<String, MutableList<BarEntry>> = mutableMapOf(
            "square" to makeBarChartData(x, y)
        )

        //②~⑦グラフの作成
        setupBarChart(allBarsEntries, barChart, barChartFormat, barDataSetFormat, context)

image.png

横軸を時系列にしたいとき

横軸を時系列にしたいときは、Entryにデータ格納するメソッドを、
makeBarChartData() → makeDateBarChartData()
に変更します

※折れ線グラフのときと異なり、timeAccuracyプロパティ指定はできません

        //表示用サンプルデータの作成//
        //X軸データ(時間)
        val sdf: SimpleDateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
        val x = listOf<Date>(
            sdf.parse("2020/09/01 00:00:00"),
            sdf.parse("2020/09/01 06:00:00"),
            sdf.parse("2020/09/01 12:00:00"),
            sdf.parse("2020/09/01 18:00:00"),
            sdf.parse("2020/09/02 00:00:00"),
            sdf.parse("2020/09/02 06:00:00"),
            sdf.parse("2020/09/02 12:00:00"),
            sdf.parse("2020/09/03 18:00:00"),
        )
        val y = listOf<Float>(1f, 2f, 3f, 5f, 8f, 13f, 21f, 34f)//Y軸データ(数値)

        //Chartフォーマット
        var barChartFormat = BarChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var barDataSetFormat =  mapOf(
            "square" to BarDataSetFormat(/*ここでDataSetフォーマット指定*/)
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allBarsEntries: MutableMap<String, MutableList<BarEntry>> = mutableMapOf(
            "square" to makeDateBarChartData(x, y)
        )

        //②~⑦グラフの作成
        setupBarChart(allBarsEntries, barChart, barChartFormat, barDataSetFormat, context)

image.png

複数の棒を積み上げ表示するとき

1本の時との違いは、
・Entryに格納するY軸データは、List<MutableList<Float>>で積み上げたいデータをまとめて格納
・棒ごとのカテゴリ名は、DataSetフォーマットのプロパティstackLabelsに、Listで指定
・棒ごと色指定は、プロパティ"color"ではなく、"colors"にリストで指定が必要(参考)
となります。

        //表示用サンプルデータの作成//
        val x = listOf<Float>(1f, 2f, 3f, 4f, 6f, 7f, 8f, 9f)//X軸データ
        val y = x.map{ mutableListOf(it, it*it)}//Y軸データ(1項目:X軸の1乗、2項目:Xの2乗)

        //Chartフォーマット
        var barChartFormat = BarChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var barDataSetFormat =  mapOf(
            "stack" to BarDataSetFormat(
                stackLabels = listOf("linear","square"),
                /*ここでその他のDataSetフォーマット指定*/
            )
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allBarsEntries: MutableMap<String, MutableList<BarEntry>> = mutableMapOf(
            "stack" to makeStackBarChartData(x, y)
        )

        //②~⑦グラフの作成
        setupBarChart(allBarsEntries, barChart, barChartFormat, barDataSetFormat, context)

image.png

複数の棒を横に並べて表示するとき

複数折れ線と同様、1本の時との違いは、
・DataSetフォーマットを棒の本数だけ指定
・Entryに棒の本数だけデータ格納
となります。
※横軸間隔が一定のときのみ使用可能なので、ご注意ください

        //表示用サンプルデータの作成
        val x = listOf<Float>(1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f)//X軸データ
        val y1 = x.map{it}//Y軸データ1(X軸の1乗)
        val y2 = x.map{it*it}//Y軸データ2(X軸の2乗)

        //Chartフォーマット
        var barChartFormat = BarChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var barDataSetFormat =  mapOf(
            "linear" to BarDataSetFormat(/*ここでDataSetフォーマット指定*/),
            "square" to BarDataSetFormat(/*ここでDataSetフォーマット指定*/)
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allBarsEntries: MutableMap<String, MutableList<BarEntry>> = mutableMapOf(
            "linear" to makeBarChartData(x, y1),
            "square" to makeBarChartData(x, y2)
        )

        //②~⑦グラフの作成
        setupBarChart(allBarsEntries, barChart, barChartFormat, barDataSetFormat, context)

image.png

3. ローソク足グラフの実装方法

「ローソク足グラフ」とは株価のチャートで使われているグラフで、
箱ひげ図の代用にも使えます

実装方法を、レイアウト(.xml)と処理部(.kt)にわけて解説します。

レイアウトの実装

LineChartのときと同様に、CandleStickChart用のウィジェットをレイアウト(例:activity_main.xml)中に組み込みます。

activity_main.xml
  :
<com.github.mikephil.charting.charts.CandleStickChart
    android:id="@+id/candleStickChartExample"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
  :

実行コードの実装

ローソク足グラフ作成メソッドを呼び出す処理を、Kotlinファイル(例:MainActivity.kt)内に実装します。
・横軸が数値のとき
・横軸を時系列にしたいとき
で例を分けて解説します。

横軸が数値のとき

Entryへのデータ格納時に、X軸の値、Y最大値、Y最小値、Y開始値、Y終了値の5種類の引数を指定する必要があることに注意してください

        //表示用サンプルデータの作成//
        val x = listOf<Float>(2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f)//X軸データ
        val yHigh = x.map{it * 2}//Y軸データ(最大値)
        val yLow = x.map{it}//Y軸データ(最小値)
        val yOpen = x.map{it + 1}//Y軸データ(開始値)
        val yClose = x.map{it + 2}//Y軸データ(終了値)

        //Chartフォーマット
        var candleChartFormat = CandleChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット(カテゴリ名のMap)
        var candleDataSetFormat = CandleDataSetFormat(/*ここでDataSetフォーマット指定*/)

        //①Entryにデータ格納(カテゴリ名のMap)
        val candleEntries = makeCandleChartData(x, yHigh, yLow, yOpen, yClose)

        //②~⑦グラフの作成
        setupCandleStickChart(candleEntries, candleStickChart, candleChartFormat, candleDataSetFormat, context)

image.png

横軸を時系列にしたいとき

横軸を時系列にしたいときは、Entryにデータ格納するメソッドを、
makeCandleChartData() → makeDateCandleChartData()
に変更します

※timeAccuracyプロパティ指定はできません

        //表示用サンプルデータの作成//
        //X軸データ(時間)
        val sdf: SimpleDateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
        val x = listOf<Date>(
            sdf.parse("2020/09/01 00:00:00"),
            sdf.parse("2020/09/01 06:00:00"),
            sdf.parse("2020/09/01 12:00:00"),
            sdf.parse("2020/09/01 18:00:00"),
            sdf.parse("2020/09/02 00:00:00"),
            sdf.parse("2020/09/02 06:00:00"),
            sdf.parse("2020/09/02 12:00:00"),
            sdf.parse("2020/09/03 18:00:00"),
        )
        val ySeed = listOf<Float>(2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f)//Y軸データ生成用
        val yHigh = ySeed.map{it * 2}//Y軸データ(最大値)
        val yLow = ySeed.map{it}//Y軸データ(最小値)
        val yOpen = ySeed.map{it + 1}//Y軸データ(開始値)
        val yClose = ySeed.map{it + 2}//Y軸データ(終了値)

        //Chartフォーマット
        var candleChartFormat = CandleChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット
        var candleDataSetFormat = CandleDataSetFormat(/*ここでDataSetフォーマット指定*/)

        //①Entryにデータ格納
        val candleEntries = makeDateCandleChartData(x, yHigh, yLow, yOpen, yClose)

        //②~⑦グラフの作成
        setupCandleStickChart(candleEntries, candleStickChart, candleChartFormat, candleDataSetFormat, context)

image.png

4. 円グラフの実装方法

円グラフの実装方法を、レイアウト(.xml)と処理部(.ktあるいは.java)にわけて解説します。

レイアウトの実装

LineChartのときと同様に、PieChart用のウィジェットをレイアウト(例:activity_main.xml)中に組み込みます。

activity_main.xml
  :
<com.github.mikephil.charting.charts.PieChart
    android:id="@+id/pieChartExample"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
  :

実行コードの実装

円グラフ作成メソッドを呼び出す処理を、Kotlinファイル(例:MainActivity.kt)内に実装します。

        //表示用サンプルデータの作成//
        val dimensions = listOf("A", "B", "C", "D")//分割円の名称(String型)
        val values = listOf(1f, 2f, 3f, 4f)//分割円の大きさ(Float型)

        //Chartフォーマット
        var pieChartFormat = PieChartFormat(/*ここでChartフォーマット指定*/)
        //DataSetフォーマット
        var pieDataSetFormat = PieDataSetFormat(/*ここでDataSetフォーマット指定*/)

        //①Entryにデータ格納
        val pieEntries = makePieChartEntries(dimensions, values)

        //②~⑦グラフの作成
        setupPieChart(pieEntries, pieChart, "PieChart", pieChartFormat, pieDataSetFormat)

image.png

UIフォーマットの指定方法

グラフ全体(Chart)に適用するものと、カテゴリ毎(DataSet)に適用するものを分けて解説します。

適用方法は、下のようにコンストラクタに引数を与えることで各プロパティ内容が指定できます。
(指定がない場合は前章「使用方法」で図示しているようなUIとなります)

        //表示用サンプルデータの作成//
        //X軸データ(時間)
        val sdf: SimpleDateFormat = SimpleDateFormat("yyyy/M")
        val x = listOf<Date>(
            sdf.parse("1990/1"),
            sdf.parse("1995/1"),
            sdf.parse("2000/1"),
            sdf.parse("2005/1"),
            sdf.parse("2010/1"),
            sdf.parse("2015/1"),
            sdf.parse("2018/1")
        )
        val y1 = listOf(6.0f, 7.6f, 10.3f, 13.0f, 15.0f, 18.2f, 20.6f)//Y軸データ1(アメリカ)
        val y2 = listOf(0.4f, 0.7f, 1.2f, 2.3f, 6.1f, 11.2f, 13.4f)//Y軸データ2(中国)
        val y3 = listOf(3.1f, 5.4f, 4.8f, 4.8f, 5.7f, 4.4f, 5.0f)//Y軸データ3(日本)
        val y4 = listOf(1.6f, 2.6f, 1.9f, 2.8f, 3.4f, 3.4f, 4.0f)//Y軸データ4(ドイツ)

        ///////////ここでChartフォーマットの指定///////////
        var lineChartFormat = LineChartFormat(
            legendTextSize = 10f,
            description = "主要国GDP推移",
            descriptionTextSize = 15f,
            descriptionYOffset = -10f,
            bgColor = Color.DKGRAY,
            xAxisDateFormat = SimpleDateFormat("yyyy年"),
            toolTipDateFormat = SimpleDateFormat("yyyy年"),
            toolTipDirection = "xy",
            toolTipUnitY = "兆ドル"
        )
        ///////////ここでDataSetフォーマットの指定(カテゴリ名のMap)///////////
        var lineDataSetFormat =  mapOf(
            "アメリカ" to LineDataSetFormat(//線1のDataSetフォーマット指定
                lineColor = UNIVERSAL_BLUE,
                lineWidth = 2f
            ),
            "中国" to LineDataSetFormat(//線2のDataSetフォーマット指定
                lineColor = UNIVERSAL_RED,
                lineWidth = 2f
            ),
            "日本" to LineDataSetFormat(//線3のDataSetフォーマット指定
                lineColor = UNIVERSAL_SKYBLUE,
                lineWidth = 2f
            ),
            "ドイツ" to LineDataSetFormat(//線4のDataSetフォーマット指定
                lineColor = Color.LTGRAY,
                lineWidth = 2f
            )
        )

        //①Entryにデータ格納(カテゴリ名のMap)
        val allLinesEntries: MutableMap<String, MutableList<Entry>> = mutableMapOf(
            "アメリカ" to makeDateLineChartData(x, y1, false),
            "中国" to makeDateLineChartData(x, y2, false),
            "日本" to makeDateLineChartData(x, y3, false),
            "ドイツ" to makeDateLineChartData(x, y4, false)
        )

        //②~⑦グラフの作成
        setupLineChart(allLinesEntries, lineChart, lineChartFormat, lineDataSetFormat, context)

image.png

GitHubのMainActivity.ktに実装例がいくつかある(特に後半の4メソッド)ので、こちらを参照頂けると分かりやすいかと思います

グラフ全体(Chart)に適用するUIフォーマット一覧

下記に、Chartフォーマットにおいて指定可能なプロパティ一覧を記載します。

「MPAndroidChartでのプロパティ名」列のリンクで、実際にどのようにUIが変わるかを図示しました(別記事)
※MPAndroidChartに対応が存在しない本パッケージオリジナルプロパティに関しては、本記事内で別途変化を図示しております。

適用対象 変更項目 Chartフォーマットでのプロパティ名 型 備考 MPAndroidChartでのプロパティ名 折れ線 棒グラフ ローソク足 円グラフ
凡例 マーク形状 legendFormat Legend.LegendForm? nullなら凡例表示なし .legend.form 〇 〇 〇 〇
凡例 文字色 legentTextColor Int? nullならデフォルト(黒) .legend.textColor 〇 〇 〇 〇
凡例 文字サイズ legendTextSize Float? nullならデフォルト .legend.textSize 〇 〇 〇 〇
説明ラベル 表示文字列 description String? nullなら説明ラベルなし .description.text 〇 〇 〇 〇
説明ラベル 文字色 descriptionTextColor Int? nullならデフォルト(黒) .description.textColor 〇 〇 〇 〇
説明ラベル 文字サイズ descriptionTextSize Float? nullならデフォルト .description.textSize 〇 〇 〇 〇
説明ラベル 横位置微調整 descriptionXOffset Float? nullならデフォルト .description.xOffset 〇 〇 〇 〇
説明ラベル 縦位置微調整 descriptionYOffset Float? nullならデフォルト .description.yOffset 〇 〇 〇 〇
背景 背景色 bgColor Int? nullならデフォルト(白) .setBackgroundColor() 〇 〇 〇 〇
タッチ操作 有効無効 touch Boolean .setTouchEnabled() 〇 〇 〇
X軸ラベル 表示有無 xAxisEnabled Boolean .xAxis.isEnabled 〇 〇 〇
X軸ラベル 文字色 xAxisTextColor Int? nullならデフォルト(黒) .xAxis.textColor 〇 〇 〇
X軸ラベル 文字サイズ xAxisTextSize Float? nullならデフォルト .xAxis.textSize 〇 〇 〇
X軸ラベル 時刻表示フォーマット xAxisDateFormat SimpleDateFormat? nullならM/d H:mm - 〇 〇 〇
左Y軸ラベル 表示有無 yAxisLeftEnabled Boolean .axisLeft.isEnabled 〇 〇 〇
左Y軸ラベル 文字色 yAxisLeftTextColor Int? nullならデフォルト(黒) .axisLeft.textColor 〇 〇 〇
左Y軸ラベル 文字サイズ yAxisLeftTextSize Float? nullならデフォルト .axisLeft.textSize 〇 〇 〇
左Y軸ラベル 表示下限 yAxisLeftMin Float? nullなら下限なし .axisLeft.axisMinimum 〇 〇 〇
左Y軸ラベル 表示上限 yAxisLeftMax Float? nullなら上限なし .axisLeft.axisMaximam 〇 〇 〇
右Y軸ラベル 表示有無 yAxisRightEnabled Boolean .axisRight.isEnabled 〇 〇 〇
右Y軸ラベル 文字色 yAxisRightTextColor Int? nullならデフォルト(黒) .axisRight.textColor 〇 〇 〇
右Y軸ラベル 文字サイズ yAxisRightTextSize Float? nullならデフォルト .axisRight.textSize 〇 〇 〇
右Y軸ラベル 表示下限 yAxisRightMin Float? nullなら下限なし .axisRight.axisMinimum 〇 〇 〇
右Y軸ラベル 表示上限 yAxisRightMax Float? nullなら上限なし .axisRight.axisMaximam 〇 〇 〇
拡大操作 拡大方向 zoomDirection String? "x", "y", "xy"
nullなら拡大無効
.isScaleXEnabled
.isScaleYEnabled
.setScaleEnabled()
〇 〇 〇
拡大操作 ピンチ操作有効 zoomPinch Boolean .setPinchZoom() 〇 〇 〇
ツールヒント 表示する軸 toolTipDirection String? "x", "y", "xy"
nullならツールヒントなし
.marker 〇 〇 〇
ツールヒント 時系列グラフのときのフォーマット toolTipDateFormat SimpleDateFormat? nullならM/d H:mm .marker 〇 〇 〇
ツールヒント X軸の単位 toolTipUnitX String デフォルトは単位なし("") .marker 〇 〇 〇
ツールヒント Y軸の単位 toolTipUnitY String デフォルトは単位なし("") .marker 〇 〇 〇
X軸表示法 時間軸スケールの正確性 timeAccuracy Boolean Trueなら時間軸を正確表示(ラベルは最大最小値のみ) - 〇
ラベル 文字色 labelColor Int? nullならデフォルト(黒) .setEntryLabelColor() 〇
ラベル 文字サイズ labelTextSize Float? nullならデフォルト .setEntryLabelTextSize() 〇
中央のテキスト 表示文字列 centerText String? nullなら中央テキストなし .centerText 〇
中央のテキスト 文字色 centerTextColor Int? nullならデフォルト(黒) .setCenterTextColor() 〇
中央のテキスト 文字サイズ centerTextSize Float? nullならデフォルト .setCenterTextSize() 〇
中央の穴 穴の半径 holeRadius Float? nullならデフォルト .holeRadius 〇
中央の穴 穴周辺の色が薄い部分の幅 transparentCircleRadius Float? nullならデフォルト .transparentCircleRadius 〇
中央の穴 穴の塗りつぶし色 holeColor Int? nullならデフォルト(黒) .setHoleColor() 〇
______________ ____________ ________________

本パッケージオリジナルメソッド

xAxisDateFormat

X軸の時刻表示フォーマットを変更します(時系列グラフのときのみ。デフォルトは"M/d H:mm")

var lineChartFormat = LineChartFormat(xAxisDateFormat = SimpleDateFormat("d日H時"))

image.png

timeAccuracy

前述した内容と同様に、時間軸スケールの正確性を指定します(時系列折れ線グラフのみ)

スケールを正確に表示し、時間ラベルは最大最小のみ表示
var lineChartFormat = LineChartFormat(timeAccuracy=true)

image.png

正確性は下がるが、時間ラベルを全て表示
var lineChartFormat = LineChartFormat(timeAccuracy=false)

image.png

toolTipDirection

ツールヒント表示なし
var lineChartFormat = LineChartFormat(toolTipDirection=null)

image.png

ツールヒントにX軸の値を表示
var lineChartFormat = LineChartFormat(toolTipDirection="x")

image.png

ツールヒントにY軸の値を表示
var lineChartFormat = LineChartFormat(toolTipDirection="y")

image.png

ツールヒントにX軸Y軸両方の値を表示
var lineChartFormat = LineChartFormat(toolTipDirection="xy")

image.png

toolTipDateFormat

ツールヒントの時刻表示フォーマットを変更します(時系列グラフのときのみ。デフォルトは"M/d H:mm")

ツールヒントの時刻表示フォーマットを"d日H時"にする
var lineChartFormat = LineChartFormat(
            toolTipDirection="xy",
            toolTipDateFormat = SimpleDateFormat("d日H時")
        )

image.png

toolTipUnitX

ツールヒントのX軸表示に付加する単位を指定します(デフォルトは単位なし)

ツールヒントのX軸単位を"日目"にする
var lineChartFormat = LineChartFormat(
            toolTipDirection="xy",
            toolTipUnitX = "日目"
        )

image.png

toolTipUnitY

ツールヒントのY軸表示に付加する単位を指定します(デフォルトは単位なし)

ツールヒントのY軸単位を"円"にする
var lineChartFormat = LineChartFormat(
            toolTipDirection="xy",
            toolTipUnitY = "円"
        )

image.png

カテゴリごと(DataSet)に適用するUIフォーマット一覧

下記に、DataSetフォーマット(線ごと、棒ごとetc.に指定)において指定可能なプロパティ一覧を記載します。

「MPAndroidChartでのプロパティ名」列のリンクで、実際にどのようにUIが変わるかを図示しました(別記事)
※MPAndroidChartに対応が存在しない本パッケージオリジナルプロパティに関しては、本記事内で別途変化を図示しております。

適用対象 変更項目 DataSetフォーマットでのプロパティ名 型 備考 MPAndroidChartでのプロパティ名 折れ線 棒グラフ ローソク足 円グラフ
値表示 値表示の有無 drawValue Boolean .setDrawValues() 〇 〇 〇 〇
値表示 値表示の文字色 valueTextColor Int? nullならデフォルト(黒) .valueTextColor 〇 〇 〇 〇
値表示 値表示の文字サイズ valueTextSize Float? nullならデフォルト .valueTextSize 〇 〇 〇 〇
値表示 値表示のフォーマット valueTextFormatter String? nullならデフォルト .valueFormatter 〇 〇 〇 〇
軸 左右軸どちらを使用するか axisDependency YAxis.AxisDependency? nullなら左Y軸 .axisDependency 〇 〇
線 線の色 lineColor Int? nullならデフォルト(水色) .color 〇
線 幅 lineWidth Float? nullならデフォルト .lineWidth 〇
線 補完方法 fittingMode LineDataSet.Mode? nullならデフォルト(直線補完) .mode 〇
データ点 表示有無 drawCircles Boolean .setDrawCircles() 〇
データ点 枠の色 circleColor Int? nullならデフォルト(水色) .setCircleColor() 〇
データ点 枠の半径 circleRadius Float? nullならデフォルト .circleRadius 〇
データ点 穴の塗りつぶし色 circleHoleColor Int? nullならデフォルト(白) .circleHoleRadius 〇
データ点 穴の半径 circleHoleRadius Float? nullならデフォルト .circleHoleColor 〇
棒 棒の色 barColor Int? nullならデフォルト(水色) .color 積み上げ以外
棒 各積み上げ棒の色リスト barColors List .colors 積み上げ
棒 積み上げ棒のカテゴリ名 stackLabels List? nullならデフォルト(ラベル名で埋める) .stackLabels 積み上げ
ローソク細線 線の色 shadowColor Int .shadowColor 〇
ローソク細線 幅 shadowWidth Float? nullならデフォルト .shadowWidth 〇
ローソク太線 減少時の色 decreasingColor Int .decreasingColor 〇
ローソク太線 減少時の塗りつぶし形式 decreasingPaint Paint.Style? nullなら塗りつぶしあり .decreasingPaintStyle 〇
ローソク太線 増加時の色 increasingColor Int? nullなら色なし .increasingColor 〇
ローソク太線 増加時の塗りつぶし形式 increasingPaint Paint.Style? nullなら塗りつぶしなし .increasingPaintStyle 〇
分割円 分割円の色 colors List .colors 〇
______________ ____________ ________________

本パッケージオリジナルメソッド

valueTextFormatter

値表示のフォーマットを指定します
(MPAndroidChartではValueFormatterクラス内をオーバーライドして指定しますが、本パッケージではString型で指定します)

整数(小数点以下0位)で表示
var lineDataSetFormat = mapOf(
            "linear" to LineDataSetFormat(
                drawValue = true,
                valueTextSize = 12f,
                valueTextFormatter = "%.0f"
            )
        )

image.png

小数点以下2位で表示
var lineDataSetFormat = mapOf(
            "linear" to LineDataSetFormat(
                drawValue = true,
                valueTextSize = 12f,
                valueTextFormatter = "%.2f"
            )
        )

image.png

小数点以下1位+"円"で表示
var lineDataSetFormat = mapOf(
            "linear" to LineDataSetFormat(
                drawValue = true,
                valueTextSize = 12f,
                valueTextFormatter = "%.1f円"
            )
        )

image.png

参考

MPAndroidChartにおける時系列グラフの作り方

時系列でないグラフの場合

時系列ではない(X軸がFloat型の数値)グラフでは、

val x = listOf<Float>(1f, 2f, 3f, 5f, 8f, 13f, 21f, 34f)
val y = x.map{it*it}
var entryList = mutableListOf<Entry>()
for(i in x.indices){
    entryList.add(Entry(x[i], y[i]))
}

というように、Entryの第一項にX軸の値を、第二項にY軸の値を入れれば、入力したX軸の値に合わせて

val lineDataSets = ListOf<ILineDataSet>(LineDataSet())
lineChart.data = LineData(lineDataSets)

とすれば、下図のように両軸共に正しくプロットされます。
image.png

時系列グラフの場合

X軸がDate(java.util.Date)型の数値のとき、Entryの第一項にX軸の値を入力することができません。

        val sdf: SimpleDateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
        val x = listOf<Date>(
            sdf.parse("2020/09/01 00:00:00"),
            sdf.parse("2020/09/01 06:00:00"),
            sdf.parse("2020/09/01 12:00:00"),
            sdf.parse("2020/09/01 18:00:00"),
            sdf.parse("2020/09/02 00:00:00"),
            sdf.parse("2020/09/02 06:00:00"),
            sdf.parse("2020/09/02 12:00:00"),
            sdf.parse("2020/09/02 18:00:00"),
        )
        val y = listOf<Float>(1f, 2f, 3f, 5f, 8f, 13f, 21f, 34f)
        //Entryにデータ格納
        var entryList = mutableListOf<Entry>()
        for(i in x.indices){
            entryList.add(
                Entry(x[i], y[i])//←ここでエラーが出る
            )
        }
        val lineDataSets = listOf<ILineDataSet>(LineDataSet(entryList,"label"))
        lineChart.data = LineData(lineDataSets)

Entryの第一項にはFloat型を格納する必要があるので、indexをFloat型に変換して格納します(代わりに第3項に日付データを保持しておく)

          :
        //Entryにデータ格納
        var entryList = mutableListOf<Entry>()
        for(i in x.indices){
            entryList.add(
                Entry(i.toFloat(), y[i], x[i])
            )
        }
          :

しかしこのままでは
image.png!

のように、X軸のラベルがただのインデックスとなってしまい、時刻が何時だか分かりません。
下記のように、時刻を文字列リストに変換してX軸ラベルに指定することで、ラベルを表示することができます。

          :
        //X軸の値を日付型→文字列に変換し、X軸ラベルに指定
        val xStr = x.map { SimpleDateFormat("M/d H:mm").format(it)}
        lineChart.xAxis.valueFormatter = IndexAxisValueFormatter(xStr)

        val lineDataSets = listOf<ILineDataSet>(LineDataSet(entryList,"label"))
        lineChart.data = LineData(lineDataSets)

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android] Realtime Databaseを使ってみる

前回はFirebase Authenticationを触りましたが、今回はRealtime Databaseを使ってみたい思います。

Firebase設定

Realtime Databeseを使用するには、Databaseルールというものを記述しなければならない。

{
  "rules": {
    "foo": {
      ".read": true,
      ".write": false
    }
  }
}

上記のルールは、あらゆるユーザーが/foo/以下の読み取りを許可するというものになります。/foo/以下というのは、

{
  "rules": {
    "foo": {
      ".read": true,
      ".write": false,
      "hoo": {
        ".read": false,
        ".write": false
      }
    }
  }
}

この場合、/hoo/の読み取りを許可しない設定にしていても、権限はデータベース内の浅いパスの方が優先されるため、/hoo/以下の読み取りが可能になります。

前回、Authenticationの設定をしてのでユーザごとにアクセス制御ができるみたいです。実際に設定したルールは以下です。

{
  "rules": {
    "foo": {
      "$uid": {
        ".read": "auth != null && auth.uid == $uid",
        ".write": "auth != null && auth.uid == $uid"
      }
    }
  }
}

ログインしたユーザーだけが自分だけのデータを閲覧できるルールを記述しました。

実装

Gradle

implementation 'com.google.firebase:firebase-database-ktx:19.4.0'

書き込み

データの読み書きを行うには、DatabaseReferenceインスタンスを使用します。

class LogoutActivity : AppCompatActivity() {

    private lateinit var auth: FirebaseAuth
    private lateinit var database: DatabaseReference

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        auth = Firebase.auth
        // インスタンス生成
        databaseRef = Firebase.database.reference
        // (ログインしていれば)データ追加
        // root/foo/auth.uid/
        button.setOnClickListener {
            auth.uid?.let {
                database.child("foo").child(it).setValue(User("hoge", "12"))
            }
        }
        // ...
    }

setValue()で、データを追加したいパスにデータを追加します。今回は、/foo/auth.uid/にユーザー情報(name, age)を追加してみました。パスの指定は.child()で指定します。(他の方法知りません)

Firebaseコンソールで確認

読み取り

データ読み取りを行うには、作成したdatabase.referenceにValueEventListenerを追加する必要があります。公式から抜粋

        val postListener = object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                // Get Post object and use the values to update the UI
                val post = dataSnapshot.value
                Log.d("TAG", post.toString())
                // ...
            }

            override fun onCancelled(databaseError: DatabaseError) {
                // Getting Post failed, log a message
                Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
                // ...
            }
        }
        // ...
        database.child("foo").child(auth.uid!!).addValueEventListener(postListener)
        // ...

onDataChage()メソッドはリスナーがアタッチされた時に1回トリガーされ、データが変更されるたびにトリガーされます。読み取りができない場合(権限がないなど)は、onCancelled()が呼び出される。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む