Android で範囲スライダーと棒グラフ(RangeSlider BarGraph)のサンプルアプリ

経緯

料金のスライダーと価格帯別の棒グラフを表示する。スライダーで料金の上限と下限を絞り込むことができる。そして絞り込んだ価格帯の棒グラフと、除外された価格帯の棒グラフの色を変更したい。という要望があった。調査用のサンプルアプリを実装したので備忘録としてまとめておく。

環境

  • Android Studio Arctic Fox | 2020.3.1 Patch 1
  • Java 11 ※まだ Kotlin がよくわかっていない

サンプルアプリ

サンプルアプリのソース

https://github.com/ka-yamao/range-slider-bar-graph

スライダー(RangeSlider)

src/main/res/layout/main_fragment.xml

<com.google.android.material.slider.RangeSlider
            android:id="@+id/rangeSlider"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:valueFrom="0.0"    ① 値の下限
            android:valueTo="18.0"     ② 値の上限
            android:stepSize="1.0"      ③ ステップの値 これを設定すると、スライダーに点のデザインが表示される。
            app:trackColor="#177bcb"
            app:trackColorActive="@color/black"
            app:thumbRadius="15dp"
            app:thumbColor="@color/white"
            app:thumbStrokeColor="#177bcb"
            app:thumbStrokeWidth="2dp"
            app:thumbElevation="0dp"
            app:haloColor="@color/transparent"  ④ デフォルトでポインター(thumb)の周りに円光が表示されるけど、表示したくないため透明にした。オーラみたいなやつ
            app:labelBehavior="gone"      ⑤★デフォルトでは、thumb の上にツールチップが表示されるが、非表示にするには ここを gone 
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent" />

棒グラフ(BarGraph) ※独自のカスタムView

  • 独自に View をカスタマイズした レイアウト の xml
src/main/res/layout/main_fragment.xml

<com.c.local.myapplication.ui.main.BarGraph
            android:id="@+id/barGraph"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:layout_marginHorizontal="20dp"
            android:layout_marginBottom="22dp"
            app:graphValues="@array/graph_sample_data"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/rangeSlider" />
  • 上記の レイアウトにいくつか追加した。
src/main/res/values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="BarGraphView">
        <attr name="graphValues" format="integer" />
        <attr name="minIndex" format="integer" />
        <attr name="maxIndex" format="integer" />
        <attr name="activeColor" format="color" />
        <attr name="inactiveColor" format="color" />
    </declare-styleable>

</resources>
  • コンストラクターでレイアウトの xml で設定するデータを読み込む
  • onDraw だけオーバーライドしてグラフを作成
src/main/java/com/c/local/myapplication/ui/main/BarGraph.java

public class BarGraph extends View {

    // グラフデータ
    private int[] mGraphValues;
    // 下限 index
    private int mMinIndex;
    // 上限 index
    private int mMaxIndex;
    // アクティブカラー
    private int mActiveColor;
    // インアクティブカラー
    private int mInactiveColor;

    /**
     * コンストラクター
     * Viewのクラス内では、getContext()でcontextの取得ができる
     *
     * @param context
     */
    public BarGraph(@NonNull Context context) {
        this(context, null);
    }

    public BarGraph(@NonNull Context context, @NonNull AttributeSet attrs) {
        this(context,
                attrs,
                R.attr.OriginalBarGraphView);
    }

    public BarGraph(@NonNull Context context, @NonNull AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // xml 設定データ
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.BarGraphView, defStyleAttr, 0);
        // データ配列の初期化
        mGraphValues = new int[]{};
        // 下限インデックス
        mMinIndex = typedArray.getInt(R.styleable.BarGraphView_minIndex, 0);
        int maxIndex = typedArray.getInt(R.styleable.BarGraphView_minIndex, -1);
        // 上限インデックス
        if (maxIndex > 0) {
            mMaxIndex = maxIndex;
        } else if (mGraphValues.length > 0) {
            // データが存在したら配列のサイズ - 1
            mMaxIndex = mGraphValues.length - 1;
        }
        // 活性カラー
        mActiveColor = typedArray.getColor(R.styleable.BarGraphView_activeColor, context.getColor(R.color.lightBlue));
        // 非活性カラー
        mInactiveColor = typedArray.getColor(R.styleable.BarGraphView_inactiveColor, context.getColor(R.color.grey20));
    }

    /**
     * 描画
     */
    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // Viewの幅、高さを知る
        int width = this.getWidth();
        int height = this.getHeight();
        // ペイント
        Paint paint = new Paint();      
        // データ数
        int dataSize = mGraphValues.length;
        // TODO 棒線との間に間隔を開けるので データ数 + X (※この数値を上げると棒線の間隔が広くなる)
        int splitNum = dataSize + 2;
        // 棒線の太さ
        paint.setStrokeWidth((float) width / splitNum);
        // 最大値
        int maxValue = Arrays.stream(mGraphValues).reduce(Math::max).getAsInt();
        float unit = (float) maxValue / height;
        // データ数のループ
        for (int i = 0; i < mGraphValues.length; i++) {
            // 棒線のスタート位置
            final float startX = (width / splitNum / 2) + i * width / dataSize;
            // 棒線の高さ計算
            final float stopY = height - (mGraphValues[i] / unit);
            // 高さ
            final float startY = height;
            // 棒線のカラー
            if (i < mMinIndex || i > mMaxIndex) {
                paint.setColor(mInactiveColor);
            } else {
                paint.setColor(mActiveColor);
            }
            // 線を引く
            canvas.drawLine(startX, startY, startX, stopY, paint);
        }
    }

    public void setGraphValues(int[] mGraphValues) {
        this.mGraphValues = mGraphValues;
    }

    public void setMinIndex(int mMinIndex) {
        this.mMinIndex = mMinIndex;
    }

    public void setMaxIndex(int mMaxIndex) {
        this.mMaxIndex = mMaxIndex;
    }
}

課題

  • スライダーにステップごとに白い点がある。ステップの値android:stepSize="1.0"を指定すると表示される。styleを調整するしか削除はできなさそう。
  • ポインターの止まる位置と、棒グラフの位置が微妙にずれてしまう。
    • 棒グラフの棒線と棒線の間に間隔がある。この間隔の白い分だけ少しずつずれてしまう。下記添付ファイルを参照
    • このズレを解消するには、価格帯 n 個ある棒線を裏では n+2 個にする。追加した仮想の2つの棒線幅分で n個の棒線の間隔幅を補い。スライダーの開始終了を位置とグラフを調整する必要がありそう。