Oreo 버전부터는 일반적인 Background 서비스를 사용하기 까다로워 졌다.


일단 서비스 실행 후 앱이 보이지 않거나 (액티비티가 화면에 나오지 않는 등) 


Foreground 서비스를 사용하지 않거나


IME, 배경화면 서비스, 알림 리스너, 음성 또는 텍스트 서비스 중 하나를 사용하지 않는 이상


서비스는 오래가지 않아 종료된다.


Foreground 서비스는 서비스 실행과 동시에 알림(Notification)을 보여주어 사용자가 서비스가 동작 중이라는 것을 인지시켜 주며 실행을 유지한다.


- Foreground 서비스를 실행

startService(Intent(context, Service::class.java))

- 서비스 내에서 Foreground 실행 함수를 호출해야한다.

startForeground(FOREGROUND_ID, notification)

이렇게 사용하면 서비스는 알림을 띄우며 동작하게 된다.


이후 서비스를 종료하기 위해서는 서비스 클래스 내에서 stopSelf() 함수를 호출하거나


외부에서 context.stopService(intent)를 호출하면 된다.


종료하는 과정에서는 알림 역시 제거된다. 하지만 알림을 제거하지 않고 서비스를 종료하기 위해서는 클래스에서 또는 클래스 내에서 onDestroy 함수에서 

stopForeground(Service.STOP_FOREGROUND_DETACH)

를 추가하면 된다.


주의해야할 사항은 위 함수는 Nougat(API 24)이상부터 사용가능하다.


또한 boolean값을 받는 같은 함수가 존재하는데 false를 주어도 서비스 종료 시 알림이 제거된다.

by JamesY 2018. 10. 23. 22:56

Constraint Layout을 이용하여 간단한 이동 애니메이션을 구현할 수 있다.


먼저 Constraint Layout을 어떤식으로 사용하는지 알아야 하기 때문에 모를 경우 배우고 난 뒤 읽는 것을 추천


결과물





1. Floating action button을 Constraint Layout에 넣기

<android.support.constraint.ConstraintLayout
android:id="@+id/menu_layout"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="bottom|end" >

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@android:drawable/ic_dialog_alert"
app:fabSize="mini" />

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@android:drawable/ic_dialog_info"
app:fabSize="mini" />

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@android:drawable/ic_dialog_email" />

</android.support.constraint.ConstraintLayout>

fab은 메뉴 버튼, fab1과 fab2는 fab을 누를 시 나오는 항목들


2. fab 버튼 이벤트 구현

class MainActivity : AppCompatActivity() {

private var isMenuCollapsed = true

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

fab.setOnClickListener { view ->
val constraintSet = ConstraintSet()
constraintSet.clone(menu_layout)

if(isMenuCollapsed) {
// Set fab1's position to top of fab
constraintSet.connect(fab1.id, ConstraintSet.BOTTOM, fab.id, ConstraintSet.TOP)
// Set fab2's position to top of fab1
constraintSet.connect(fab2.id, ConstraintSet.BOTTOM, fab1.id, ConstraintSet.TOP)
}
else {
// Set fab1's position back to bottom
constraintSet.connect(fab1.id, ConstraintSet.BOTTOM, fab.id, ConstraintSet.BOTTOM)
// Set fab2's position back to bottom
constraintSet.connect(fab2.id, ConstraintSet.BOTTOM, fab.id, ConstraintSet.BOTTOM)
}
val transition = AutoTransition()
transition.duration = 300
transition.interpolator = AccelerateDecelerateInterpolator()

TransitionManager.beginDelayedTransition(menu_layout, transition)
constraintSet.applyTo(menu_layout)

isMenuCollapsed = !isMenuCollapsed
}
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu)
return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
return when (item.itemId) {
R.id.action_settings -> true
else -> super.onOptionsItemSelected(item)
}
}
}


이 방법은 menu_layout의 클론(constraintSet)을 만들고 fab1과 fab2의 새로운 위치를 지정 해주고 Transition을 한다. 결과적으로는 두 개의 menu_layout을 통해 애니메이션을 구현한 것이다.


Constraint를 잘 활용하면 예제처럼 아래서 위로 가는 것뿐만 아니라 오른쪽에서 왼쪽 또는 대각선 까지 다양한 방향 전환이 가능하다.


GitHub : https://github.com/Hot6ix/FloatingActionButton

참조 : https://robinhood.engineering/beautiful-animations-using-android-constraintlayout-eee5b72ecae3

'Android > Kotlin' 카테고리의 다른 글

커스텀 리스트뷰 ( Custom ListView)  (0) 2017.12.21
Volley를 이용한 간단한 네트워크 통신  (0) 2017.12.21
by JamesY 2018. 1. 9. 23:45

1. 기본 환경 세팅


- 두 액티비티 모두 Google Map 적용

- 사용자지정 액태비티 전환을 위한 코드 삽입

https://developer.android.com/training/material/animations.html?hl=ko#Transitions


2. Google Map에 적용

두 액티비티의 소스 코드 중 초기 Google Map Fragment를 가져오는 부분에 추가

val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.view!!.transitionName = resources.getString(R.string.shared_element_map)

Fragment의 View를 가져와 TransitionName을 넣는다. 

넣은 후 액티비티 전환을 위한 Intent의 Options을 정의할 때 다음과 같이 추가한다.

var options = ActivityOptionsCompat.makeSceneTransitionAnimation(this,
Pair<View, String>(map.view, ViewCompat.getTransitionName(map.view)))


by JamesY 2018. 1. 8. 17:54

Kotlin으로 작성된 커스텀 리스트뷰 예제



MainActivity.kt

import android.content.Context
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.TextView
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

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

val list: ArrayList<CustomItem> = ArrayList()
list.add(CustomItem("James", 23, "Male"))
list.add(CustomItem("Jamie", 20, "Female"))
list.add(CustomItem("John", 30, "Male"))

listview1.adapter = CustomAdapter(this, list)
}

private class CustomAdapter(context: Context, array: ArrayList<CustomItem>): BaseAdapter() {

private var list: ArrayList<CustomItem> = array
private var inflater: LayoutInflater = LayoutInflater.from(context)

override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View {

val view: View?
val viewHolder: ViewHolder

if(p1 == null) {
view = this.inflater.inflate(R.layout.listview_item, p2,false)
viewHolder = ViewHolder(view)
view.tag = viewHolder
}
else {
view = p1
viewHolder = view.tag as ViewHolder
}

viewHolder.name.text = list[p0].name
viewHolder.age.text = list[p0].age.toString()
viewHolder.gender.text = list[p0].gender

return view!!
}

override fun getItem(p0: Int): Any {
return list[p0]
}

override fun getItemId(p0: Int): Long {
return p0.toLong()
}

override fun getCount(): Int {
return list.size
}

}

private class ViewHolder(view: View?) {

val name: TextView = view?.findViewById(R.id.nameTextView) as TextView
val age: TextView = view?.findViewById(R.id.ageTextView) as TextView
val gender: TextView = view?.findViewById(R.id.genderTextView) as TextView
}

data class CustomItem(var name: String, var age: Int, var gender: String)
}


listview_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="match_parent">

<TextView
android:id="@+id/nameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="TextView" />

<TextView
android:id="@+id/ageTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="TextView" />

<TextView
android:id="@+id/genderTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="TextView" />
</LinearLayout>


GitHub : https://github.com/Hot6ix/SimpleCustomListView

by JamesY 2017. 12. 21. 17:52

Volley를 이용하여 간단한 HTTP 통신을 하는 예제이다.


1. AndroidManifest.xml 에서 권한 명시 

<uses-permission android:name="android.permission.INTERNET" />


2. build.gradle 내  dependencies 에 Volley 사용을 위한 명시

compile 'com.android.volley:volley:1.1.0'


3. Build - Make Project 를 하여 프로젝트에서 Volley 사용 가능


4. HTTP 통신 예제

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log.i
import com.android.volley.Request
import com.android.volley.RequestQueue
import com.android.volley.Response
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

lateinit var request: RequestQueue
lateinit var stringRequest: StringRequest

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

// Setup RequestQueue
request = Volley.newRequestQueue(this)
val url = "http://www.google.com"

// Setup StringRequest
stringRequest = StringRequest(Request.Method.GET, url, Response.Listener<String> {
response -> textView1.text = response // Print http source using textview
}, Response.ErrorListener {
error -> i(applicationContext.packageName, error.toString()) // Print log if error occurred
})

// Set tag for cancel
stringRequest.tag = applicationContext.packageName
// Request
request.add(stringRequest)
}

override fun onStop() {
super.onStop()

// Cancel all request that have packageName tag
if(request != null) {
request.cancelAll(applicationContext.packageName)
}
}
}


GitHub : https://github.com/Hot6ix/SimpleHttpRequest


출처 : https://developer.android.com/training/volley/simple.html

by JamesY 2017. 12. 21. 16:30
| 1 |