C#에서 JSON 해석하기

.NET 5부터는 JSON을 해석하는 API가 기본적으로 제공되는 듯 하지만, 아직도 내 PC에는 .NET 5가 기본적으로 설치되어 있지 않으므로 다른 방안이 필요했고 Newtonsoft.Json이라는 매우 뛰어난 JSON 라이브러리가 있어 이를 사용하게 되었습니다.

설치는 간단이 Visual Studio의 Package Manager 기능을 통해 설치할 수 있으며 NuGet 사이트에서 제공하는 정보를 통해 쉽게 설치할 수 있습니다.

이제 다음과 같은 JSON을 해석해 봅시다. 이 JSON은 test.json이라는 파일이 저장되어 있습니다.

{
    "form0": {
        "metadata": {
            "title": "테스트"
        }
    },

    "form1": {
        "metadata": {
            "title": "연락처"
        }
    }
}

위에서 form0과 form1에 대한 정보를 얻어와야 하고, 각가게 대한 title 값을 얻어와야 합니다. 코드는 아래와 같습니다.

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

...

var strFormJson = Utility.ReadAllTextFromFile("test.json");
var jsonForm = JObject.Parse(strFormJson);
var iter = jsonForm.GetEnumerator();

while(iter.MoveNext())
{
    var keyValue = iter.Current;
    MessageBox.Show(keyValue.Key + " " + keyValue.Value["metadata"]["title"].ToString());
}

JSON에 대해 배열에 대한 처리는 JArray로 타입을 변환하면 됩니다. 코드의 예는 다음과 같습니다.

var jsonLayers = JObject.Parse(strLayersJson);
var arrayJsonLayers = jsonLayers["layers"] as JArray;
var iterLayers = arrayJsonLayers.GetEnumerator();

while(iterLayers.MoveNext())
{
    var value = iterLayers.Current;
    var layerName = value["name"].ToString();

    ...
}

시간이 좀더 흘려 .NET 5가 사용자의 PC에 기본적으로 설치되는 날이 오면 .NET 5에서 제공하는 JSON API를 사용하시기 바랍니다.

Drawer Menu 항목의 우측에 커스텀 뷰(Custom View) 달기

Drawer Menu는 안드로이드의 표준화된 메뉴입니다. 이 UI는 단순히 메뉴로써의 기능 뿐만이 아니라 다양한 정보를 사용자에게 제공할 수 있습니다. 이를 위해 Drawable의 특정 메뉴 항목의 우측에 커스텀 뷰를 달게 되면, 이 커스텀 뷰의 내용을 변경함으로써 어떤 정보를 사용자에게 전달할 수 있게 됩니다. 이 글은 이처럼 Drawable의 특정 메뉴 항목에 커스텀 뷰를 추가하는 코드를 정리합니다.

Drawer Menu를 추가하기 위해서는 Activity의 Layout에서 기본적으로 com.google.android.material.navigation.NavigationView를 사용하게 됩니다. 이를 NavigationView를 통해 메뉴의 각 항목에 접근할 수 있고, 접근된 메뉴에 원하는 View를 지정할 수 있게 됩니다. 아래 코드처럼요.

navigationView = findViewById<NavigationView>(R.id.main_navigation_view)

    ...

navigationView.getMenu().getItem(4).setActionView(R.layout.drawer_menu_item_collecting_data_searched_items_count)

위의 코드는 5번째 메뉴에 대한 커스텀 뷰를 R.layout.drawer_menu_item_collecting_data_searched_items_count으로 지정하고 있습니다. R.layout.drawer_menu_item_collecting_data_searched_items_count에 대한 코드는 다음과 같습니다.

<?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="match_parent"
    android:gravity="center_vertical"
    android:orientation="vertical" >

    <TextView
        android:background="@drawable/rounding_box"
        android:id="@+id/tvResultItemCount"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#ffffff"
        android:textSize="@dimen/very_small_text_size"
        android:paddingVertical="2dp"
        android:paddingHorizontal="10dp"
        android:text="0" />

</LinearLayout>

위의 뷰를 좀더 시각적으로 안정감 있도록 배경을 rounding_box라는 Drawable로 지정하였는데요. rounding_box 코드는 다음과 같습니다.

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/uiColor" />
    <padding
        android:left="45px"
        android:top="15px"
        android:right="45px"
        android:bottom="15px" />
    <corners android:radius="30px" />
</shape>

앞서 언급했던 것처럼 Drawer Menu의 항목에 대한 커스텀 뷰의 내용을 변경함으로써 정보를 사용자에게 전달한다고 했는데, 커스텀 뷰를 변경하는 코드의 예는 다음과 같습니다.

navigationView.getMenu().getItem(4).actionView.findViewById<TextView>(R.id.tvResultItemCount).text = "999"

Kotlin에서 propery 정의

field를 통해 내부 변수에 접근해야 한다는 점에 유의해야 함.

var state: State? = null
set(value) {
    field?.leave()
    field = value
    field?.enter()
}
get() = field

[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의 예시는 다음과 같습니다.