[Android] アスペクト比が固定のビューを作成する

2016.02.24

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

アスペクト比を設定すれば高さが決まる

そんなビューを作ります。

カスタムビュークラスの作成

まずはカスタムクラスを作成しましょう。
例によってコンストラクタを記述します。

/**
 * 高さを動的に決めるカスタムビュー
 * 利用する側がアスペクト比(高さ ÷ 横幅)をセットする
 */
public class DynamicHeightView extends View {

    public DynamicHeightView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

属性の定義

res/values/attrs.xml を作成し、aspectRatio というカスタム属性を追加します。
XML での属性追加に関してはこちらの記事をご覧ください。

<resources>
    <declare-styleable name="DynamicHeightView">
        <attr name="aspectRatio" format="float"/>
    </declare-styleable>
</resources>

コンストラクタの実装

作成したカスタムビューのコンストラクタに、値の取得と設定処理を実装します。

private float mAspectRatio;

public DynamicHeightView(Context context, AttributeSet attrs) {
    super(context, attrs);

    TypedArray a = context.getTheme().obtainStyledAttributes(
            attrs,
            R.styleable.DynamicHeightView,
            0, 0);

    try {
        mAspectRatio = a.getFloat(R.styleable.DynamicHeightView_aspectRatio, 0);
    } finally {
        a.recycle();
    }

    if (mAspectRatio < 0) mAspectRatio = 0;
}

onMeasure() のオーバーライド

onMeasure() メソッドは View の高さと横幅を決定するメソッドです。
このメソッドをオーバーライドし、動的に高さを求めます。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthSize = MeasureSpec.getSize(widthMeasureSpec); // *1
    int heightSize = (int) (widthSize * mAspectRatio);  // *2

    setMeasuredDimension(widthSize, heightSize);  // *3

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);

    super.onMeasure(widthMeasureSpec, heightMeasureSpec); // *4
}

解説

まず、引数で渡された widthMeasureSpec からビューの横幅を取得します。(*1

次にメンバ変数の mAspectRatio を利用して、高さを動的に設定します。(*2

そして setMeasuredDimension() メソッドで、横幅と高さを決定します。(*3
onMeasure() メソッドをオーバーライドした場合、setMeasuredDimension() は必ず実行しなければなりません。

それ以降の部分ではスーパークラスの onMeasure() に渡す MeasureSpec を作成しています。
ここではビューのサイズから正しい値の MeasureSpec を作成し、スーパークラスの onMeasure() メソッドを呼び出す必要があります。(*4
これを行わなかった場合、ビューの表示時にレイアウトが崩れることがあるので注意しましょう。

layout xml での設定

<jp.co.ketanaka.app.dynamicheightview.view.DynamicHeightView
    android:layout_width="200dp"
    android:layout_height="0dp"
    android:background="#00ff00"
    app:aspectRatio="0.75"
    />

作成したカスタムビューを指定し、aspectRatio に任意の値を設定します。
この値に x を指定した場合、「高さは横幅の x 倍」というビューができあがります。

実行

app:aspectRatio="0.75" (4 : 3)

1


app:aspectRatio="1.0" (1 : 1)

2


app:aspectRatio="2.0" (1 : 2)

3

リンク

ミレニアム・ファルコン製作日記 #6

6 号 表紙

mfd_6_1

パーツ

mfd_6_3

mfd_6_4

mfd_6_2

成果

mfd_6_5

mfd_6_6

mfd_6_7

今回の作業は以下の 4 つでした。

  • コクピット外装を仮組みする
  • コクピット後部計器パネルにステッカーを貼る
  • 操作レバーを仮組みする
  • コクピットの取り付け位置を確認する

今回はコクピットの回でした。
内装・外装共にパーツが多く、かなり仕上がってきたように感じます。

外装部分ではコクピット連結リングをはめ込むのにコツが必要で、なかなか苦戦しました。
ただしこれはまだ仮組みです。

ステッカーも徐々に慣れてきて、今回は綺麗に貼ることができました。
そろそろステッカーパダワンは卒業ですね。
ヨーダさん、三つ編み切っちゃってください!

操作レバーの仮組みでは指先を痛めました。
湾曲したレバー 3 つがなかなかの曲者で、前方制御コンソールの小穴に差し込むときにとても力が入れづらかったのです。
I can feel my anger.

少し見づらいですが操縦レバーが 4 つ付いているのがわかると思います。
私が商売道具の指先を痛めてまで取り付けたレバーです。
凝視してやってください。

今回からは薄刃ニッパーが必要となりました。
私は持っていないため、カッターでパーツを切り取りました。
プラスドライバーはパーツの一部として提供してくれたのですが、ニッパーは各自で用意ですか。
そうですか・・・

それではまた次回。

May the Force be with you!