17 Apr 2019
|
Android
Kotlin
item_selected_tab.xml
<?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="horizontal">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_gravity="center"
android:maxWidth="@dimen/tab_max_width"
android:minWidth="20dp"
android:singleLine="true"
android:textColor="@color/textColorPrimary"
android:textSize="16sp"/>
<Button
android:id="@+id/remove_button"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:background="@drawable/btn_remove"/>
</LinearLayout>
MainActivity.kt
class MainActivity : AppCompatActivity(), TabLayout.OnTabSelectedListener {
private val tabLayout: TabLayout by lazy {
findViewById<TabLayout>(R.id.tabs)
}
private val adapter: CategoryPagerAdapter by lazy {
CategoryPagerAdapter(supportFragmentManager)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
viewpager.adapter = adapter
tabLayout.setupWithViewPager(viewpager)
tabLayout.addOnTabSelectedListener(this)
add_tab.setOnClickListener {
showEditCategoryNameDialog(null)
}
}
override fun onTabReselected(tab: TabLayout.Tab?) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
tab?.customView = null
}
override fun onTabSelected(tab: TabLayout.Tab?) {
val view = LayoutInflater.from(applicationContext).inflate(R.layout.item_selected_tab, null)
todoInsertFragment.currentTabPosition = tab?.position?.toLong() ?: 0
val tabInfo = adapter.getTabInfo(tab?.position ?: 0)
view.title.text = tabInfo?.name
view.title.setOnLongClickListener(object : View.OnLongClickListener {
override fun onLongClick(v: View?): Boolean {
showEditCategoryNameDialog(tabInfo)
return true
}
})
view.remove_button.setOnClickListener {
if (tabInfo != null) showDeleteCategoryDialog(tabInfo)
}
tab?.customView = view
}
private fun showEditCategoryNameDialog(tabInfo: TabInfo?) {
val dialogLayout = LayoutInflater.from(applicationContext).inflate(R.layout.dialog_edit_name, null)
val editText = dialogLayout.findViewById<EditText>(R.id.editText)
editText.setText(tabInfo?.name ?: "")
AlertDialog.Builder(this)
.setTitle("이름을 입력하세요.")
.setView(dialogLayout)
.setPositiveButton("OK") { _, _ ->
if (tabInfo != null) {
ModelManager.instance.updateTabInfo(tabInfo.id, editText.text.toString())
} else {
ModelManager.instance.addTabInfo(editText.text.toString())
selectLastTab()
}
}
.show()
}
private fun showDeleteCategoryDialog(tabInfo: TabInfo) {
AlertDialog.Builder(this)
.setTitle("Category 삭제")
.setMessage("${tabInfo.name} 항목을 정말 삭제하시겠습니까?")
.setPositiveButton("확인") { _, _ ->
ModelManager.instance.deleteTabInfo(tabInfo.id)
selectNextTab(tabLayout.selectedTabPosition)
}
.show()
}
}
16 Apr 2019
|
Kotlin
lateinit
lateinit
는 다음과 같은 특징이 있습니다.
var
변수에만 사용 가능
Non-null
데이터 타입에만 가능
- primitive type에는 사용할 수 없음
- 클래스 생성자에서 사용 못함
- 로컬 변수로 사용 못함
그럼, 어떤 경우에 사용해야 할까요? 코틀린은 NULL 체크에 대한 검사가 엄격하기 때문에
class Sample {
private var name: String
}
와 같은 코드는 Property must be initialized or be abstract
오류가 발생합니다.
실제로 초기화 때 값을 지정해주고 코드를 작성하는 것이 더욱 좋지만, 그렇지 못한 경우가 많이 발생합니다. 그럴 때는 아래와 같이 lateinit
키워드를 변수 앞에 적어주면 변수 초기화를 나중에 할 수 있게 됩니다.
class Sample {
private lateinit var name: String
}
lazy
lazy()
함수는 lateinint
와 비슷한 역할을 하는데 다음과 같은 특징이 있습니다.
val
변수에만 사용 가능
- primitive type에도 사용 가능
Non-null
, Nullable
둘 다 사용 가능
- 클래스 생성자에서 사용 못함
- 로컬 변수로 사용 가능
private val lazyExample: String by lazy {
println("lazyExample - init()")
"[lazy] lazyExample is initialized."
}
fun test() {
println("Start...")
println("1st : $lazyExample")
println("2nd : $lazyExample")
println("3rd : $lazyExample")
}
실행 결과는 다음과 같습니다.
Start...
lazyExample - init()
1st : [lazy] lazyExample is initialized.
2nd : [lazy] lazyExample is initialized.
3rd : [lazy] lazyExample is initialized.
위 결과와 같이, 처음 호출될 시점에 초기화를 한 번 하며 그 이후로는 그 결과값만 사용하는 것을 확인할 수 있습니다.
16 Apr 2019
|
Kotlin
람다 함수는 다음과 같은 형태로 사용할 수 있습니다.
(매개 변수) -> { TODO("...) }
실제 코드로 예를 들면 다음과 같습니다.
button.setOnClickListener((v) -> {
TODO("onClicked !!")
})
16 Apr 2019
|
Android
Kotlin
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)
}
}
}
15 Apr 2019
|
Kotlin
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?.length
도 null
을 리턴하기 때문에 length
변수가 null
이 됩니다.
즉, 위험할 수 있는 코드이기 때문에 아래와 같이 표현할 수 있습니다.
fun test() {
var str: String? = "hello"
str = null
var length: Int = str?.length ?: 0
}
위와 같이 코드를 작성하면 str?.length
가 null
일 경우 숫자 0 으로 리턴하기 때문에 length
변수는 항상 값을 가질 수 있습니다.
그리고 !!
기호를 사용하게 되면 명시적으로 변수에 절대 null
을 참조할 수 없다는 것을 지정할 수 있습니다.
fun test() {
var str: String? = "hello"
var length: Int = str!!.length
}
== 와 ===
==
와 ===
는 비교 연산자로 ==
는 Java에서 사용하던 ==
와 동일한 역할을 합니다. 그리고 내부적으로 NULL 체크를 하기 때문에 좀 더 간략하게 코드를 작성할 수 있습니다. 다만, a == b
에서 a
와 b
모두 null
인 경우 true
가 되기 때문에 주의할 필요는 있습니다.
===
의 경우는 두 변수가 정말 똑같은 주소값을 갖고 있는지 판단하며 Java에서의 equals()
와 동일합니다.