ANDROID 프로그래밍/LAYOUT

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

뽀따 2019. 5. 16. 12:28


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

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


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


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

컨스트레인트레이아웃은 대상 뷰를 기준으로, 원하는 "각도(angle)"에, 지정된 "반경(radius)"만큼 떨어진 위치에 뷰를 배치하는 제약을 제공하는데요, 이를 원형 위치 지정(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)의 예제입니다.




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) ;
    }
}


4. 참고.

.END.