CustomViewの基本を学ぶ

#CustomViewの基本
android超初心者がCustomViewの基本を学びました
初心者なため、ここはこうしたらいい、このやり方は良くないなどのコメントをいただけるととても嬉しいです！！！

RecycleViewの基本はここにまとめてあります！
[Main Activityの基本(Recycler view編)](http://qiita.com/hoonokonoko/items/bc107e758b286ba5690b)

##CustomViewとは何か
- Android Frameworkには多くのview classがあるが、すでに作られたものではないuniqueなviewを作りたい時に自分でカスタマイズしたview classのこと


メリットとしては...

- Activity/Fragmentになんでも書いてしまうとどんどんFatになっていくため、Viewに依存するものはCustomView内で完結させてしまうことでActivity/Fragmentに書く処理が減る
- xmlファイルに組み込んで繰り返し使えるため便利！

##今回作ったもの
- 簡単に画面に複数のImageViewを表示させただけのもの
- ImageViewの枚数によってサイズが変わる(1 ~ 4枚、5枚以降は画面に表示されない)

###4枚の場合
<img width="400px" alt="4枚の場合" src="https://qiita-image-store.s3.amazonaws.com/0/117537/a001a124-8901-aa6a-4541-ef51edf8e047.png">

###3枚の場合
<img width="400px" alt="3枚の場合" src="https://qiita-image-store.s3.amazonaws.com/0/117537/f54a4c31-2473-d76e-78e0-09d880b83945.png">

- 4枚目以降は画面に一度に画面に表示されないようになっています
- 1枚の場合は画面一面に、2枚の場合は横半分に表示されますが、ここでは省略します

##どうやって実装するのか
###attrs.xml 
- 定義を書く

```xml
<?xml version="1.0" encoding="utf-8"?>
<!--xmlファイルで定義するときに使う-->
<resources>
<attr name="scaleType">
        <enum name="matrix" value="0" />
        <enum name="fitXY" value="1" />
        <enum name="fitStart" value="2" />
        <enum name="fitCenter" value="3" />
        <enum name="fitEnd" value="4" />
        <enum name="center" value="5" />
        <enum name="centerCrop" value="6" />
        <enum name="centerInside" value="7" />
    </attr>
</resources>
```

###TileLayout.java　
- ファイルに動的な部分(枚数によってレイアウトを変える)を実装する

```java
public class TileLayout extends ViewGroup {
    public TileLayout(Context context) {
        this(context, null);
    }

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

    public TileLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    //ここでgetChildCount()で帰ってきた値に対して条件分岐をする
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //子ビューの位置を指定しているメソッド
        //padding分を差し引いて子ビューの領域を求める
        int width = (r - l) - getPaddingLeft() - getPaddingRight();
        int height = (b - t) - getPaddingTop() - getPaddingBottom();

        int left = getPaddingLeft();
        int top = getPaddingTop();
        int right = left + width;
        int bottom = top + height;

        if (height > width) {
            //縦の時
            layoutVertical(width, height, left, top, right, bottom);
        } else {
            layoutHorizontal(width, height, left, top, right, bottom);
        }
    }

    private void layoutVertical(int width, int height, int left, int top, int right, int bottom) {
//viewが1つ・2つ・3つの時と条件分岐をして表示する
//それぞれの条件に対して位置をchildLayout()に出力する
}
private void layoutHorizontal(int width, int height, int left, int top, int right, int bottom) {
//Horizontalの時も同じように    
    }

//それぞれの位置を指定するためのメソッド
private void childLayout(View child, int l, int t, int r, int b) {
        child.layout(l, t, r, b);
    }

//子ビューのサイズを指定しているメソッド
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //レイアウトモードがEXACTLYの時はエラーにする
        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
            throw new IllegalStateException("Must measure with an exact width");
        }
        //このViewGroupに割り当てられているサイズを取得する
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //このViewGroupのサイズをセットする
        setMeasuredDimension(widthSize, heightSize);

        //padding分を差し引いて親の幅の高さを求める
        int width = widthSize - getPaddingLeft() - getPaddingRight();
        int height = heightSize - getPaddingTop() - getPaddingBottom();
        
        boolean isVertical = height > width;

        int childWidth1;
        int childHeight1;
        int childWidth2;
        int childHeight2;
        int childWidth3;
        int childWidth4;
        int childHeight3;
        int childHeight4;
        final int childCount = getChildCount();
        
        //childCountごとに条件分岐し、childWidth1~4ごとのサイズを指定する
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);

            int childWidth = 0;
            int childHeight = 0;
            if (i == 0) {
                //1番目の子ビュー
                childWidth = childWidth1;
                childHeight = childHeight1;
            } else if (i == 1) {
            ...(省略)
				//条件分岐をし、それぞれの場合での子ビューのサイズを指定する

    		 //最終的にサイズを出力
            //子ビューに対してmeasure()を呼んでサイズを指定する
            //TileLayoutは子ビューでのlayout_heightやlayout_widthの指定に関係なく
            //決まったサイズで配置するのでMeasureSpec.EXACTLYにする
            
            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
            int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

```
 
###activity_main.xml　
- 実際にxmlファイルに先ほどのCustomViewを導入

```xml
<?xml version="1.0" encoding="utf-8"?>
<neonankiti.recyclerviewsample.TileLayout
//呼び出すにはこのように指定する
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
//以下のImageViewの個数でViewのレイアウトが自動的に変わるようになっている
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@drawable/view1"/>
...
</neonankiti.recyclerviewsample.TileLayout>
```

###MainActivity.java 
- 画面を出力

```java
//最終的にここで出力
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
```

##全てのfile
今回コードが長く、省略してしまった部分はここに載せてあります
https://gist.github.com/MihoSasaki/064051a55b10ae92b28631469eb9446a