액티비티 생명주기
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // 현재 UI를 일단 저장해둠
setContentView(R.layout.activity_main) // R.layout.activity_main이 View 타입 반환
}
}
-
onCreate()
- 데이터를 목록에 바인딩, ViewModel과 활동 연결등 화면 초기구성 관련 코드를 수행
- 이후 onStart()와 onResume()를 연달아서 호출함
- Activity 처음 생성될 때 실행됨
-
onStart()
- Activity를 포그라운드에 보내 상호작용할 수 있도록 준비함. 예를들어 이 메서드에서 앱이 UI를 관리하는 코드를 초기화함
- Activity가 보여지기 직전에 호출됨
-
onPause()
- 사용자가 Activity를 떠나면 이 메서드를 호출함. Activity가 포그라운드에 있지 않게 됐다는걸 의미함. (사용자가 멀티윈도우 모드면 여전히 표시가 가능)
- 상태바를 내리거나 긴급메세지가 팝업되거나 하는등 다른 Activity가 화면에 나타나는 시점에 호출됨
-
onResume()
- Activity가 포그라운드에 표시됨. 여기서 어떤 이벤트가 발생해서 포커스가 변경될 때 까지 앱이 onResume()상태에 머무름.
- 전화가 오거나, Activity가 전환되거나 했을 경우 onResume()을 벗어남
- Activity가 보여지고 Input을 받을 수 있는 상태에서 호출됨
-
onRestart()
- onPause()가 호출되었던 Activity를 복원시킬 때 호출됨
-
onStop()
- 상태바, 긴급메세지 등 일시정지인 상태가 아니라 뒤로가기로 App이 더이상 화면에 나오지 않게 했을 때 호출됨
- App 뒤로가기 한 다음에 멀티 윈도우 버튼 누르면 App이 여전히 있는것을 확인할 수 있음
- 상태바, 긴급메세지 등 일시정지인 상태가 아니라 뒤로가기로 App이 더이상 화면에 나오지 않게 했을 때 호출됨
-
onDestory()
- 멀티 윈도우에 있던 App이 오랜시간이 지나면 사용자가 App을 안쓴다고 판단해서 핸드폰이 자체적으로 대기중인 App을 종료시켜버림. 그때 호출됨
네이밍 규칙
- 액티비티에 대한 모든 컴포넌트는 액티비티 이름으로 시작해야 함
activity_login_btn_login
activity_login_et_username
activity_login_et_password
- 클래스, 메소드명은 파스칼 표기법(여러 단어를 붙여서 한 단어로 표기할 때 각 단어의 첫 문자를 대문자로 하는 것)
public class HelloWorld{
public void HelloCity(){
}
}
- 변수, 파라미터 등은 카멜 표기법
int totalCost = 0;
String fullName = "";
public void HelloCity(String familyName){};
안드로이드 4가지 구성요소
- Activity
- Service
- Broadcast Receiver
- Content Provider
Intent
Intent는 위 4가지 구성요소(component)간에 작업 수행을 위한 정보 전달 역할
- 명시적 인텐트
- 인텐트에 클래스 객체나 컴포넌트 이름을 지정하여 호출될 대상을 명확히 알 수 있는 경우
val intent = Intent(this, SubActivity::class.java)
startActivity(intent)
- 암시적 인텐트
- 호출된 작업의 속성만 지정해 두었으며 호출될 대상이 달라질 수 있는 경우
- 인텐트 필터 개념은 암시적 인텐트에만 쓰임 (해석하는데 필요)
val intent = Intent(Intent.ACTION_DIAL)
val TEST_DIAL_NUMBER = Uri.fromParts("tel", "5551212", null)
intent.data = TEST_DIAL_NUMBER
startActivity(intent)
- 위 그림과 같이 Intent action은 String 타입임
Intent-filter
- 구성요소는 3가지
- <action>
- name 속성에서 허용된 intent 작업 선언. 문자열이어야 함
- <data>
- 허용된 데이터 유형 선언. 하나 이상의 속성을 사용하여 데이터 URI, MIME 유형 나타냄
- <category>
- name 속성에서 허용된 인텐트 카테고리 선언. 문자열이어야 함
- <action>
- 아래 예시를 통한 이해가 훨씬 빠를
<activity android:name="MainActivity">
<!-- 애플리케이션의 진입 액티비티, 런처에서 나타납니다 -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="ShareActivity">
<!-- 이 액티비티는 텍스트데이터와 함께 SEND 액션을 수행합니다 -->
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
<!-- 이 액티비티는 미디어데이터와 함께 SEND와 SEND_MULTIPLE을 수행합니다-->
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.SEND_MULTIPLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="application/vnd.google.panorama360+jpg"/>
<data android:mimeType="image/*"/>
<data android:mimeType="video/*"/>
</intent-filter>
</activity>
- MainActivity
- ACTION_MAIN 작업은 이곳이 앱 주요 진입 지점이며 사용자가 어플 아이콘을 클릭해서 처음 시작할 때 열리는 액티비티임을 명시하는것
- CATEGORY_LAUNCHER 카테고리는 이 액티비티 아이콘이 시스템의 앱 시작 관리자에 배치되어야 한다는것을 의미
- ShreActivity는 텍스트, 미디어 콘텐츠 공유를 용이하게 할 목적임
- MainActivity에서 직접 진입할수 있지만 전혀 다른 앱에서 두가지 인텐트 필터 중 하나와 일치하는 암시적 인텐트를 발생시키는 전혀 다른 앱에서 SharedActivity에 직접 진입할 수 있음
Content-Provider
데이터 제공하는 역할을 하며 여러 App들이 서로 데이터를 공유하는 유일한 방법
File-Provider
위 Content-Provider를 상속하며 안전한 파일 공유가 가능하게함
- file:// 형태의 uri -> content:// 형태의 uri로 변경
- content URI는 임시 권한 부여가 가능
- 사용법
manifest.xml 파일
<manifest>
...
<application>
...
<provider
android:name="androidx.core.content.FileProvider" // 이건 고정
android:authorities="com.mydomain.provider" // 도메인이름.fileprovider 혹은 도메인이름.provider
android:exported="false" // 다른 app에서 접근못하게 하도록 false
android:grantUriPermissions="true"> // 임시 권한 부여를 위해 해당속성을 true로 변경
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS" // 고정
android:resource="@xml/file_paths"> // 정의한 xml 파일을 연결(이 xml 파일에 경로 명시함)
</provider>
...
</application>
</manifest>
xml/file_paths.xml 파일
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images/" /> // Context.getFilesDir()에 해당하는 영역
// name 속성은 실제 path를 숨기기 위한 일종의 별명
// path 속성의 값이 실제 경로임. 파일이 아니라 디렉토리를 넣어줘야 한다는것 주의
// 조금더 해석하자면 실제 경로는 Context.getFilesDir()/images/ 에 해당하는 모든 파일
// contentURI는 content://com.mydomain.fileprovider/my_images/default_image.jpg로 나옴
<files-path name="my_docs" path="docs/"/>
</paths>
File imagePath = new File(Context.getfilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), "com.mydomain.provider", newFile);
- paths 태그 들어갈 수 있는 것
- files-path
- 여러개 들어갈 수 있음
- Context.getFilesDir()에 해당하는 영역
- cache-path
- Context.getCacheDir()에 해당하는 영역
- external-files-path
- Context.getExternalFilesDir(String)에 해당하는 영역
- external-cache-path
- Context.getExternalCacheDir()에 해당하는 영역
- external-media-path
- Context.getExternalMediaDirs()에 해당하는 영역
- files-path
레이아웃 인플레이터
XML에 미리 정의해둔 틀을 실제 메모리에 올려주는 역할. 즉 LayoutInflater는 XML에 정의된 Resource를 View 객체로 반환해주는 역할
setContentView(R.layout.activity_main) // 이것도 역시 Inflater 역할
FrameLayout container = (FrameLayout) findViewById(R.id.container);
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.sub1, container, true); // R.layout.sub1: 객체화 하고픈 xml 파일
// 객체화한 View를 넣을 부모 레이아웃
// 바로 인플레이션 할지 여부
// 위 코드를 통해 사전에 미리 선언한 container 레이아웃에 작성한 xml 메모리 객체가 삽입된다.
뷰 바인딩
- findViewById로 일일이 xml과 매칭시키는것은 상당히 귀찮음. xml 파일의 id를 바로 kotlin class에서 사용할 수 있도록 해줌
Activity 에서 뷰 바인딩
android {
.
.
.
buildFeatures {
viewBinding = true
}
}
// 뷰바인딩 적용
class RecyclerActivity : AppCompatActivity() {
private lateinit var binding: ActivityRecyclerBinding // Activity 이름에 걸맞게 타입을 적어줘야 함
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityRecyclerBinding.inflate(layoutInflater) // 여기도 마찬가지
setContentView(binding.root) // 여기도 변경
}
}
Fragment 에서 뷰 바인딩
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
Activity 내에서 Fragment 보여줘야 하는 상황일 때 뷰 바인딩
- ChangeFragmentActivity.kt
package com.example.android_study
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.example.android_study.databinding.ActivityChangeFragmentBinding
import com.example.android_study.databinding.FragmentMyBinding
class ChangeFragmentActivity : AppCompatActivity() {
private lateinit var binding : ActivityChangeFragmentBinding
private lateinit var myFragment: MyFragment
private lateinit var mySecondFragment: MySecondFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityChangeFragmentBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.fragmentChangeBtn1.setOnClickListener {
myFragment = MyFragment.newInstance("hohohoho", "dfsfs")
supportFragmentManager.beginTransaction().replace(R.id.activity_change_frame_layout, myFragment).commit()
}
binding.fragmentChangeBtn2.setOnClickListener {
mySecondFragment = MySecondFragment.newInstance("hohohohoasfslkadjf", "Asdfsa")
supportFragmentManager.beginTransaction().replace(R.id.activity_change_frame_layout, mySecondFragment).commit()
}
}
}
- MyFragment.kt
package com.example.android_study
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.android_study.databinding.FragmentMyBinding
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [MyFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class MyFragment : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
private var _binding: FragmentMyBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
_binding = FragmentMyBinding.inflate(inflater, container, false)
return binding.root ?
}
override fun onDestroyView() {
_binding = null // Fragment가 없어질 때 _binding을 null 처리함
super.onDestroyView()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.textview.text = param1
// super.onViewCreated(view, savedInstanceState)
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment MyFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
MyFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
해상도 단위
- dp, dpi (px대신 이것 사용)
- 여러 단말기 화면에서 앱의 전체 비율을 유지하기 위함
- sp (텍스트에서는 이것 사용)
- 단말기 자체의 글자 크기(크게, 보통, 작게)를 반영하는 단위
레이아웃
- 위 그림에서 weight를 3, 2를 줬는데 버튼 2까지가 화면상 가운데까지 차지하는게 아니라 넘어버린다. wrap_content는 버튼의 최소 크기를 가지고 있기 때문이다. 따라서 0dp로 만들어줘야 한다
버튼
이미지 넣기
- 커스텀 버튼을 사용하고 싶은 경우 android.widget.Button을 사용한다
- 그냥 Button은 몇가지 속성을 무시해서 변경해도 적용이 안된다
<android.widget.Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/custom_youtube_button"
android:drawableLeft="@drawable/youtube_button"
android:text="Button"/>
- drawable 폴더에 이미지 넣기
- drawable(우클릭) -> New -> Resource File 생성해서 xml 파일 이름 지정(같은 파일 이름이면 안됨)
- layer list, item 태그 활용
xml로 모양 수정
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#31D1BF"/>
<corners android:radius="50dp" />
<size android:height="70dp" android:width="200dp"/>
<stroke android:width="3dp" android:color="#A232D3"/>
<gradient
android:startColor="#0BFBB9"
android:centerColor="#4417EA"
android:endColor="#A32E3B"
android:gradientRadius="300dp"
android:centerX="1"
android:centerY="1"
android:type="radial"
</shape>
- 원을 중심으로 gradient를 줄 것이며 300dp 만큼 주겠다. 이때 원의 포지션은 (1, 1) 이다
- 왼쪽 위 (0, 0), 오른쪽 아래 (1, 1)
버튼 눌렀을 때 모양 변경되게
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#31D1BF"/>
<corners android:radius="50dp" />
<size android:height="70dp" android:width="200dp"/>
<stroke android:width="3dp" android:color="#A232D3"/>
<gradient
android:startColor="#0BFBB9"
android:centerColor="#4417EA"
android:endColor="#A32E3B"
android:gradientRadius="300dp"
android:centerX="1"
android:centerY="1"
android:type="radial"/>
</shape>
</item>
<item android:state_pressed="false">
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#31D1BF"/>
<corners android:radius="50dp" />
<size android:height="70dp" android:width="200dp"/>
<stroke android:width="3dp" android:color="#A232D3"/>
</shape>
</item>
</selector>
- item 태그로 이벤트 설정한 뒤 그 안에서 shape 지정
EditText 키보드 이벤트 처리 (키보드 숨기기)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val et_id = findViewById<EditText>(R.id.et_id)
val et_pw = findViewById<EditText>(R.id.et_pw)
// 키보드의 엔터가 눌렸을 때 이벤트 처리하기 위한 코드
et_pw.setOnEditorActionListener() { v, actionId, event ->
if(actionId == EditorInfo.IME_ACTION_DONE){// 엔터가 눌렸다면
Login(v) // 아래 정의한 함수를 호출하라
true // 반환 타입은 boolean
} else {
false // 반환 타입은 boolean
}
}
}
fun Login(v: View){
// 키보드 숨기기
var imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
Log.d(TAG, "Login: 로그인 성공")
Toast.makeText(this, "로그인 성공", Toast.LENGTH_LONG).show()
}
}
EditText 로그인 처리 및 불러오기
class MainActivity : AppCompatActivity() {
lateinit var et_id:EditText
lateinit var et_pw:EditText
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
et_id = findViewById<EditText>(R.id.et_id)
et_pw = findViewById<EditText>(R.id.et_pw)
// 아이디, 비밀번호가 저장되어 있다면 불러오고 그렇지 않으면 빈칸으로 세팅함
// onCreate에서 이렇게 구현하면 한번만 로그인에 성공했던 디바이스의 경우 매번 로그인정보가 불러와지는 효과를 가진다
var pref = this.getPreferences(0)
et_id.setText(pref.getString("아이디", ""))
et_pw.setText(pref.getString("비밀번호", ""))
et_pw.setOnEditorActionListener() { v, actionId, event ->
if(actionId == EditorInfo.IME_ACTION_DONE){
Login(v)
true
} else {
false
}
}
}
fun Login(v: View){
if(et_id.text.toString() == "hojun" && et_pw.text.toString() =="0303"){
Toast.makeText(this, "로그인 성공", Toast.LENGTH_LONG).show()
var editor = this.getPreferences(0).edit()
editor.putString("아이디", "hojun").apply()
editor.putString("비밀번호", "0303").apply()
}
else{
Toast.makeText(this, "로그인 실패", Toast.LENGTH_LONG).show()
}
// 키보드 숨기기
var imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
}
}
비디오뷰
- raw 파일 추가 및 mp4 파일 추가
- activity_video.xml ```xml <?xml version=”1.0” encoding=”utf-8”?>
- VideoActivity.kt
```kotlin
package com.example.android_study
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.MediaController
import com.example.android_study.databinding.ActivityVideoBinding
class VideoActivity : AppCompatActivity() {
private lateinit var binding: ActivityVideoBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityVideoBinding.inflate(layoutInflater)
setContentView(binding.root)
val videoPath = "android.resource://" + packageName + "/" + R.raw.lol
binding.activityVideoVideoView.setVideoPath(videoPath)
val mediaController = MediaController(this)
binding.activityVideoVideoView.setMediaController(mediaController)
mediaController.setAnchorView(binding.activityVideoVideoView)
binding.activityVideoVideoView.keepScreenOn = true
}
}
PREVIOUS경제