컨스트레인트레이아웃 - 원형 위치 지정 (ConstraintLayout - Circular Positioning)

2019. 5. 16. 12:28


1. 뷰 위젯 상대 위치 지정.

이전 글 [컨스트레인트레이아웃 - 상대 위치 지정(ConstraintLayout - Relative Positioning)]에서, 컨스트레인트레이아웃의 상대 위치 지정(Relative Positioning) 방법에 대해 살펴보았습니다. 대상 뷰 또는 부모 레이아웃을 기준으로, 수평 방향(Left, Right)과 수직 방향(Top, Bottom)으로 뷰가 배치될 상대 위치를 지정하는 방법에 대해 설명했었죠.

ConstraintLayout Relative Positioning


그런데 컨스트레인트레이아웃이 제공하는 상대 위치 지정(Relative Positioning)은, 네 방향(4-way, 상/하/좌/우)에 대한 상대 위치 지정에만 한정되지 않고, 훨씬 다양한 배치를 가능하게 하는 방법을 제공합니다. 대상 뷰를 기준으로 "360도 방향"으로 상대 위치를 지정할 수 있게 말이죠.

ConstraintLayout Circular Positioning


2. 원형 위치 지정(Circular Positioning)

컨스트레인트레이아웃은 대상 뷰를 기준으로, 원하는 "각도(angle)"에, 지정된 "반경(radius)"만큼 떨어진 위치에 뷰를 배치하는 제약을 제공하는데요, 이를 원형 위치 지정(Circular Positioning)이라고 부릅니다.

ConstraintLayout Circular Positioning


위 그림을 통해 알수 있듯이 원형 위치 지정(Circular Positioning) 방법은 그 이름답게, 대상 뷰 위젯의 중심을 기준으로, 주어진 반지름(radius)만큼 떨어진 원(Circle) 둘레의 한 지점에 뷰를 배치할 수 있게 해 줍니다. 원 둘레에서 뷰가 배치될 정확한 지점은 0~360 사이의 각도(angle) 값을 지정하여 결정하는데, 대상 뷰 위젯의 중심에서 정확히 위(top)쪽 방향 반지름만큼 떨어진 위치가 0도의 기준이 됩니다.


자, 위 설명을 보면, 컨스트레인트레이아웃의 원형 위치 지정(Circular Positioning)에 사용되는 속성이 어떤 것들이 있는지 감이 오죠? 바로 "대상 뷰 위젯", "반지름", "각도" 이렇게 세 개의 속성이 제공됩니다.


  * layout_constraintCircle - 대상 뷰 위젯 지정.
        > 속성 값에는 대상 뷰의 ID 지정.

  * layout_constraintCircleRadius - 뷰 위젯과 대상 뷰 위젯 중심 사이의 거리.
        > 치수(dimension) 값 지정 가능.

  * layout_constraintCircleAngle - 원 둘레에서 뷰 위젯이 배치될 각도.
        > 기본적으로 0~360 사이의 정수 값 사용.
        > 음수(-) 값 지정 시, 절대 값으로 적용.
        > 360 이상 값 지정 시, 360을 나눈 나머지 값 적용.

아래는 몇 가지 원형 위치 지정(Circular Positioning)의 예제입니다.

ConstraintLayout Circular Positioning Example 1


ConstraintLayout Circular Positioning Example 2


ConstraintLayout Circular Positioning Example 3


3. 컨스트레인트레이아웃 예제 : 아날로그 시계

컨스트레인트레이아웃의 원형 위치 지정(Circular Positioning) 방법으로 만들 수 있는 UI로 어떤 화면이 가장 먼저 떠오르나요? "아날로그 시계"가 딱 떠오르지 않나요? 아래와 같이, 아주 간단하게 한번 만들어봤습니다. 음... 뭐, 일단 동작은 하네요.


어쨌든, 아래 예제 코드에서는 원형 위치 지정(Circular Positioning)으로 레이아웃을 구성하는 방법에 대해 확인하실 수 있습니다.


[STEP-1] "activity_main.xml" - 메인액티비티에서 아날로그 시계 레이아웃 구성
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".MainActivity"
    tools:showIn="@layout/activity_main">

    <View
        android:layout_width="10dp"
        android:layout_height="10dp"
        android:background="#00FF00"
        android:id="@+id/watchCenter"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="60sp"
        android:text="1"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="250dp"
        app:layout_constraintCircleAngle="30" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="60sp"
        android:text="2"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="250dp"
        app:layout_constraintCircleAngle="60" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="60sp"
        android:text="3"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="250dp"
        app:layout_constraintCircleAngle="90" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="60sp"
        android:text="4"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="250dp"
        app:layout_constraintCircleAngle="120" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="60sp"
        android:text="5"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="250dp"
        app:layout_constraintCircleAngle="150" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="60sp"
        android:text="6"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="250dp"
        app:layout_constraintCircleAngle="180" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="60sp"
        android:text="7"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="250dp"
        app:layout_constraintCircleAngle="210" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="60sp"
        android:text="8"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="250dp"
        app:layout_constraintCircleAngle="240" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="60sp"
        android:text="9"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="250dp"
        app:layout_constraintCircleAngle="270" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="60sp"
        android:text="10"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="250dp"
        app:layout_constraintCircleAngle="300" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="60sp"
        android:text="11"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="250dp"
        app:layout_constraintCircleAngle="330" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="60sp"
        android:text="12"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="250dp"
        app:layout_constraintCircleAngle="0" />

    <View
        android:layout_width="200dp"
        android:layout_height="2dp"
        android:background="#FF0000"
        android:id="@+id/handSecond"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="100dp"
        app:layout_constraintCircleAngle="90" />

    <View
        android:layout_width="160dp"
        android:layout_height="2dp"
        android:background="#0000FF"
        android:id="@+id/handMinute"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="80dp"
        app:layout_constraintCircleAngle="90" />

    <View
        android:layout_width="100dp"
        android:layout_height="2dp"
        android:background="#000000"
        android:id="@+id/handHour"
        app:layout_constraintCircle="@id/watchCenter"
        app:layout_constraintCircleRadius="50dp"
        app:layout_constraintCircleAngle="90" />
</android.support.constraint.ConstraintLayout>


[STEP-2] "MainActivity.java" - 메인 액티비티에서 시각 갱신하는 코드 작성.
public class MainActivity extends AppCompatActivity {

    private View handSecond = null ;
    private View handMinute = null  ;
    private View handHour = null  ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ... 코드 계속

        handSecond = findViewById(R.id.handSecond) ;
        handMinute = findViewById(R.id.handMinute) ;
        handHour = findViewById(R.id.handHour) ;

        handSecond.setPivotX(0.0f) ;
        handSecond.setPivotY(0.0f) ;
        handMinute.setPivotX(0.0f) ;
        handMinute.setPivotY(0.0f) ;
        handHour.setPivotX(0.0f) ;
        handHour.setPivotY(0.0f) ;

        final Handler handler = new Handler() ;
        final Runnable runnalble = new Runnable() {
            @Override
            public void run() {

                LocalTime time = LocalTime.now() ;
                int second = time.getSecond() ;
                int minute = time.getMinute() ;
                int hour = time.getHour() ;

                System.out.println("hour    : " + hour) ;
                System.out.println("minute  : " + minute) ;
                System.out.println("second  : " + second) ;

                // Set the rotation of the view.
                handSecond.setRotation(360 * second / 60 - 90) ;
                handMinute.setRotation(360 * minute / 60 - 90) ;
                // handHour.setRotation(360 * hour / 12 - 90);
                handHour.setRotation(360 * ((hour%12)*60+minute)/(12*60) - 90) ;

                handler.postDelayed(this, 1000);
            }
        } ;

        handler.postDelayed(runnalble, 1000) ;
    }
}

ConstraintLayout Circular Positioning 아날로그 시계 예제


4. 참고.

.END.


ANDROID 프로그래밍/LAYOUT