ConstraintLayoutのCircular positioningを使って円状にViewを配置してみる

# Circular positioning
ConstraintLayout 1.1.0-beta3から、Circular positioningという制約が追加されました。
https://developer.android.com/reference/android/support/constraint/ConstraintLayout.html

![circle1.png](https://qiita-image-store.s3.amazonaws.com/0/220801/c51722d1-575a-0c6d-1c64-81669a904029.png)
![circle2.png](https://qiita-image-store.s3.amazonaws.com/0/220801/4f590d29-25eb-dee5-b042-6b7bd20f521d.png)
(画像は上記URLより引用)


これはView Aの位置から極座標でView Bを配置できるようにするものです。
```layout_constraintCircle```
```layout_constraintCircleRadius```
```layout_constraintCircleAngle```(12時方向が0、値を増やすと時計回りに角度が変わる。負の値や360を超えた値も使える)
の3つのパラメータを指定することで利用できます。

面白そうだったのでこれを使って簡単なサンプルを作ってみました。
EditTextに数値を入力して画面遷移すると、その数だけ等間隔にTextViewを円状に表示するアプリです。
TextViewはコードから動的にaddView()するようにしています。

![input_num.png](https://qiita-image-store.s3.amazonaws.com/0/220801/3af0f99f-315c-978b-d7ea-e624fea79710.png)

![circule.png](https://qiita-image-store.s3.amazonaws.com/0/220801/1de58bc6-db40-b00b-05ed-494b38da662b.png)

# 実装
## 環境
Android Studio:3.0.1
ConstraintLayout:1.1.0-beta3

## 導入
build.gradleにConstraintLayout 1.1.0-beta3を追加します。
```ruby
implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta3'
```

## コード
1つ目の画面はEditTextに入力してボタンで画面遷移するだけなので省略。
2つ目の画面に使うコードを以下に示します。

CircularPositionActivity.java

```java
public class CircularPositionActivity extends AppCompatActivity {

    private static final String NUMBER_OF_VIEW = "numberOfView";

    public static Intent createIntent(Context context, int num) {
        Intent intent = new Intent(context, CircularPositionActivity.class);
        intent.putExtra(NUMBER_OF_VIEW, num);
        return intent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent intent = getIntent();
        int num = intent.getIntExtra(NUMBER_OF_VIEW, 0);

        ActivityCircularPositionBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_circular_position);

        ConstraintLayout root = binding.root;
        View centralView = binding.centralView;

        int radius = getResources().getDimensionPixelSize(R.dimen.circle_radius);

        // 入力された数だけ等間隔に配置する
        for (int i = 0; i < num; i++) {
            TextView textView = new TextView(this);

            ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(
                    ConstraintLayout.LayoutParams.WRAP_CONTENT,
                    ConstraintLayout.LayoutParams.WRAP_CONTENT);
            
            layoutParams.circleConstraint = centralView.getId(); // 基点になるViewを指定
            layoutParams.circleAngle = computeAngle(num, i); // 角度を指定
            layoutParams.circleRadius = radius; // 半径を指定(dp)
            textView.setLayoutParams(layoutParams);

            textView.setText(String.valueOf(i));

            root.addView(textView);
        }
    }

    private static final int CIRCLE_RADIUS = 360;

    private float computeAngle(int num, int index) {
        float angleUnit = (float) CIRCLE_RADIUS / num;
        return angleUnit * index;
    }
}
```

activity_circular_position.xml

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

<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" >

    <android.support.constraint.ConstraintLayout
        android:id="@+id/root"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <View
            android:id="@+id/centralView"
            android:layout_width="3dp"
            android:layout_height="3dp"
            android:background="@color/colorAccent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>

    </android.support.constraint.ConstraintLayout>
</layout>
```

dimens.xml

```xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="circle_radius">120dp</dimen>
</resources>
```

# 感想
正直なところCircular positioningをどう活かすべきなのかまだよくわかりませんが、今回のようなレイアウトを組むのには非常に簡単に実装できます。
今までだと同じことを実装するのに、一つ一つのViewの座標を出すためにマージンの計算などめんどくさい処理をたくさん書かないといけませんでした。その手間が大幅に解消できるところはメリットだと思います。