この夏こそカスタムViewを作くって貯めよう-基本編

#カスタムViewを作ろう
今回は[ここ](http://nanapi.co.jp/blog/2014/07/30/android_multi_diplay/)で軽くふれた三角形を描画するカスタムビューを例題にカスタムビューの作り方の基本について説明します。

今後、暇をみていくつかの作例を載せたいと思います。

カスタムViewと一口にいってもImageViewを継承して作ったり、Viewクラスそのものを継承してonDrawで独自の描画持たせたり色々なパターンがありますが、カスタムViewを丁寧に作っておくと次回の開発が楽になりますよ。

#関連記事
[styleableの属性についてまとめました](http://qiita.com/Hoshi_7/items/57c3a79c43efe05b5368)

#この記事でつくるカスタムビュー

 - [サンプルアプリをDeployGateに用意しました。](https://dply.me/u4gi97)
 - [サンプルコードをコードブレイクに用意しました。](http://codebreak.com/git/redno8/MakeCustomView/)

注:Styleableの属性のリファレンスと同じサンプルアプリです。

![device-2014-08-07-115111.png](https://qiita-image-store.s3.amazonaws.com/0/43688/5c220cb1-def3-9ae9-d186-0200c1950148.png)

単純に三角形を描画できるだけの簡単なカスタムビューを作る。

#カスタムView実装の流れ
サンプルソースはこの項目の後に載せていますので参照してください。

##1.attrsの設計
実現したいViewに対してXMLから設定したパラメタの当たりを付けます。
それらをattrs.xmlに記述することでXMLで記述したパラメーターをView側に渡すことができます。
設定できるパラメタについては近いうちに別途記事でまとめます。
まとめたら、こちらからリンクも貼りますのでストックしていただければ通知いたします。

##2.attrs.xml作成
Viewで必要になりそうなパラメタをattrs.xmlに定義します。

サンプルソースから抜粋して説明します。

```xml
<!-- 
ストロークの幅ですdimensionを指定することで10dpなどの単位を付けた値が利用できます。
赤い三角形ではこの値が10dpに設定されています 。
-->
<attr name="stroke_width" format="dimension" />
<!-- 
塗りつぶしの色です。
colorを指定すると#ddaa0000や#aa0000のcolor定義が利用できます。
res/values/color.xmlに定義した`@color/○○○`のリソースも設定可能です。
-->
<attr name="fill_color" format="color" />
<!-- ストロークの色です -->
<attr name="stroke_color" format="color" />
<!-- ストロークの描画フラグです青い三角形はfalseなのでストロークが描画されません -->
<attr name="stroke_flg" format="boolean" />
```

##3.Viewの実装

TypedArrayでattrs.xmlで指定したソースを受け取り、onSizeChengedで描画のPathの指定を行います。
onDrawでは描画のみは極力行わないようにしましょう。
onDrawで重い計算などをやらせてしまうと、アニメーションさせる場合などに、画面が乱れる原因になりやすいです。

```java
TypedArray tArray =
    context.obtainStyledAttributes(
        attrs,
        R.styleable.TriangleView
);
```

コンストラクタから、setInitPaint()というメソッドを呼び、使いますPaintオブジェクトの初期化をしています。
塗りつぶし用のPaintとストローク用のペイントに分けています。

```java
    private void setInitPaint(){
        mFillPaint = new Paint();
        mFillPaint.setAntiAlias(true);
        mFillPaint.setColor(mFillColor);

        mStrokePaint = new Paint();
        mStrokePaint.setAntiAlias(true);
        mStrokePaint.setStrokeWidth(mStrokeWidth);
        mStrokePaint.setStyle(Paint.Style.STROKE);

        mStrokePaint.setColor(mStrokeColor);

    }
```

ソースが単純なのでサンプルを読んでいただけば問題ないと思いますが、
onSizedChangedで三角形のPathを設定し、onDrawで描画しています。

回転を設定するangle属性をattrsに用意し、onSizedChangedで角度を考慮したパスを引けば三角形を回転させることもできます。

またgetter、setterを用意しコードからのViewの生成時にも属性を設定できるようにしています。

##4.layoutXMLの作成

あとはlayoutXMLにTriangleViewを置くだけです。置き方はサンプルソースを確認していただくのが良いと思います。

```
xmlns:app="http://schemas.android.com/apk/res/(パッケージ名)"
```
こちらを属性として記載することで自アプリのattrsに設定した属性をapp:○○○という形で利用できりょうになります。この後のサンプルを確認してください、親であるLinearLayoutに設定しているのが確認できます。

と描いているサイトが多いのですが、こっちがいいでしょ。
サンプルはこちらになってないので注意してください。

```
xmlns:app="http://schemas.android.com/apk/res-auto"
```

#サンプルソース

```attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="TriangleView">
        <attr name="stroke_width" format="dimension" />
        <attr name="fill_color" format="color" />
        <attr name="stroke_color" format="color" />
        <attr name="stroke_flg" format="boolean" />
    </declare-styleable>
</resources>
```

```activity_main.xml
<!-- xmlns:appの後ろでこのアプリ独自の属性を設定できるようにしている。 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res/jp.sample.app.densitytest"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:orientation="vertical"
    >

<!-- app:○○○でattrsで設定した属性を設定できる。 -->

    <jp.sample.app.densitytest.TriangleView
        android:id="@+id/triangle"
        android:layout_width="130dp"
        android:layout_height="100dp"
        android:layout_centerHorizontal="true"
        app:stroke_flg = "false"
        app:fill_color = "#0000FF"
        />
    <jp.sample.app.densitytest.TriangleView
        android:id="@+id/triangle2"
        android:layout_width="130dp"
        android:layout_height="100dp"
        android:layout_centerHorizontal="true"
        app:stroke_flg = "true"
        app:fill_color = "#ff9999"
        app:stroke_color = "#dd5556"
        app:stroke_width = "10dp"
        />
</LinearLayout>
```

```TriangleView.java
package jp.sample.app.densitytest;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by yusukehoshi on 2014/07/01.
 */
public class TriangleView extends View {

    private int mDefaultStrokeWidth = 2;

    private int mGraphHeight = 100;
    private int mGraphWidth = 100;
    private int mStrokeWidth = 1;
    private int mFillColor = Color.WHITE;
    private int mStrokeColor = Color.BLACK;
    private boolean mStrokeFlg = true;

    /**
     * 中心座標
     */
    private int mCenterX;
    private int mCenterY;

    private float mDensity;
    private Paint mFillPaint;
    private Paint mStrokePaint;
    private Path mPath = new Path();

    public int getStrokeWidth() {
        return mStrokeWidth;
    }

    public void setmStrokeWidth(int strokeWidth) {
        this.mStrokeWidth = strokeWidth;
    }

    public int getFillColor() {
        return mFillColor;
    }

    public void setFillColor(int fillColor) {
        this.mFillColor = fillColor;
    }

    public int getStrokeColor() {
        return mStrokeColor;
    }

    public void setStrokeColor(int strokeColor) {
        this.mStrokeColor = strokeColor;
    }

    public boolean isStrokeFlg() {
        return mStrokeFlg;
    }

    public void setStrokeFlg(boolean strokeFlg) {
        this.mStrokeFlg = strokeFlg;
    }

    public int getCenterX() {
        return mCenterX;
    }

    public void setCenterX(int centerX) {
        this.mCenterX = centerX;
    }

    public int getCenterY() {
        return mCenterY;
    }

    public void setCenterY(int centerY) {
        this.mCenterY = centerY;
    }

    public TriangleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TriangleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setWillNotDraw(false);

        mDensity = getContext().getResources().getDisplayMetrics().density;
        mStrokeWidth = (int) (mDefaultStrokeWidth * mDensity);

        if (attrs == null) {
            setInitPaint();
            return;
        }

        TypedArray tArray =
                context.obtainStyledAttributes(
                        attrs,
                        R.styleable.TriangleView
                );

        mStrokeWidth = tArray.getDimensionPixelSize(R.styleable.TriangleView_stroke_width, mStrokeWidth);
        mFillColor = tArray.getColor(R.styleable.TriangleView_fill_color, mFillColor);
        mStrokeColor = tArray.getColor(R.styleable.TriangleView_stroke_color, mStrokeColor);
        mStrokeFlg = tArray.getBoolean(R.styleable.TriangleView_stroke_flg, true);

        setInitPaint();
    }

    private void setInitPaint(){
        mFillPaint = new Paint();
        mFillPaint.setAntiAlias(true);
        mFillPaint.setColor(mFillColor);

        mStrokePaint = new Paint();
        mStrokePaint.setAntiAlias(true);
        mStrokePaint.setStrokeWidth(mStrokeWidth);
        mStrokePaint.setStyle(Paint.Style.STROKE);

        mStrokePaint.setColor(mStrokeColor);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mCenterX = w / 2;
        mCenterY = h / 2;

        mGraphWidth = w;
        mGraphHeight = h;

        int drawLinePadding = 0;
        if(mStrokeFlg) {
            drawLinePadding = mStrokeWidth;
        }

        mPath.reset();
        mPath.moveTo(drawLinePadding,mGraphHeight - drawLinePadding);
        mPath.lineTo(mGraphWidth - drawLinePadding,mGraphHeight - drawLinePadding);
        mPath.lineTo(mCenterX,drawLinePadding);
        mPath.close();

    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        drawTriangle(canvas);
    }

    private void drawTriangle(Canvas canvas){


        canvas.drawPath(mPath,mFillPaint);
        if(mStrokeFlg){
            canvas.drawPath(mPath,mStrokePaint);
        }
    }
}

```