안드로이드는 여러 종류의 모바일폰과 태블릿 등 다양한 구동환경이 있다. 그런데 Activity는 하나의 화면 단위이기 때문에 구성요소가 많아지면 디스플레이 해상도에 따른 화면 구성 관리가 더욱 어려워진다.

그래서 사용되기 시작한 것이 Fragment이다. Fragment를 사용해 화면 구성을 나누고 화면의 각 부분별로 관리함으로써 다양한 디바이스(특히 디스플레이)에도 유연하게 대응할 수 있다.

일종의 파편화된 activity와 같다.

 

특징

Activity처럼 라이프 사이클이 존재하며 더욱 다양하다.

Activity에 종속적이다.

 

 

//fragment.ko 파일과 라이프 사이클
package com.example.myapplication

import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment

class Fragment1:Fragment() {
    override fun onAttach(context: Context) {
        Log.d("life_cycle","F onAttach aaa")
        super.onAttach(context)

    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        Log.d("life_cycle","F onCreateView aaa")
        //fragment가 interface를 처음으로 그릴 때 호출
        //inflator 뷰를 그리는 역할
        // container fragment를 붙일 부모 뷰

        return inflater.inflate(R.layout.fragment1, container,false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        Log.d("life_cycle","F onViewCreated aaa")
        super.onViewCreated(view, savedInstanceState)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        Log.d("life_cycle","F onActivityCreated aaa")
        super.onActivityCreated(savedInstanceState)
    }

    override fun onStart() {
        Log.d("life_cycle","F onStart aaa")
        super.onStart()
    }

    override fun onResume() {
        Log.d("life_cycle","F onResume aaa")
        super.onResume()
    }

    override fun onStop() {
        Log.d("life_cycle","F onStop aaa")
        super.onStop()
    }

    override fun onDestroyView() {
        Log.d("life_cycle","F onDestroyView aaa")
        super.onDestroyView()
    }

    override fun onDetach() {
        Log.d("life_cycle","F onDetach")
        super.onDetach()
    }
}

 

사용방법

방법1. Layout.xml에 <fragment> 컴포넌트를 명시적으로 작성해 놓는다. 정적.

방법2. 소스코드에서 fragment객체를 생성해서 xml에 동적으로 추가한다.

 

 

방법1. Layout.xml 파일에 명시적으로 <fragment> 작성

  1. 원하는 Fragment class 파일과 xml 파일을 생성한다.
  1. Activity 안에 fragment를 포함시킬 자리에 <fragment> 컴포넌트를 넣는다.
  1. 화면 구현을 위한 동작을 Activity파일에 작성한다.
  1. Activity.xml 파일에 있는 <fragment>컴포넌트에 속성 name 값을 fragment kotlin(or java)파일의 경로로 지정한다.
//activity_layout.xml에 선언되어 있는 <fragment> 컴포넌트
<fragment
	android:id="@+id/frag1"
  android:layout_width="match_parent"
  android:layout_height="300dp"
  android:name="com.example.myapplication.Fragment1"
  >
</fragment>

//fargment 파일 (fragment1.kt)
package com.example.myapplication

import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment

class Fragment1:Fragment() {
    override fun onAttach(context: Context) {
        Log.d("life_cycle","F onAttach aaa")
        super.onAttach(context)

    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        Log.d("life_cycle","F onCreateView aaa")
        //fragment가 interface를 처음으로 그릴 때 호출
        //inflator 뷰를 그리는 역할
        // container fragment를 붙일 부모 뷰

        return inflater.inflate(R.layout.fragment1, container,false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        Log.d("life_cycle","F onViewCreated aaa")
        super.onViewCreated(view, savedInstanceState)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        Log.d("life_cycle","F onActivityCreated aaa")
        super.onActivityCreated(savedInstanceState)
    }

    override fun onStart() {
        Log.d("life_cycle","F onStart aaa")
        super.onStart()
    }

    override fun onResume() {
        Log.d("life_cycle","F onResume aaa")
        super.onResume()
    }

    override fun onStop() {
        Log.d("life_cycle","F onStop aaa")
        super.onStop()
    }

    override fun onDestroyView() {
        Log.d("life_cycle","F onDestroyView aaa")
        super.onDestroyView()
    }

    override fun onDetach() {
        Log.d("life_cycle","F onDetach")
        super.onDetach()
    }
}

//fragment1.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"
    android:background="#654321">

</LinearLayout>

 

 

방법2. 소스코드에서 fragment인스턴스를 생성해 xml에 동적으로 추가.

  1. 원하는 fragment class와 xml을 작성한다.
  1. Activity class에서 fragmentManager를 통해 fragment를 제어하도록 정의한다.

 

//Activity에서 fragment를 동적으로 동작
findViewById<Button>(R.id.fragBtn).setOnClickListener {
   val frag1 : Fragment1 = Fragment1()
   val fragmentManager:FragmentManager = supportFragmentManager

   val fragmentTransaction = fragmentManager.beginTransaction()
   fragmentTransaction.replace(R.id.fragContainer, frag1)
   fragmentTransaction.commit()
   //commit() 시스템이 알아서 실행
   //commitnow() 바로 실행
}

//Activity에서 fragment를 동적으로 삭제
findViewById<Button>(R.id.fragCABtn).setOnClickListener {
   val fragmentManager:FragmentManager = supportFragmentManager

   val fragmentTransaction = fragmentManager.beginTransaction()
   //detach(frag1) 으로 제거시 다시 붙이기가 안된다.
   //remove는 제거 후 다시 붙이기도 가능하다.
   fragmentTransaction.remove(frag1)
   fragmentTransaction.commit()
}

 

Activity에서 Fragment로 data 전송

Android에서 제공하는 bundle 객체를 이용해서 key-value형식으로 데이터를 전송할 수 있다.

  1. bundle 인스턴스 생성
  1. bundle 인스턴스 에 key-value 형식으로 값을 바인딩.
  1. 전달하려는 fragment 인스턴스 arguments 필드에 bundle 객체를 바인딩 한다.

 

// activity.ko 파일에서
// bundle 타입 객체를 생성한다.
				val bundle:Bundle = Bundle()
// bundle 타입 객체에 키-값을 묶는다.
        bundle.putString("hello","hello")
// fragment 객체에 arguments 필드에 번들을 대입하는 것으로 해당 fragment에서 사용할 수 있다.
        frag1.arguments = bundle

 

fragment 객체에서는 argument필드에서 해당 값을 꺼내 쓸 수 있다.

단 null을 주의해야 한다.

// fragment1.ko 파일
		override fun onActivityCreated(savedInstanceState: Bundle?) {
        Log.d("life_cycle","F onActivityCreated aaa")
        val data = arguments?.getString("hello")
		
        super.onActivityCreated(savedInstanceState)
    }

 

 

동적으로 fragment를 추가할 경우 주의해야 하는 점

fragment class 인스턴스 생성시 파라미터를 전달하거나, 입력값이 필요할 경우가 있다.

이런 경우 동적으로 fragment를 생성하면서 값을 전달하면 되는데, 만약 xml에 명시적으로 해당 fragment를 미리 선언해 사용하고 있다면 전달값이 없이 activity가 생성될때 fragment를 생성하려 하기

// 만약 fragment1이 activity.xml에 포함이 되어 있다면, activity가 생성되면서
// fragment1 객체를 생성하려 하지만, onActivityCreated() 메서드가 실행될 때 전달 받은
// arguments를 찾을 수가 없어서 에러가 발생한다.
// 왜냐면 해당 arguments는 런타임시 전달되는 값이기 때문이다.
// 따라서 activity.xml 상에는 fragment1을 명시적으로 선언해 놓지 않아야 한다.

// activity.xml 상에서 fragment를 명시해놓은 경우.
// activity가 생성되면서 바로 fragment 객체도 생성하려 한다.
			<fragment
            android:id="@+id/frag1"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:name="com.example.myapplication.Fragment1"
            >
        </fragment>

 

Fragment에서 Activity로 데이터 전송

Activity에서 fragment로 데이터를 전송할 땐 bundle을 지원하는것과 달리 반대로는 직접 작성해야한다.

  1. fragment에서 Listener 인터페이스를 선언한다.
  1. lateinit 변수로 해당 리스너 변수를 선언한다. (생성, 초기화 x)
  1. onAttach() 메서드 내에서 2에서 선언한 변수에 context를 캐스팅해 넣는다.
  1. onViewCreated() 메서드 안에서 리스너 객체를 사용해 선언해 놓은 메서드 인자값으로 데이터를 넣어준다.
  1. activity 파일에서 fragment에 선언되어 있는 Listener 인터페이스를 구현하고 해당 메서드에서 인자값을 받아 데이터를 사용한다.

 

// Fragment.ko 에서 인터페이스 선언
interface  OnDataPassListener{
		fun onDataPass(data: String?)
}

// 해당 인터페이스 변수 선언
lateinit var dataPassListener: OnDataPassListener

// as(casting) 으로 위에 선언한 변수에 context를 대입해준다.
override fun onAttach(context: Context) {
    super.onAttach(context)
		dataPassListener = context as OnDataPassListener
}

// Listener 객체로 메서드를 호출하면서 전달할 데이터를 인자로 넣어준다.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    view.findViewById<Button>(R.id.fragSubBtn).setOnClickListener {
		    dataPassListener.onDataPass("Good bye")
		}
}

// Activity.ko 파일에서 Fragment에 선언된 Interface인 OnDataPassListener를 구현한다.
// 해당 인터페이스에 있는 onDataPass() 메서드를 구현하면서 인자로 data를 받는다.
class FragmentActivity : AppCompatActivity() , Fragment1.OnDataPassListener{
	  override fun onDataPass(data: String?) {
        Log.d("pass",data.toString())
    }
}

 

 

'Android > Android기본' 카테고리의 다른 글

Resource  (0) 2021.11.24
Binder, Binding  (0) 2021.11.24
Context  (0) 2021.11.24
Thread  (0) 2021.11.24
Permission  (0) 2021.11.24

App에서 사용되는 자원들을 저장하고 App에서 접근하는 디렉터리

 

drawable

이미지

 

layout

view 컴포넌트 정의 문서

 

values

하드 코딩을 피하고 key-value로 원하는 값들을 저장해 놓고 사용하거나, 관리-수정도 가능하다.

 

colors.xml

컬러명과 컬러를 선언해 놓는다.

다른 뷰(xml) 에서는 @color/col_name 으로 불러올 수 있고, 나중에 변경도 쉽게 할 수 있다.

 

strings.xml

string 이름과 내용을 선언해 놓는다.

@string/string_name 으로 불러와서 사용할 수 있다.

 

theme

app에 적용할 theme를 지정해놓을 수 있다.

theme는 primary color나 secondert color 등을 지정할 수 있다.

mainfests에서 적용할 theme를 선택해서 적용할 수 있다.

// 주로 추가하는 att는 app 상단 title을 숨긴다. <item name="windowNoTitle">true</item>

 

 

 

코드상에서 resource 접근

R.drawable.<drawable_name> R.color.<color_name> R.string.<string_name>  //deprecated resources.getColor(R.color.<color_name>) resources.getString(R.string.<string_name>)

 

코드상에서 view component 값 변경

//finViewById findViewById<type>(R.id.<component_id>).setBackgroundColor(<color>)  //Binding binding.component_id.text = "_text"

'Android > Android기본' 카테고리의 다른 글

Fragment  (0) 2021.11.24
Binder, Binding  (0) 2021.11.24
Context  (0) 2021.11.24
Thread  (0) 2021.11.24
Permission  (0) 2021.11.24

findViewById 를 매번 사용하기보단 편하게 view와 code를 이어주기 위한 도구.

Kotlin android extensions 사용이 간편했기에 많이 사용하였지만, app의 크기가 커지면서 view가 많아지고 같은 id가 사용되는 경우가 생기면서 실수하기 쉬운 요소가 되었다. 이를 해결하기 위해서 간편하게 사용하면서도 바인딩 하려는 layout을 명시적으로 사용하여 실수를 최소화하기 위해 고안된 것이 Binder 이다.

 

//gradle(module)에 binding 요소를 추가한다. android {         ...         viewBinding {             enabled = true         } }  // lateinit으로 변수명을 선언한다. private lateinit var binding: <ActivityNameBinding>   override fun onCreate(savedInstanceState: Bundle?) { 	super.onCreate(savedInstanceState)  	// lateinit으로 선언된 변수 binding에 매치시킨다. 	binding = ActivityNameBinding.inflate(layoutInflater)  	//contentView에 등록시킨다. 	val view = binding.root   setContentView(view)  	//view component의 id로 접근이 가능해진다. 	binding.<ViewCompont_id> } 

단, Activity가 아닌 Fragment에서는 사용법 차이가 있다. 특히 Nullable 처리를 별도로 필요로 한다.

fragment가 해제 될때 binding도 해제해준다.

private var _binding: GuideFragmentBinding? = null private val binding get() = _binding!!  override fun onCreateView(         inflater: LayoutInflater,         container: ViewGroup?,         savedInstanceState: Bundle?     ): View? {         _binding = GuideFragmentBinding.inflate(inflater,container,false)         val view = binding.root          return view }  override fun onDestroyView() {     super.onDestroyView() 		_binding = null } 

레이아웃을 xml에 선언해 놓은 것이 아니라 코드상 include해서 사용하는 경우, 아래와 같이 subView 필드로 접근한다.

// include된 layout에 접근할 경우. binding.subView.<viewComponent_id>

'Android > Android기본' 카테고리의 다른 글

Fragment  (0) 2021.11.24
Resource  (0) 2021.11.24
Context  (0) 2021.11.24
Thread  (0) 2021.11.24
Permission  (0) 2021.11.24

ActivityManagerService에 접근 하도록 해주는 역할을 한다.

Android에 이미 만들어져 있는 기능(패키지)들에 접근하려할 때 Context 객체를 사용하게 된다.

AppCompatActivity 가 Context를 구현하고 있고, 모든 Activity는 AppCompatActivity는 상속하고 있다. 때문에 Activity 자신(this)를 지정해서 context에 선언된 필드와 메서드에 접근이 가능하다.

val contex:Context = this

 

종류

activity context

특정 activity에 대한 정보, 기능 들을 포함한다.

 

application context

application에 대한 전체적인 정보, 기능을 포함한다.

 

//바로 접근이 가능하다. applicationContext  //안드로이드 버전이 낮거 getApplicationContext()으로 호출해서 사용한다. val applicationContext = getApplicationContext()

 

 

'Android > Android기본' 카테고리의 다른 글

Resource  (0) 2021.11.24
Binder, Binding  (0) 2021.11.24
Thread  (0) 2021.11.24
Permission  (0) 2021.11.24
AddView  (0) 2021.11.24

시스템이 작업을 처리하는 단위로 MainThread는 자동으로 동작한다. 필요시 추가적인 Thread가 생성된다.

 

MainThread

 MainThread의 명칭은 UI Thread 인데, 주 역할로 사용자와 상호작용하며 Input을 받는다.

 정지시킬 수 없는 Thread로 장기간 대기상태로 놓거나 정지, 종료시키면 앱이 중지된다.

 통신 등을 UI Thread에서 처리하려 할 경우 응답대기 시간 동안 Thread가 정지하게 되고 crash가 발생하면서 앱이 종료되기 쉽다. 때문에 UI Thread에선 연산이 복잡하거나 대기가 필요한 작업을 수행하지 않아야 한다.

 

 

View Component를 새로운 Thread에서 조작하려 할 때,

view component를 생성한 Thread만이 해당 component를 조작할 수 있다.

기본 화면은 UIThread가 생성하기 때문에 다른 Thread에서 컴포넌트를 조작하려 하면 예외가 발생한다.

이를 피하기 위해서 runOnUiThread{ } 람다식을 이용한다.

runOnUiThread { 	findViewById(R.id.<component_id>).setBackgroundColor(getColor(R.color.<color_name>)) }

 

 

비동기 작업은 Coroutine이나 RxJava 등으로 처리하도록 한다.

 

'Android > Android기본' 카테고리의 다른 글

Binder, Binding  (0) 2021.11.24
Context  (0) 2021.11.24
Permission  (0) 2021.11.24
AddView  (0) 2021.11.24
ListView  (0) 2021.11.24

 장치에서 제한하는 기능을 사용하기 위해선 해당 권한을 선언해야 하고, 중요한 권한에 대해서는 사용자에게 요청을 해서 권한을 받아야 사용할 수 있다.

 

권한은 manifests 에 선언한다.

 

네트워크 권한

// app에서 네트워크를 사용하려면 메니페스트에 다음 두 줄을 추가한다. <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

 

위치기반(GPS) 권한

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

 

블루투스 권한

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

 

외부 저장소 권한

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

 

권한 요청 및 결과 처리

activity에서 필요한 권한을 요청하고, 결과를 받아서 결과에 따른 작업을 처리한다.

//권한 요청 ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_REQUEST_CODE)  const val PERMISSION_REQUEST_CODE = 1000 val PERMISSIONS = arrayOf( 	android.Manifest.permission.ACCESS_FINE_LOCATION   ,android.Manifest.permission.ACCESS_COARSE_LOCATION )  //권한 요청 결과 처리 override fun onRequestPermissionsResult(         requestCode: Int,         permissions: Array<out String>,         grantResults: IntArray ) { 		super.onRequestPermissionsResult(requestCode, permissions, grantResults)     when(requestCode) { 		    PERMISSION_REQUEST_CODE -> { 		        todoFunction() 				} 	  } }

'Android > Android기본' 카테고리의 다른 글

Context  (0) 2021.11.24
Thread  (0) 2021.11.24
AddView  (0) 2021.11.24
ListView  (0) 2021.11.24
RecyclerView  (0) 2021.11.24

addView

뷰를 추가하는 가장 기본 형태인데, 실제로 리스트 형식을 보여줄 때는 addView는 잘 사용하지 않고 이를 구조화하고 개선시킨 ListView, RecycleView가 주로 사용된다.

  1. Item을 담을 xml을 만든다.
  1. xml에 내용물을 채운다.
  1. Container view에 더해준다.
  1. 1-3을 필요만큼 반복한다.

 

 

  1. 화면에 리스트로 보여줄 대상이 되는 리스트를 준비한다.
  1. Inflater를 준비한다.
  1. Inflater로 목록 하나에 대응하는 아이템 뷰를 반복해서 만들어준다.
  1. 컨테이너에 붙여준다.

 

'Android > Android기본' 카테고리의 다른 글

Thread  (0) 2021.11.24
Permission  (0) 2021.11.24
ListView  (0) 2021.11.24
RecyclerView  (0) 2021.11.24
TabLayout, Pager  (0) 2021.11.24

 

ListView는 AddView에 비해 자원을 훨씬 효율적으로 사용할 수 있다.

AddView와 ListVew 차이점

  1. View를 생성하고 구현하는 과정이 다르다.
  1. 그리는 방식이 addView는 모든 뷰를 바로 생성하지만, ListView는 보이는 부분 + 일부분 정도만 만들고, 필요한 경우에 따라 추가로 view를 생성한다.

 

 

ListView 구현과정

  1. 리스트로 보여줄 대상이 되는 리스트를 준비한다. (화면에 출력할 객체List와, view를 담을 <ListView> Component )
  1. 리스트 뷰를 만들어 줄 Adapter를 구현하고, <ListView>에 adapter를 적용해 view를 그린다.

 

대상이 될 객체 List를 준비한다.

val carList = ArrayList<CarForList>()

 

xml에 <ListView> Component를 생성하고 id를 부여한다.

	<ListView         android:id="@+id/listView"         android:layout_width="match_parent"         android:layout_height="match_parent"> 	</ListView>

 

Adapter를 구현한다.

class ListViewAdapter(val carList: ArrayList<CarForList>, val layoutInflater: LayoutInflater ): BaseAdapter() {      override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {         val view = layoutInflater.inflate(R.layout.item_view, null)         var carNameTextView = view.findViewById<TextView>(R.id.car_name)         var carEngineTextView = view.findViewById<TextView>(R.id.car_engine)          carNameTextView.setText(carList.get(position).name)         carEngineTextView.setText(carList.get(position).engine)          return view     }      override fun getItem(position: Int): Any {         return carList.get(position)     }      override fun getItemId(position: Int): Long {         return position.toLong()     }      override fun getCount(): Int {         return carList.size     } }

 

객체 List를 넣어서 adapter 인스턴스를 생성하고, <ListView>에 적용한다.

val adapter = ListViewAdapter(carList, LayoutInflater.from(this@ListViewActivity)) val listView = findViewById<ListView>(R.id.listView) listView.adapter = adapter

 

 

List에 click 이벤트로 Toast 출력하기.

val listView = findViewById<ListView>(R.id.listView) listView.adapter = adapter listView.setOnItemClickListener{ parent, view, position, id -> 	val car = adapter.getItem(position) as CarForList   Toast.makeText(this@ListViewActivity, car.name + " " + car.engine, Toast.LENGTH_LONG).show() }

 

List에 기본 divider(border) 제거하기

divider 뿐 아니라 여러 속성이 있다 필요시 찾아서 사용하면 된다.

 

//<ListView>에 divider="@null" 속성 추가 android:divider="@null"

 

ListView 사용시 성능 향상을 위한 Holder 사용

ListView 생성시 view를 계속 생성하기 보단, Holder를 사용하면 생성한 View에 값을 변경해 View를 재사용하는 것으로 디바이스 자원을 절약할 수 있다.

한 화면에 표시할 수 있는 최대 View의 갯수만큼은 새롭게 생성 되지만, 화면이 스크롤해서 넘어가면 화면에서 벗어난 View를 다시 가져와 값만 새롭게 넣어서 화면에 출력하는 방식으로 동작된다.

 

 

Holder 구현

class ViewHolder {     var carName: TextView? = null     var carEngine: TextView? = null }

 

Holder를 사용한 View 생성

override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {          val view : View         val holder: ViewHolder          if(convertView == null) {             view = layoutInflater.inflate(R.layout.item_view, null)             holder = ViewHolder()             holder.carName = view.findViewById(R.id.car_name)             holder.carEngine = view.findViewById(R.id.car_engine)              view.tag = holder         } else {             holder = convertView.tag as ViewHolder             view = convertView         }          holder.carName?.setText(carList.get(position).name)         holder.carEngine?.setText(carList.get(position).engine)          return view     }

'Android > Android기본' 카테고리의 다른 글

Permission  (0) 2021.11.24
AddView  (0) 2021.11.24
RecyclerView  (0) 2021.11.24
TabLayout, Pager  (0) 2021.11.24
SharedPreference  (0) 2021.11.24

+ Recent posts