[Android] Custom Dialog 만들기

Custom Dialog를 만들기 위해 Dialog를 상속받으면 Title부터 OK, Cancel 처리를 위한 버튼까지 모두 만들어줘야 합니다. 손을 좀 줄이고자 상속의 개념이 아닌 AlertDialog.Builder를 이용해 Custom Dialog를 만들 수 있습니다. 먼저 Custom Dialog의 UI를 구성하는 레이아웃을 아래처럼 정의합니다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <Spinner
        android:id="@+id/spLayers"
        android:layout_marginHorizontal="40dp"
        android:layout_height="wrap_content"
        android:layout_width="match_parent" />

</LinearLayout>

Spinner 하나만 존재하는 단순한 UI입니다. 이 레이아웃의 파일 명을 edit_target_layer_dialog.xml이라고 하겠습니다.

이제 Dialog를 표시하는 코드입니다.

val builder = AlertDialog.Builder(mainActivity)
val inflater = mainActivity.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = inflater.inflate(R.layout.edit_target_layer_dialog, null)

val spLayers = view.findViewById<Spinner>(R.id.spLayers)
var layerList = ArrayList<String>()

// layerList에 Spinner를 채울 문자열 항목 구성

val adapter = ArrayAdapter(mainActivity, android.R.layout.simple_spinner_dropdown_item, layerList)

spLayers.adapter = adapter
spLayers.setSelection(0)

builder.setView(view)

val listener = DialogInterface.OnClickListener() { dialog: DialogInterface, which: Int ->
    if (which == DialogInterface.BUTTON_POSITIVE) {
        Log.v("DIP2K", spLayers.selectedItem.toString())
    } else if (which == DialogInterface.BUTTON_NEGATIVE) {
        Log.v("DIP2K", "CANCEL")
    }
}

builder.setPositiveButton("확인", listener)
builder.setNegativeButton("취소", listener)

val dlg = builder.create()
dlg.setTitle("편집 대상 레이어")
dlg.show()

결국 아래와 같은 대화상자를 볼 수 있습니다.

[Android] selector Drawable

selector Drawable은 상태에 따라 달라지는 Drawable로써, 상태는 enabled, selected 등이 있습니다. 버튼 등과 같은 UI에 적용함으로써 해당 버튼이 비활성되었을때, 선택되었을때에 따른 상태를 시각적으로 사용자에게 전달할 수 있습니다.

만약 상태를 enabled와 selected에 대해 구분한다면 총 3가지 Drawable이 필요한데, 기본 상태도 포함되어야 하기 때문입니다.

selector Drawable의 정의는 다음 예와 같습니다.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_enabled="false"
        android:drawable="@drawable/ic_add_disabled" />

    <item
        android:state_selected="true"
        android:drawable="@drawable/ic_add_selected" />

    <item
        android:drawable="@drawable/ic_add" />
</selector>

selector를 구성하는 item의 순서가 중요한데, 가장 처음 해당되는 상태가 선택되기 때문입니다. 각 item에 대한 Drawable 중 enabled에 대한 ic_add_disabled.xml의 예시는 다음과 같습니다.

그리고 selected에 대한 ic_add_selected.xml의 예시는 다음과 같습니다.

마지막으로 기본 상태에 대한 ic_add.xml의 예시는 다음과 같습니다.

[Android] 날짜 선택 다이얼로그

먼저 아래와 같은 UI가 있고, 설정 버튼을 터치하면 날짜를 선택할 수 있는 대화상자가 표시되며 이 대화상자에서 날짜를 지정하면 지정한 날짜를 얻기 위한 내용이다.

설정 버튼 클릭시 호출되는 코드는 다음과 같다.

btnStartDate.setOnClickListener {
    val today = GregorianCalendar()
    val year: Int = today.get(Calendar.YEAR)
    val month: Int = today.get(Calendar.MONTH)
    val date: Int = today.get(Calendar.DATE)

    val dlg = DatePickerDialog(this, object : DatePickerDialog.OnDateSetListener {
        override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) {
            etStartDate.setText("${year}년 ${month+1}월 ${dayOfMonth}일")
        }
    }, year, month, date)

    dlg.show()
}

설정 버튼 터치시 다음과 같은 대화상자가 표시된다.

[Android] XML로부터 View 생성하기

안드로이드는 UI의 구성을 XML로 정의하여 생성한다. 아래는 UI를 위한 XML인 map_legend_item.xml 파일이다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <LinearLayout
        android:paddingHorizontal="15dp"
        android:layout_width="match_parent"
        android:layout_height="54dp"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <Switch
            android:id="@+id/swLayerVisibility"
            android:layout_weight="0"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <geoservice.nexgen.maplegend.LegendSingleSymbol
            android:id="@+id/lssItem"
            android:layout_width="36dp"
            android:layout_height="36dp" />

        <Space
            android:layout_width="5dp"
            android:layout_height="1px" />

        <TextView
            android:layout_weight="1"
            android:id="@+id/tvLayerName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="@dimen/normal_text_size"
            android:textStyle="bold"
            android:text="LayerName" />


    </LinearLayout>
</LinearLayout>

위의 XML을 통해 View를 생성하는 코드는 다음과 같다.

for( ... ) {
    val itemLayout = inflater.inflate(R.layout.map_legend_item, null, false)
    itemLayout.findViewById<TextView>(R.id.tvLayerName).setText(title)

    ...

    mainLayout.addView(itemLayout)
}

위의 코드 중 inflater는 다음 3가지 방식 중 하나를 통해 생성된다.

val inflater = layoutInflater
val inflater = LayoutInflater.from(this)
val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater

위의 코드를 통한 실제 결과는 다음과 같다.