Kotlin Material 스타일 TabLayout 예제

|

colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <color name="colorPrimary">#F48FB1</color>
  <color name="colorPrimaryDark">#F48FB1</color>
  <color name="textColorPrimary">#EC407A</color>
  <color name="windowBackground">#FFFFFF</color>
  <color name="navigationBarColor">#000000</color>
  <color name="colorAccent">#c8e8ff</color>
</resources>


dimens.xml

<resources>
  <!-- Default screen margins, per the Android Design guidelines. -->
  <dimen name="activity_horizontal_margin">16dp</dimen>
  <dimen name="activity_vertical_margin">16dp</dimen>
  <dimen name="tab_max_width">264dp</dimen>
  <dimen name="tab_padding_bottom">16dp</dimen>
  <dimen name="tab_label">14sp</dimen>
  <dimen name="custom_tab_layout_height">72dp</dimen>
</resources>


styles.xml

<resources>
  <!-- Base application theme. -->
  <style name="AppTheme" parent="RanMaterialTheme.Base">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
  </style>

  <style name="RanMaterialTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="windowNoTitle">true</item>
    <item name="windowActionBar">false</item>
  </style>
</resources>


activity_main.xml

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

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

    <android.support.v7.widget.Toolbar
      android:id="@+id/toolbar"
      android:layout_width="match_parent"
      android:layout_height="?attr/actionBarSize"
      android:background="?attr/colorPrimary"
      app:layout_scrollFlags="scroll|enterAlways"
      app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

    <RelativeLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

      <Button
        android:id="@+id/add_tab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_gravity="end"
        android:text="+"/>

      <android.support.design.widget.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toStartOf="@id/add_tab"
        app:tabGravity="fill"
        app:tabMode="scrollable"/>

    </RelativeLayout>

  </android.support.design.widget.AppBarLayout>
  
  <android.support.v4.view.ViewPager
    android:id="@+id/viewpager"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

</android.support.design.widget.CoordinatorLayout>


MainActivity.kt

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.design.widget.TabLayout
import android.support.v7.widget.Toolbar
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val toolbar = findViewById<Toolbar>(R.id.toolbar)

        setSupportActionBar(toolbar)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)

        val list = ArrayList<String>()
        list.add("Year")
        list.add("Month")
        list.add("Week")
        list.add("Day")

        val adapter = TodoListPagerAdapter(supportFragmentManager)
        adapter.setList(list)
        viewpager.adapter = adapter

        val tabLayout = findViewById<TabLayout>(R.id.tabs)
        tabLayout.setupWithViewPager(viewpager)

        add_tab.setOnClickListener {
            list.add("Added")
            adapter.setList(list)
        }
    }
}

타입 체크 및 캐스팅, 비교 연산

|

is와 as

코틀린에서는 타입 체크할 때 is 키워드를 사용하며, Java에서의 instanceof와 동일한 역할을 합니다. 그리고 타입 캐스팅시에는 as 키워드를 사용합니다.

fun setLayoutParam(view: View) {
    if (view is LinearLayout) {
        var param = view.layoutParams as LinearLayout.LayoutParams
        param.gravity = Gravitiy.CENTER
        view.layoutParams = param
    } else if (view is RelativeLayout) {
        var param = view.layoutParams as RelativeLayout.LayoutParams
        param.addRule(RelativeLayout.ALIGN_PARENT_CENTER)
        view.layoutParams = param
    }
}


NullPoint Exception 방지

코틀린에서는 ? 기호를 이용해서 해당 변수가 null 값을 가질 수 있음을 알려주며, 기본적으로는 non null 상태입니다.

var str:String? = "hello"
str = null

그리고 변수를 사용할 때도 변수뒤에 ? 기호를 붙여서 Nullpoint Exception 처리를 할 수 있습니다.

fun test() {
    var str: String? = "hello"
    str = null
    var length: Int? = str?.length
}

위의 예제에서 만약 str 변수가 null이 되면 str?.lengthnull을 리턴하기 때문에 length 변수가 null이 됩니다. 즉, 위험할 수 있는 코드이기 때문에 아래와 같이 표현할 수 있습니다.

fun test() {
    var str: String? = "hello"
    str = null
    var length: Int = str?.length ?: 0
}

위와 같이 코드를 작성하면 str?.lengthnull일 경우 숫자 0 으로 리턴하기 때문에 length 변수는 항상 값을 가질 수 있습니다.

그리고 !! 기호를 사용하게 되면 명시적으로 변수에 절대 null을 참조할 수 없다는 것을 지정할 수 있습니다.

fun test() {
    var str: String? = "hello"
    var length: Int = str!!.length
}


== 와 ===

=====는 비교 연산자로 ==는 Java에서 사용하던 ==와 동일한 역할을 합니다. 그리고 내부적으로 NULL 체크를 하기 때문에 좀 더 간략하게 코드를 작성할 수 있습니다. 다만, a == b에서 ab 모두 null인 경우 true가 되기 때문에 주의할 필요는 있습니다.

===의 경우는 두 변수가 정말 똑같은 주소값을 갖고 있는지 판단하며 Java에서의 equals()와 동일합니다.

Collections

|

코틀린은 자바에서와는 달리 리스트(List)나 맵(Map) 등의 자료 구조에 읽기 전용 객체와 수정이 가능한 객체로 나누어 놓았습니다.

코틀린에서는 리스트를 생성할 때 클래스를 선언하지 않아도 만들 수 있는 함수를 제공하고 있습니다. 리스트를 생성하는 함수는 listOf이며, 맵을 생성하는 함수는 mapOf입니다. 이 함수를 이용하면 읽기 전용의 리스트가 만들어집니다.

fun init() {
    val numList = listOf(1, 3, 7, 5, 10)
    val strList = listOf("snowdeer", "ran", "yang")

    val map = mapOf(1 to "one", 2 to "two", 3 to "three")
}

읽기 전용 리스트를 만들면 get, first, last 등 데이터를 읽을 수 있는 함수들만 제공됩니다.

만약, 수정이 가능한 리스트들을 만들고 싶으면 mutableListOf, mutableMapOf 함수를 사용하면 됩니다.

fun init() {
    val numList = mutableListOf(1, 3, 7, 5, 10)
    val strList = mutableListOf("snowdeer", "ran", "yang")
    val map = mutableMapOf(1 to "one", 2 to "two", 3 to "three")
}

Kotlin Fragment 템플릿

|

팩토리 메소드(newInstance)로 Fragment를 생성하는 예제 코드입니다.

import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

private const val ARG_TITLE = "TITLE"

class TodoListFragment : Fragment() {

    private var title: String? = ""

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            title = it.getString(ARG_TITLE)
        }
    }

    companion object {
        fun newInstance(title: String): TodoListFragment {
            return TodoListFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_TITLE, title)
                }
            }
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_todolist, container, false)

    }
}

consider adding at least one Activity with an ACTION-VIEW intent filter

|

AndroidManifest.xml 에서 다음과 같은 오류가 발생할 경우 해결하는 방법입니다.

App is not indexable by Google Search; consider adding at least one Activity with an ACTION-VIEW intent filter.

적어도 하나 이상의 액티비티의 인텐트 필터에 다음 라인을 추가하면 됩니다.

<action android:name="android.intent.action.VIEW"/>