반응형

fragment 를 이동하는 방법에는 두가지가 있다.

 

navication component 를 사용하는것과 사용하지 않는것.

 

1~2 페이지만 있다면 일반

뒤로가기, 딥링크, 애니메이션 등 효과를 줘야한다면 navigation component 사용하면 된다

 

1. Navigation graph 설정

네비게이션 컴포넌트를 사용하려면 navigation graph 를 추가해야한다.

그전에 우선 build.gradle 을 수정하자

 

1) build.gradle 수정

dependencies {

...
    def nav_version = "2.4.1"

    // navigation
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}

 

2) Main Fragment 생성

Main Activity 에 띄울 mainFragment 를 생성한다

fragment_main.xml 과 MainFragment.kt 파일을 각각  생성하자

 

build.gradle 데이터바인딩 설정 추가

android {
...
    dataBinding {
        enabled = true
    }
}

dependencies {
...

 

fragment_main.xml 추가

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white">

        <EditText
            android:id="@+id/et_message"
            android:layout_width="144dp"
            android:layout_height="35dp"
            android:background="#ffdddd"
            app:layout_constraintBottom_toTopOf="@id/btn_move_second"
            app:layout_constraintEnd_toEndOf="@id/btn_move_second"
            app:layout_constraintStart_toStartOf="@id/btn_move_second" />

        <Button
            android:id="@+id/btn_move_second"
            android:layout_width="150dp"
            android:layout_height="wrap_content"
            android:text="go second"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/btn_move_third"
            android:layout_width="150dp"
            android:layout_height="wrap_content"
            android:text="go Third"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/btn_move_second" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

 

MainFragment 추가

class MainFragment : Fragment() {

    private var _binding: FragmentMainBinding? = null
    private val binding: FragmentMainBinding
        get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = DataBindingUtil.inflate(
            layoutInflater, R.layout.fragment_main, container, false
        )

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.apply {
            btnMoveSecond.setOnClickListener {

            }

            btnMoveThird.setOnClickListener {

            }
        }
    }

}

 

3) navigation graph 추가

res 폴더에 navigation 패키지를 생성하고 navigation_graph.xml 파일을 생성한다

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/navigation_graph"
    app:startDestination="@id/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="com.example.movepage.MainFragment"
        android:label="MainFragment">
    </fragment>


</navigation>

 

startDestination : 제일 먼저 실행 할 fragment 를 설정한다
fragment : 기본적으로 보여줄 frament 를 넣어준다. 해당공간에 앞으로 이동될 fragment 들이 위치할것

 

2. Fragment 이동

상단의 title bar 에서 뒤로가기, fragment 에서 다른 fragment 로 이동하기를 해보자

 

1) title bar 생성

적당히 타이틀, 뒤로가기버튼을 넣어줬다

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

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@color/title_background"
        tools:context=".SecondFragment">

        <ImageButton
            android:id="@+id/btn_back"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_marginStart="20dp"
            android:background="@drawable/btn_back"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/txt_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="title"
            android:textSize="18dp"
            android:textColor="@color/black"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

 

 

2) Second Fragment , Thid Fragment 추가

첫번째로 이동할 페이지를 만들어준다

 

title 추가 해주고, 다른 fragment 로 이동할 버튼하나 넣어준다

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/teal_200"
        tools:context=".SecondFragment">

        <include
            android:id="@+id/inc_titlebar"
            android:layout_height="50dp"
            android:layout_width="match_parent"
            layout="@layout/inc_title_bar" />

        <Button
            android:id="@+id/btn_move"
            android:layout_width="150dp"
            android:layout_height="wrap_content"
            android:text="go third"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

 

데이터바인딩 로직추가

class SecondFragment : Fragment() {

    private var _binding : FragmentSecondBinding? = null
    private val binding: FragmentSecondBinding
        get() = _binding!!

    companion object {
        fun newInstance() : SecondFragment {
            val fragment = SecondFragment()
            val args = Bundle()
            fragment.arguments = args
            return fragment
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = DataBindingUtil.inflate(layoutInflater, R.layout.fragment_second,container,false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.apply {
            incTitlebar.txtTitle.text = "Second"
            incTitlebar.btnBack.setOnClickListener {
                
            }
            
            btnMove.setOnClickListener {
                
            }
        }
    }
}

 

이런식으로 Third Fragment 도 생성해준다.

 

그리고 타이틀의 뒤로가기 버튼과 화면 페이지이동 버튼에 onclick 이벤트를 줄건데

다수의 fragment 에서 공통으로 fragment 이동로직을 사용해야하므로, 해당로직은 MainActivity 에 넣기로 하자

 

4) Main Activity 에 공통 fragment 이동 로직 추가

MainActivity 위에 다양한 fragment 가 올라갈것이고,

모든 fragment 들이 공통적으로 페이지이동 로직이 필요하기때문에, MainActivity 에 fragment 이동 공통로직을 작성한다

 

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

	//추가
	fun openFragment(fragment: Fragment, tag: String) {
        val transaction: FragmentTransaction = supportFragmentManager.beginTransaction()
        transaction.replace(R.id.nav_host_fragment, fragment)
        transaction.addToBackStack(tag)
        transaction.commit()
    }

}

 

 

해당 로직을 MainFragment, SecondFragment 에서 사용하자

 

MainFragment

btnMoveSecond.setOnClickListener {
    val act = activity as MainActivity
    act.openFragment(ThirdFragment() , "2")
}
btnMoveThird.setOnClickListener {
    val act = activity as MainActivity
    act.openFragment(ThirdFragment() , "3")
}

 

내가 변경하고싶은 fragment 와 해당 fragment 임을 특정할수있는 TAG를 끼워준다

 

Second Fragment

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    //추가
    binding.apply {
        incTitlebar.txtTitle.text = "Second"

        incTitlebar.btnBack.setOnClickListener {
        }

        btnMove.setOnClickListener {
            val act = activity as MainActivity
            act.openFragment(ThirdFragment(), "3")
        }
    }
}

 

title 의 text 를 주고 onclick 이벤트를 작성했다.

 

이렇게 MainFragment 의 로직을 통해서 fragment 이동이 가능하다

 

3. 뒤로가기

title bar 에서 공통으로 사용될 뒤로가기 로직을 MainActivity 에 작성한다

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

    fun navigateUp(tag: String) {
        Log.d("TLOG", "btn back 2.................")
        supportFragmentManager.popBackStack(tag, FragmentManager.POP_BACK_STACK_INCLUSIVE)
    }
    
    ...

 

supportFargmentManager.popbackStack 을 이용하는데, 이때 파라미터로 tag 값이 필요하다

그리고 POP_BACK_STACK_INCLUSIVE 옵션은 백스택에서 해당 Fragment와 위의 Fragment 들을 모두 제거한다

 

이것을 활용하여 뒤로가기를 실행해보자

 

Second Fragment와 Third fragment 에서 titlebar의 click이벤트를 설정한다

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    binding.apply {

        //추가
        incTitlebar.txtTitle.text = "Second"
        incTitlebar.btnBack.setOnClickListener {
            val act = activity as MainActivity
            act.navigateUp("2")
        }

        btnMove.setOnClickListener {
            val act = activity as MainActivity
            act.openFragment(ThirdFragment(), "3")
        }
    }
}

 

이렇게하면 뒤로가기 완료

 

 

4. 홈으로 이동하기

모든 back Stack 을 전부 지우고 MainFragment 로 이동하는 로직이다

 

이것도 동일하게 MainActivity 에 로직을 추가한다

fun clearBackStack() {
    val fragmentManager: FragmentManager = supportFragmentManager
    fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}

 

기존에는 TAG 값을 넣고 해당 TAG 를 없앴지만, null 을 넣으면 모든 stack 을 없애준다

 

5. 데이터 전달하기

특정 fragment 에서 다른 fragment 로 데이터 전달하는 로직을 만들어보자

 

1) MainFragment 에 EditText 추가

...
        <include
            android:id="@+id/inc_titlebar"
            android:layout_height="50dp"
            android:layout_width="match_parent"
            layout="@layout/inc_title_bar" />

		//추가
        <EditText
            android:id="@+id/et_message"
            android:layout_width="144dp"
            android:layout_height="35dp"
            android:background="#ffdddd"
            app:layout_constraintBottom_toTopOf="@id/btn_move_second"
            app:layout_constraintEnd_toEndOf="@id/btn_move_second"
            app:layout_constraintStart_toStartOf="@id/btn_move_second" />

        <Button
            android:id="@+id/btn_move_second"
            android:layout_width="150dp"
            android:layout_height="wrap_content"
            android:text="go second"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
....

 

그리고 onViewCreated 에서 secondFragment 로 이동할대 해당 string 값을 전달해주자

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    binding.apply {

        incTitlebar.btnBack.visibility = View.GONE
        incTitlebar.txtTitle.text = "MAIN"

        btnMoveSecond.setOnClickListener {
            val paramData = if(etMessage.text.isNotEmpty()) etMessage.text.toString() else "empty msg"
            val bundle = Bundle()
            bundle.putString("param", paramData)
            val act = activity as MainActivity
            act.openFragment(SecondFragment.newInstance().apply {
                arguments = bundle
            }, "2")
        }
...

 

editText 값이 empty 라면 empty msg 라는 string을 세팅하고 bundle을 만들어서 string 값울 붙인다.

그리고 SecondFragment 에서 newInstance 할때 해당 bundle을 가져갈수있도록 argments에 넣어준다

 

2) Second Fragment 에서 받기

class SecondFragment : Fragment() {

    private var _binding : FragmentSecondBinding? = null
    private val binding: FragmentSecondBinding
        get() = _binding!!

    var strParam = ""

    companion object {
        fun newInstance() : SecondFragment {
            val fragment = SecondFragment()
            val args = Bundle()
            fragment.arguments = args
            return fragment
        }
    }

    fun setJsonParam() {
        val bundle = arguments
        bundle?.let {
            strParam = it.getString("param", "").toString()
            Log.d("TLOG - SecondFragment", "받은값 : $strParam")
        }
    }

...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
		
        //추가
        setJsonParam()
        
        binding.apply {
...

 

newInstance 에서 MainFragment 에서 보낸 Bundle 을 argument 에 저장하고,

해당 arguments 를 풀어서 strParam에 저장했다

 

 

 

전체 소스코드는 아래 깃주의 "movePage" 프로젝트에 위치해있다

https://github.com/Daseul727/Mobile-Skill-Up

728x90
반응형

<data> 태그를 사용하면 activity 로직부분에서 구현하지않더라도, xml 쪽에서 바로 데이터바인딩이 가능하다

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".MainActivity">

    <data>
        <import type="android.view.View"/>
        
        <variable
            name="isDebug"
            type="Boolean" />
    </data>
    
</layout>

 

<data> 태그 내부에 <import> 태그를 우선 살펴보자

 

import 태그란 ?

특정 클래스나 패키지를 임포트 할 수 있는데,

현재 View 를 임포트했기때문에 android:visibility, onClick 과 같은 속성 사용이 가능하다

 

//import 안했을 경우

android:visibility="@{isDebug ? View.VISIBLE : View.GONE}"

//import 했을 경우
android:visibility="@{isDebug ? VISIBLE : GONE}"

 

이런 차이로 보면 된다

 

variable 태그란 ?

단순 변수이다. 로직에서 값을 세팅하면 xml에서 사용이 가능하다

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    _binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(_binding.root)

    initView()
}

private fun initView() {
    _binding.isDebug = true
}

 

바인딩을 사용하는 경우의 예시이며, 데이터바인딩으로 바로 사용가능하다.

 

만약 type 으로 model 을 넣으면 모델 째로 사용이 가능하다.

...
    <data>
        <import type="android.view.View"/>

        <variable
            name="isDebug"
            type="Boolean" />

        <variable
            name="item"
            type="com.example.network_sample.model.DogResponse" />

    </data>

...

        <TextView
            android:text="@{item.status}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:ignore="MissingConstraints" />

 

 

github주소

 - showDog 프로젝트

https://github.com/Daseul727/Mobile-Skill-Up

728x90
반응형

 

이전 포스팅에서 api 통신을 하였는데, 결과값으로 이미지를 받을것이다.

해당 이미지를 뿌려주기 위한 activity를 꾸며보자

 

1. activity_main 수정

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

        <ImageView
            android:id="@+id/imgDog"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:contentDescription="@string/dog_image"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_marginTop="30dp"
            android:layout_marginBottom="100dp"
            android:layout_marginStart="32dp"
            android:layout_marginEnd="32dp"/>

        <ProgressBar
            android:id="@+id/pbDog"
            android:layout_width="100dp"
            android:layout_height="100dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>

        <ImageView
            android:id="@+id/imgRefresh"
            android:layout_width="50dp"
            android:layout_height="50dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintEnd_toEndOf="parent"
            android:src="@drawable/ic_refresh"
            android:contentDescription="@string/refresh"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/imgDog"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 

이미지 소스는 적당한것을 붙여넣거나, 아래 깃주소에 해당 프로젝트 올려놨으니 참고!

https://github.com/Daseul727/mobile_sample/tree/main/network_mobile

 

mobile_sample/network_mobile at main · Daseul727/mobile_sample

코틀린 안드로이드 basic. Contribute to Daseul727/mobile_sample development by creating an account on GitHub.

github.com

 

2. data binding 설정

data binding이랑 이미지 로드 라이브러리를 사용하기 위해 build.gradle 을 수정해야한다

android {
    namespace 'com.example.network_sample'
    compileSdk 34

...
    //추가
    buildFeatures {
        viewBinding = true
        dataBinding true
    }
}

dependencies {

    ...
    //추가
    /*image load*/
    implementation "io.coil-kt:coil:$coilVersion"
}

 

coilVersion 은 1.2.2 를 사용했다

 

 

3. MainActivity 수정

 

1) view data binding

우선 activity_main과 연결하기위해 바인딩을 시켜준다

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var mainViewModel: MainViewModel
    
    //추가
    private lateinit var _binding: ActivityMainBinding 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        //추가
        _binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(_binding.root)
    }

 

위에서 build.gradle에 데이터바인딩 사용설정이 성공적으로 완료되었다면 ActivityMainBinding 이 자동완성으로 import 될것이다.

 

그리고 onCreate 에서 한번더 _binding을 inflate 시켜주면 초기화 완료

 

2) api 연결

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var mainViewModel: MainViewModel
    private lateinit var _binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        _binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(_binding.root)
    }

    //추가
    private fun fetchResponse() {
        mainViewModel.fetchDogResponse()
        _binding.pbDog.visibility = View.VISIBLE
    }

 

이전 포스팅에서 model에 만들었던 함수를 호출하자

 

3) observer 추가

이제 실제로 fetchresponse 를 호출해볼건데, 해당함수 호출되어 model 의 "response"에 값이 담기면,

담기는것을 observe 하고있다가 화면에 데이터를 뿌려주는 함수를 넣어준다.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var mainViewModel: MainViewModel
    private lateinit var _binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        _binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(_binding.root)
        
        //추가
        fetchData()
        //추가
        _binding.imgRefresh.setOnClickListener {
            fetchResponse()
        }
    }

    //추가
    private fun fetchData() {
        fetchResponse()

        mainViewModel.response.observe(this) { res->
            when (res) {
                is NetworkResult.Success -> {
                    res.data?.let {
                        _binding.imgDog.load(
                            res.data.message
                        ) {
                            transformations(RoundedCornersTransformation(16f))
                        }
                    }
                    _binding.pbDog.visibility = View.GONE
                }
                is NetworkResult.Error -> {
                    _binding.pbDog.visibility = View.GONE
                    Toast.makeText(
                        this,
                        res.message,
                        Toast.LENGTH_SHORT
                    ).show()
                }
                is NetworkResult.Loading -> {
                    _binding.pbDog.visibility = View.VISIBLE
                }
            }
        }
    }

    private fun fetchResponse() {
        mainViewModel.fetchDogResponse()
        _binding.pbDog.visibility = View.VISIBLE
    }
}

 

이렇게하면 완성!

 

 

github주소

 - showDog 프로젝트

https://github.com/Daseul727/Mobile-Skill-Up

728x90
반응형

 

1. Network 통신설정

Network 통신하여 강아지 이미지를 받아보겠음!

 

1) Manifest network 권한추가

http 통신을 하기 위하여 Retrofit 이라는 라이브러리를 사용할것이다.

해당 라이브러리를 사용하기에 앞서 아래의 퍼미션을 추가하자

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

//추가
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    
    <application
        android:name=".NetworkApplication"
 ...

 

2) build.gradle 설정 추가

Retrofit 관련 설정을 build.gradle(app)에 추가한다

/* Retrofit */
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

/*lifecycle*/
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"

 

3) 통신관련 정보 static 보관

통신과 관련된 URL 이나 IP정보는 변하지않기때문에 한곳에서 관리하는것이 좋다.

 

utils 폴더 하나만들어서, Constants 파일을 만들어주자

package com.maxi.dogapi.utils

class Constants {

    companion object {

        const val BASE_URL = "https://dog.ceo/"
        const val RANDOM_URL = "api/breeds/image/random"
    }
}

 

companion object를 쓰면 Constants 인스턴스를 생성하지않고도 BASE_URL 에 접근할수있다

 

2. 통신연결

위에서 설정한 RANDOM_URL과 get방식 통신을 할건데, 결과값을 받기위한 Response 클래스를 만들어주자

 

1) Response class 생성

package com.example.network_sample.model

import com.google.gson.annotations.SerializedName

data class DogResponse(
    @SerializedName("message")
    val message: String,
    @SerializedName("status")
    val status: String
)

 

data class 를 사용하면 클래스의 기본적인 메소드들(toString(), equals(), hashCode(), copy())을 자동으로 생성해준다.

 

2) API Service 생성

package com.example.network_sample.data.remote

import com.example.network_sample.model.DogResponse
import com.example.network_sample.utils.Constants
import retrofit2.Response
import retrofit2.http.GET

interface APIService {

    @GET(Constants.RANDOM_URL)
    suspend fun getDog(): Response<DogResponse>
}

 

만약 @GET import 안되면 retrofit build.gradle 설정을 확인해보자.

위에서 RANDOM_URL 을 만든이유는, 저런식으로 상수화 시켜서 사용하기 위함이었다.

 

실제로 통신할때는 해당 api를 바로 DI 시켜서 쓰지않는다. 별도 ServiceImpl 같이 하나 만들어서 주입해주자

 

3) RemoteDataSource 생성

class RemoteDataSource @Inject constructor(private val dogService: APIService) {

    suspend fun getDog() =
        dogService.getDog()

}

 

@Inject?

이전 포스팅에서는 @Inject 로 DI 를 할수있다고 했는데 @Inject 방법에는 두가지가 있다.

 

방법1. 필드주입

이전 포스팅과 같이 class 내부에 @Inject 하는방법이며 코드가 간결하게보일 수 있지만 외부 의존성에 대한 테스트가 어려울수 있다고 한다

 

방법2. 생성자주입

클래스 의존성을 외부로부터 완벽히 분리하고 의존성주입을 명시적으로 만든다.

왠만하면 생상자주입이 권장되는편인가보다.

 

constructor ?

클래스 생성자 선언하는 방법중에 하나인데, 파라미터 선언없이도 바로 생성이 가능하게 만들어준다

// 클래스의 주 생성자를 선언하는 방법
class MyClass constructor(param1: Int, param2: String) {
    // 클래스 본문
}

// 클래스의 주 생성자를 선언하지 않고 속성과 함께 선언하는 방법
class MyClass(param1: Int, param2: String) {
    // 주 생성자 파라미터로 선언된 속성들
    val property1: Int = param1
    val property2: String = param2
}

 

suspend?


코틀린에서 코루틴(coroutine)을 사용하는 함수를 정의할 때 사용되는 키워드
네트워크 요청이나 파일 I/O 등의 비동기적인 작업을 수행할 수 있고 위 코드를 보면 getDog() 함수는 일시 중단되어 네트워크 응답을 기다린 후 결과를 반환한다.

코루틴은 Android에서 비동기적인 작업을 수행할 때 매우 유용하며, UI 스레드를 차단하지 않고 작업을 수행할 수 있도록 해준다고 한다.

 

-> 결과적으론 RemoteDataSource 에서 API 를 비동기연결하였다.

 

 

3. Model 과 API 연결

 

이제 실제로 MainViewModel 에서 직접 연결할 repository 를 만들어보자.

 

model  ---- RemoteDataSource 를 연결하는 공간이라고 보면 된다.

 

model에게 api 통신결과를 보내줄건데, success/fail 에 따라 어떻게 보내줄건지도 정의해야할것이다.

repository 를 생성하기에 앞서, BaseApiResponse를 만들어보자

 

1) BaseApiResponse 생성

package com.maxi.dogapi.model

import com.maxi.dogapi.utils.NetworkResult
import retrofit2.Response

abstract class BaseApiResponse {

    suspend fun <T> safeApiCall(apiCall: suspend () -> Response<T>): NetworkResult<T> {
        try {
            val response = apiCall()
            if (response.isSuccessful) {
                val body = response.body()
                body?.let {
                    return NetworkResult.Success(body)
                }
            }
            return error("${response.code()} ${response.message()}")
        } catch (e: Exception) {
            return error(e.message ?: e.toString())
        }
    }

    private fun <T> error(errorMessage: String): NetworkResult<T> =
        NetworkResult.Error("Api call failed $errorMessage")

}

 

이런식으로 api 통신결과를 어떻게 처리할지 간단히 정의해하였고 result 를 보내기위해 NetworkResult 도 아래와 같이 만들어주었다

package com.example.network_sample.utils

sealed class NetworkResult<T>(
    val data: T? = null,
    val message: String? = null
) {

    class Success<T>(data: T) : NetworkResult<T>(data)

    class Error<T>(message: String, data: T? = null) : NetworkResult<T>(data, message)

    class Loading<T> : NetworkResult<T>()

}

 

2) Repository 생성

위에서 만든 BaseApiResponse를 사용하여 Repository를 만들어보자

 

package com.example.network_sample.data

import com.example.network_sample.data.remote.RemoteDataSource
import com.example.network_sample.model.BaseApiResponse
import com.example.network_sample.model.DogResponse
import com.example.network_sample.utils.NetworkResult
import dagger.hilt.android.scopes.ActivityRetainedScoped
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import javax.inject.Inject

@ActivityRetainedScoped
class Repository @Inject constructor(
    private val remoteDataSource: RemoteDataSource
): BaseApiResponse() {

    suspend fun getDog(): Flow<NetworkResult<DogResponse>> {
        return flow { 
            emit(safeApiCall { remoteDataSource.getDog() })
        }.flowOn(Dispatchers.IO)
    }

}

 

 

4) Model - Repository 연결

 

위에서 만든 repository 를 model에서 불러보자

package com.example.network_sample.model

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.network_sample.data.Repository
import com.example.network_sample.utils.NetworkResult
import kotlinx.coroutines.launch
import javax.inject.Inject

class MainViewModel @Inject constructor(private val repository: Repository) : ViewModel() {

    private val _response : MutableLiveData<NetworkResult<DogResponse>> = MutableLiveData()
    val response : LiveData<NetworkResult<DogResponse>> = _response

    fun fetchDogResponse() = viewModelScope.launch {
        repository.getDog().collect { values ->
            _response.value = values
        }
    }
}

 

만약 viewModelScope가 안써지진다면, 상단 build.gradle 설정에 lifecycle이 제대로 설정되어잇는지 확인해보자

 

이렇게하면 fetchDogResponse 를 호출하면 결과값이 잘 호출될것이다

 

 

github주소

 - showDog 프로젝트

https://github.com/Daseul727/Mobile-Skill-Up

728x90
반응형

Hilt 란?

의존성주입(DI)를 위한 라이브러리이다.

 

나는 자바개발자로 시작했기 때문에 Spring boot 를 예로 들겠다.

spring boot 에서 @SpringBootApplication 로, 스프링부트 시작점이라는것을 알수있고

의존성 주입, 다양한 어노테이션을 사용할수있다.

 

1. 새 프로젝트 생성

 

안드로이드 스튜디오에서 New project 해주었다

 

 

안드로이드에서도 스프링처럼 어노테이션을 활용할수있는데, 그러려면 hilt 설정을 build.gradle에 추가해줘야한다.

 

2. Hilt 추가하기

// gradle(project)
buildscript {
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.50'
    }
}

// gradle(app)
plugins {
    id 'dagger.hilt.android.plugin'
    id 'kotlin-kapt'
}
dependencies {
    implementation "com.google.dagger:hilt-android:2.50"
    kapt "com.google.dagger:hilt-android-compiler:2.50"
}

 

혹시 Unsupported metadata version. Check that your Kotlin version is >= 1.0 에러가 발생한다면, 

 

https://dagger.dev/hilt/gradle-setup

 

Gradle Build Setup

Hilt dependencies To use Hilt, add the following build dependencies to the Android Gradle module’s build.gradle file: dependencies { implementation 'com.google.dagger:hilt-android:2.51' annotationProcessor 'com.google.dagger:hilt-compiler:2.51' // For in

dagger.dev

 

이곳에서 최신버전을 확인해서 적용시켜주자

 

3. Application 클래스 추가

MainActivity가 위치한 폴더에 자기가 원하는 이름의 코틀린 클래스를 추가해준다.

package com.example.network_sample

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class NetworkApplication : Application(){
}

 

 

@HiltAndroidApp 어노테이션을 사용함으로써 의존성 주입이 가능해졌다.

지금은 아무 내용도 입력하지않을것이지만, 나중에는 전역변수 등 공통으로 들어가는 로직을 추가할 수 있다.

 

Manifest 쪽에도 추가해주자

...

<application
    android:name=".NetworkApplication"
    android:allowBackup="true"
    ....

 

4. Activity 에 의존성 추가

mainViewModel 이라는 model을 만들어서 MainActivity에 의존성주입해보자

package com.example.network_sample.model

import androidx.lifecycle.ViewModel

class MainViewModel : ViewModel() {

}

 

해당 클래스를 의존성주입하려면 2가지 방법이 있다.

 

방법 1. @Inject 사용

직접 구현한 클래스에 사용가능하다

 

방법 2. @Module 사용

인터페이스, 추상 클래스, 직접 구현불가한 클래스 에 사용 가능하다.

위 클래스들은 @Inject 사용이 불가하므로 @Module 사용한다.

 

나는 직접구현한 클래스이므로, @Inject를 사용하겠다.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var mainViewModel: MainViewModel
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

 

DI를 성공적으로 진행하기위해서, MainActivity에 @AndroidEntryPoint 어노테이션을 붙여준다.

 

@AndroidEntryPoint란?

Hilt가 애플리케이션의 컴포넌트를 생성/주입할수있게된다.

 

안쓰면? 

해당 클래스를 주입 가능한 대상으로 인식하지 않아서 필드에 의존성을 주입할 수 없다.

 

아래 유형에서 @AndroidEntryPoint  사용가능하다

(Application, Activity, Fragment, service, View, BroadcastReceiver, ViewModel)

 

 

 

github주소

 - showDog 프로젝트

https://github.com/Daseul727/Mobile-Skill-Up

728x90
반응형

 

햄버거 버튼 동작이벤트를 넣어보자.

 

1. GNB UI 생성

MainActivity 로 이어질 Home

별도 Activity 로 이어질 버튼을 넣어보겠다

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <LinearLayout
        android:id="@+id/ll_menu"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginRight="0dp"
        android:layout_marginLeft="0dp"
        android:orientation="vertical"
        android:background="@color/white">

        <LinearLayout
            android:id="@+id/ll_item_1"
            android:layout_width="match_parent"
            android:layout_height="53dp"
            android:gravity="center_vertical"
            android:paddingLeft="40dp"
            android:paddingRight="20dp"
            android:layout_marginBottom="4dp"
            android:background="@color/home_menu_bg_light_gray"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/tv_item_1"
                android:layout_width="wrap_content"
                android:layout_height="53dp"
                android:layout_gravity="center_vertical"
                android:paddingLeft="0dp"
                android:gravity="center_vertical"
                android:text="Home"
                android:textSize="@dimen/sp_17"
                android:maxLines="1"
                android:textColor="@color/orange"
                android:textStyle="bold" />
            <View
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1">
            </View>
            <ImageView
                android:id="@+id/iv_orange_arrow1"
                android:layout_width="7dp"
                android:layout_height="12dp"
                android:layout_marginRight="0dp"
                android:background="@drawable/btn_detail_org"/>
        </LinearLayout>

        <LinearLayout
            android:id="@+id/ll_item_2"
            android:layout_width="match_parent"
            android:layout_height="53dp"
            android:gravity="center_vertical"
            android:paddingLeft="40dp"
            android:paddingRight="20dp"
            android:layout_marginBottom="4dp"
            android:background="@color/home_menu_bg_light_gray"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/tv_item_2"
                android:layout_width="wrap_content"
                android:layout_height="53dp"
                android:layout_gravity="center_vertical"
                android:paddingLeft="0dp"
                android:gravity="center_vertical"
                android:text="기본 Recycler View"
                android:textSize="@dimen/sp_17"
                android:maxLines="1"
                android:textColor="@color/orange"
                android:textStyle="bold" />
            <View
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1">
            </View>
            <ImageView
                android:id="@+id/iv_orange_arrow2"
                android:layout_width="7dp"
                android:layout_height="12dp"
                android:layout_marginRight="0dp"
                android:background="@drawable/btn_detail_org"/>
        </LinearLayout>

    </LinearLayout>
</layout>

 

나는 이런식으로 추가해봤다.

메뉴가 몇개안되기때문에 LinearLayout을 사용했음!!

 

2. MainActivity 에 GNB 영역 추가

다른 액티비티에서는 사용하지않고 Main 에 들어왔을때만 보이면 되기때문에 MainActivity 에만 추가하자

<com.example.basic_mobile.ui.TitleBar
        android:id="@+id/inc_titlebar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/title_height"
        app:layout_constraintTop_toTopOf="parent"/>

////추가////
<com.google.android.material.navigation.NavigationView
    android:id="@+id/nav_view"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:orientation="vertical"
    android:visibility="gone"
    tools:ignore="MissingConstraints">

    <com.example.basic_mobile.ui.CustomDrawerMenu
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</com.google.android.material.navigation.NavigationView>

 

 

 

그런데!! 이렇게하면 햄버거버튼 위에 위치하며 버튼이랑 겹쳐진다

각 요소들을 나열해놓기만 하고 줄을 제대로 세우지 않았기때문에 이런식으로 먹혀버린다...

 

 

LinearLayout 으로 서열을 제대로 알려주자

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <com.example.basic_mobile.ui.TitleBar
            android:id="@+id/inc_titlebar"
            android:layout_width="match_parent"
            android:layout_height="@dimen/title_height" />

        <com.google.android.material.navigation.NavigationView
            android:id="@+id/nav_view"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:orientation="vertical"
            android:visibility="gone">

            <com.example.basic_mobile.ui.CustomDrawerMenu
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

        </com.google.android.material.navigation.NavigationView>
        <Button
            android:id="@+id/btn_recycler"
            android:text="기본 Recycler View"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <Button
            android:id="@+id/btn_epoxy"
            android:text="epoxy View"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@+id/btn_recycler"/>
    </LinearLayout>


</androidx.constraintlayout.widget.ConstraintLayout>

 

 

이런식으로 ConstraintLayout -> LinearLayout -> 요소 배치 의 방식으로 설정하면 된다.

 

자 이제 보여줄 화면을 세팅해놨다면, 햄버거버튼 클릭시에만 NavigationView 가 visible 되도록 해보자

 

3. GNB 클릭 이벤트 설정

MainActivity 에서 햄버거버튼을 클릭했을때,

메뉴영역이 노출되어있으면 visibility = gone, 미노출되어있으면 visible 상태로 만들어보자.

 

하지만 햄버거버튼, 뒤로가기, 타이틀 로고 이미지클릭시 실행되는 동작들이 Activity 별로 달라야할때가 있다.

즉 include 된 페이지의 이벤트를 부모 페이지에게 알려줘야할때 쓰는 방법을 사용해보겠다

 

1) TitleBar 상수 추가

Title bar 에서 어떤 동작을 수행했는지 알려주기 위해서 상수를 추가했다

class TitleBar : FrameLayout {
    companion object {
        const val BUTTON_NAME_BACK = 1 //뒤로가기
        const val BUTTON_NAME_HOME_MENU = 2 //햄버거버튼
        const val BUTTON_NAME_HOME = 3 //타이틀
    }
    ...
}

 

뒤로가기, 햄버거버튼, 타이틀을 클릭했을때 각 상수값을 부모 페이지로 보내도록 하겠다

class TitleBar : FrameLayout {
    object {
        const val BUTTON_NAME_BACK = 1
        const val BUTTON_NAME_HOME_MENU = 2
        const val BUTTON_NAME_HOME = 3
        const val BUTTON_NAME_CLOSE = 4
    }
...
    private fun initView() {

        //햄버거버튼 클릭 이벤트
        binding?.ivMenu?.setOnClickListener() {
            ///이곳에서 상수값을 보내고 싶음
        }
    }
...
}

 

자 어떻게 보내야할까?

 

내가 택한 방법으로는 DataChangeListener를 이용하는것이다.

 

리스너를 하나 만들고 부모에서 해당리스너를 오버라이딩 시켜서 사용하는 방법이다. 차근차근 해보자

 

2) DataChangeListener 추가

TitleBar 하단에 interface로 리스너를 하나 만들어준다.

그리고 그 안에 메소드를 하나 추가해서 파라미터로 상수값을 제공받는다

class TitleBar : FrameLayout {
    object {
        const val BUTTON_NAME_BACK = 1
        const val BUTTON_NAME_HOME_MENU = 2
        const val BUTTON_NAME_HOME = 3
        const val BUTTON_NAME_CLOSE = 4
    }
...

    private var binding: IncTitlebarBinding? = null
    private var dataChangeListener: DataChangeListener? = null
    
    private fun initView() {

        //햄버거버튼 클릭 이벤트
        binding?.ivMenu?.setOnClickListener() {
            if(dataChangeListener != null)
                this.dataChangeListener?.onChanged(BUTTON_NAME_HOME_MENU,"")
        }
    }
...

    interface DataChangeListener {
        fun onChanged(index: Int,  value : String);
    }

}

 

햄버거버튼을 클릭했을 때, 해당 리스너의 onChanged 메소드로 상수값을 전달한다

그렇다면 이것을 부모가 쓰게끔 컨트롤가능한 메소드를 하나 추가해준다

 

3) 부모페이지 전용 메소드 추가

class TitleBar : FrameLayout {
    object {
        const val BUTTON_NAME_BACK = 1
        const val BUTTON_NAME_HOME_MENU = 2
        const val BUTTON_NAME_HOME = 3
        const val BUTTON_NAME_CLOSE = 4
    }
...

    private var binding: IncTitlebarBinding? = null
    private var dataChangeListener: DataChangeListener? = null
    
    private fun initView() {

        //햄버거버튼 클릭 이벤트
        binding?.ivMenu?.setOnClickListener() {
            if(dataChangeListener != null)
                this.dataChangeListener?.onChanged(BUTTON_NAME_HOME_MENU,"")
        }
    }
...

	//부모 사용 전용 메소드
    fun setOnClickBackBtnListener(listener: (Int, String) -> Unit) {
        this.dataChangeListener = object : DataChangeListener {
            override fun onChanged(index: Int, value: String) {
                listener(index, value)
            }
        }
    }

    interface DataChangeListener {
        fun onChanged(index: Int,  value : String);
    }

}

 

이렇게 메소드를 만들어서 뚫어줘야 부모에서 사욯할 수 있다.

 

전체 코드는 이렇다

class TitleBar : FrameLayout {
    companion object {
        const val BUTTON_NAME_BACK = 1
        const val BUTTON_NAME_HOME_RIGHT_SIDE = 2
        const val BUTTON_NAME_HOME = 3
        const val BUTTON_NAME_CLOSE = 4
    }

    constructor(context: Context) : super(context) {
        inflateView()
        initView()
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        inflateView()
        initView()
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        inflateView()
        initView()
    }

    private var binding: IncTitlebarBinding? = null
    private  var dataChangeListener: DataChangeListener? = null

    private fun inflateView() {
        binding = IncTitlebarBinding.inflate(LayoutInflater.from(context))
        addView(binding?.root)
    }

    private fun initView() {

        //클릭이벤트
        binding?.ivMenu?.setOnClickListener() {
            if(dataChangeListener != null)
                this.dataChangeListener?.onChanged(BUTTON_NAME_HOME_RIGHT_SIDE,"")
        }
    }

    /**
     * set title (TEXT)
     */
    fun setTitle(title:String) {
        if (title.isNullOrEmpty()) {
            binding?.tvTitle?.hide()
        } else {
            binding?.tvTitle?.show()
            binding?.tvTitle?.text = title
        }
    }

    fun showMenuIcon(b: Boolean) {
        if (b) {
            binding?.ivMenu?.show()
        } else {
            binding?.ivMenu?.hide()
        }
    }

    fun setOnClickBackBtnListener(listener: (Int, String) -> Unit) {
        this.dataChangeListener = object : DataChangeListener {
            override fun onChanged(index: Int, value: String) {
                listener(index, value)
            }
        }
    }

    interface DataChangeListener {
        fun onChanged(index: Int,  value : String);
    }

}

 

 

4.  Main Activity 에서 리스너 사용

setTitleBar 부분에서 아래와같이 해당 메소드를 이용할 수 있다

class MainActivity : AppCompatActivity() {

    private var binding: ActivityMainBinding? = null
    lateinit var drawerLayout: DrawerLayout

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding!!.root)
        setTitleBar()
    }

    private fun setTitleBar () {
        binding?.incTitlebar?.setTitle("MAIN")
        binding?.incTitlebar?.showMenuIcon(true)
        binding?.incTitlebar?.setOnClickBackBtnListener { i, s ->
            when (i) {
                TitleBar.BUTTON_NAME_HOME -> {} //로고 클릭 event
                TitleBar.BUTTON_NAME_BACK -> {} //뒤로가기 event
                TitleBar.BUTTON_NAME_HOME_MENU -> { //햄버거버튼 event
                    if (binding?.navView?.isVisible == true) {
                        binding?.navView?.hide()
                    } else {
                        binding?.navView?.show()
                    }

                }
            }
        }
    }

}

 

뭐 모든 페이지가 같은 동작을 수행한다면 굳이 이럴필요는 없지만 

아닌경우가 많기때문에.. 해당방법을 사용했다.

 

중심적으로 봐야할점은 부모로 이벤트를 보내는 방법!! 나중에 fragment 에서 리스트클릭하거나 다양한 이벤트를 보낼때 이러한 방식을 사용하게된다.

 

완성본!

 

 

 

 

 

728x90
반응형

1. 타이틀 UI 생성

 

보편적인 안드로이드 타이틀 모양을 제작해보자

위치는 layout 폴더 안에 inc_titlebar 라는 이름의 파일을 만들것이다

inc 의 의미는 include 를 할 파일이라는 의미인데, 액티비티에 타이틀 연결할때 예제를 봐보자

 

타이틀 디자인은 왼쪽에 햄버거 버튼이 있고, 가운데에 타이틀 텍스트가 오도록 한다

 

1) 타이틀 xml 작성

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/layout_title"
            android:layout_width="match_parent"
            android:layout_height="@dimen/title_height"
            android:layout_marginLeft="12dp"
            android:layout_marginRight="12dp">

            <!-- back button -->
            <ImageView
                android:id="@+id/iv_back"
                style="@style/AppTheme.Button.SmallIcon.titleBar"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_gravity="center_vertical|left"
                android:visibility="gone"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/btn_back" />

            <ImageView
                android:id="@+id/iv_menu"
                style="@style/AppTheme.Button.SmallIcon.menu"
                android:layout_width="@dimen/title_height"
                android:layout_height="match_parent"
                android:layout_gravity="center_vertical"
                android:visibility="gone"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/ic_menu" />

            <!-- title -->
            <TextView
                android:id="@+id/tv_title"
                style="@style/AppTheme.Text.Title"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginLeft="60dp"
                android:layout_marginRight="60dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:text="title" />

            <!-- border line -->
            <View
                android:id="@+id/vw_line"
                android:layout_width="match_parent"
                android:layout_height="8dp"
                android:layout_gravity="bottom"
                android:background="@drawable/gradient_stroke_gray"
                app:layout_constraintBottom_toBottomOf="parent" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </LinearLayout>

</layout>

 

햄버거버튼부분은, 뒤로가기 이미지가 올때도 있어야 하므로 ImageView 두개를 넣어뒀다.

@Style, @drawable, @dimen 부분은 사용성을 좋게하기위해 했지만 안해도되고, 직접 속성을 설정해줘도 되므로 생략!

 

자세한 코드는 아래 깃주소 참고

https://github.com/Daseul727/mobile_sample.git

 

2) 타이틀 소스코드 작성

TitleBar.kt 파일을 만들어주자

 

아래 소스코드 중 FrameLayout 을 import 하면 기본 constructor 세개를 넣어야지 오류가 발생하지않는다.

class TitleBar : FrameLayout {

    constructor(context: Context) : super(context) {
        inflateView()
        initView()
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        inflateView()
        initView()
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        inflateView()
        initView()
    }

    private var binding: IncTitlebarBinding? = null

    private fun inflateView() {
        binding = IncTitlebarBinding.inflate(LayoutInflater.from(context))
        addView(binding?.root)
    }

    private fun initView() {

        //클릭이벤트

    }

    /**
     * set title (TEXT)
     */
    fun setTitle(title:String) {
        if (title.isNullOrEmpty()) {
            binding?.tvTitle?.hide()
        } else {
            binding?.tvTitle?.show()
            binding?.tvTitle?.text = title
        }
    }

    fun showMenuIcon(b: Boolean) {
        if (b) {
            binding?.ivMenu?.show()
        } else {
            binding?.ivMenu?.hide()
        }
    }
}

 

initView 쪽에는 햄버거버튼 클릭이벤트를 달아주면 좋을것같고

나머지 하단의 function 들은, 외부에서 햄버거버튼을 보여줄지? 타이틀을 숨질지 동작을 위한 메소드이다.

 

3) Activity 에 타이틀 추가

 

MainActivity 에 타이틀을 추가해보겠다.

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

////추가////
    <com.example.basic_mobile.ui.TitleBar
        android:id="@+id/inc_titlebar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/title_height"
        app:layout_constraintTop_toTopOf="parent"/>
////////

    <Button
        android:id="@+id/btn_recycler"
        android:text="기본 Recycler View"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <Button
        android:id="@+id/btn_epoxy"
        android:text="epoxy View"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_recycler"/>

</androidx.constraintlayout.widget.ConstraintLayout>

 

 

위와같은 방법으로 TitleBar 를 include 한다

 

그리고 Activity 에서 햄버거버튼 및 title 의 text 를 설정한다

 

class MainActivity : AppCompatActivity() {

    private var binding: ActivityMainBinding? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        ////추가////
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding!!.root)
        setTitleBar()
    }

    private fun setTitleBar () {
        binding?.incTitlebar?.setTitle("MAIN")
        binding?.incTitlebar?.showMenuIcon(true)
    }
}

 

이렇게하면 타이틀 추가 완료

 

728x90
반응형

앱을 실행시키면 Activity가 띄워질때까지 흰 화면이 보일것이다.

이것을 감추기위하여 splsh 화면을 추가해보자.

 

순서는 새로운 splash 전용 새로운 액티비티를 생성하고 해당 액티비티에 theme 로 splash 이미지를 걸어둘것이다.

 

1. 이미지 다운로드 및 추가

난 적당히 로고로 사용할 이미지를 다운받았다

https://pixabay.com

 

이미지를 프로젝트의 res > drawable 폴더에 추가한다

 

2. 로고 이미지 resource 화

이미지를 그대로 사용하기보다는 themes 에 추가하여 사용하는것이 재사용성에 좋다

 

우선 drawable 우클릭 -> New -> Drawable Resource File 선택 후 file name 을 splash.xml 로 설정한다

 

 

아래와 같이 코드를 바꿔준다.

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/white" />
        </shape>
    </item>

    <item android:bottom="0dp" android:right="30dp">
        <bitmap
            android:gravity="center_vertical|right"
            android:src="@drawable/logo" />
    </item>

</layer-list>

 

이미지를 xml resource 로 바꿔준것이며 바탕화면을 하얀색으로 바꾸고 이미지를 추가해주었다.

 

3. Themes에 해당 resource 추가

 

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Base.Theme.Basic_mobile" parent="Theme.Material3.DayNight.NoActionBar">
        <!-- Customize your light theme here. -->
        <!-- <item name="colorPrimary">@color/my_light_primary</item> -->
    </style>

    <style name="Theme.Basic_mobile" parent="Base.Theme.Basic_mobile" />

////추가////
    <style name="AppTheme" parent="Theme.MaterialComponents.Light.Bridge">
        <item name="android:textSize">18dp</item>
    </style>
 ////추가////
    <style name="AppTheme.Splash">
        <item name="android:windowBackground">@drawable/splash</item>
    </style>

</resources>

 

4. Entry Activity 추가

empty activity 를 추가해주자. 파일명은 EntryActivity 이며 activity_entry.xml 은 건들지말고 EntryActivity.kt 파일만 수정한다

class EntryActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        goPageIntro()
    }

    fun goPageIntro() {
        Handler(Looper.getMainLooper()).postDelayed({
            val intent = Intent(this, IntroActivity::class.java)
            startActivity(intent)

            finish()
        },1500) //여기숫자로 splash 화면 초 설정 가능
    }
}

 

해당 액티비티에 들어오면바로 IntroActivity 로 이동하게 하는 로직을 추가했다.

 

5. AndroidManifest 수정

 

이렇게 만든 EntryActivity 가 앱 첫실행 시 로드되도록 AndroidManifest를 수정해보자

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" >

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Basic_mobile"
        tools:targetApi="31" >
        <activity
            android:name=".IntroActivity"
            android:exported="false" />
        <activity
            android:name=".MainActivity"
            android:exported="false" />
        <activity
            android:name=".EntryActivity" ////수정////
            android:exported="true"
            android:theme="@style/AppTheme.Splash" > ////수정////
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

이렇게 하면 logo 이미지를 resource 화 시키고 theme 에 등록시켜서 android:theme 으로 불러오기까지 완성!

 

 

 

추가적으로 오류사항을 발견했다. splash 화면을 넣었음에도, 상단에 basic_mobile 이라는 app name이 노출된다.

app name을 없애보자

 

6. app name 제거

themes.xml 파일을 수정해보자

 

....

    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

    <style name="AppTheme.NoActionBar.Splash">
        <item name="android:windowBackground">@drawable/splash</item>
    </style>
....

 

windowActionBar , windowNotitle 속성을 추가했다.

그리고 겸사겸사 다른이미지로 바꿈...

이렇게하면 더이상 app name이 보이지않는다.

 

 

 

728x90
반응형

 

예제로, 특정 버튼을 클릭하면 홈화면이 뜨도록 해보자.

나는 IntroActivity 를 생성하고 버튼을 클릭하면 MainActivity 가 뜨도록 수정할것이다.

 

1. Intro Activity 생성

지난 포스팅에서와 같이, Activity 는 화면이라고 이해하면 되겠다.

 

MainActivity 의 상위 패키지에서 New > Activity > Empty Views Activity 를 선택한다

 

새로운 Activity 이름은 IntroActivity 로 설정하자

Layout Name 은 자동으로 바뀔텐데 혹시 자동으로 바뀌지않았다면 activity_intro 라고 입력해주자

 

2. Intro 꾸미기

 

아래와 같이 깔끔하게 기본세팅해준다

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

</layout>

 

이곳이 Intro Activity 임을 알수있게 꾸며주자.

 

간단하게 텍스트를 추가해보겠다. 웹에서는 <input> 태그를 사용했다면, 안드로이드에서는 EditText, 혹은 TextView 를 사용한다.

EditText는 수정가능한 <input> 이고, TextVeiw 는 수정불가한 <p> 태그와 비슷하다

 

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

////추가////
    <TextView
        android:text="This is Intro"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</layout>

 

이렇게하면 우측 미리보기화면에 글자가 나와있는것을 볼 수 있다.

만약 layout_width, height 를 설정하지않으면 글자가 안보인다. 해당 설정을 해줘야만 보인다

 

여기에, 버튼을 하나 추가해보자.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <TextView
        android:text="This is Intro"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

///추가/////
    <Button
        android:text="버튼"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</layout>

 

이상하게도 버튼이 보이지않는다.

이럴때 필요한게 웹에서의 <li> 와 같은 기능이다.

 

안드로이드는 여러가지 요소를 세로, 혹은 가로로 나타나고싶을때 부모태그를 추가해줘야한다.

 

ConstraintLayout 을 쓰면 자동완성으로 아래와 같은 태가 보인다.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

////추가/////
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:text="This is Intro"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

        <Button
            android:text="버튼"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 

width, height 를 match_parent로 해준다. 높이 넓이를 부모요소와 같게 하겠다는 뜻이다.

나는 텍스트와 버튼을 세로정렬로 해주고싶어서 orientation = vertical 인데, 세로정렬은 horizontal 이다

 

하지만 이렇게하면 버튼이 TextView를 덮어버리므로 버튼을 맨 아래로 보내고 TextView 를 정가운데로 보내보자.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:text="This is Intro"
            android:layout_width="wrap_content" ////수정////
            android:layout_height="wrap_content" ////수정////
            app:layout_constraintTop_toTopOf="parent" ////추가////
            app:layout_constraintBottom_toBottomOf="parent" ////추가////
            app:layout_constraintStart_toStartOf="parent" ////추가////
            app:layout_constraintEnd_toEndOf="parent"/> ////추가////

        <Button
            android:text="버튼"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"/> ////추가////
    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

 

이렇게 인트로 화면을 만들었다.

TextView 를 wrap_content 로 바꾼이유는, 상위요소 기준으로 가운데로 보내야되는데 넓이가 부모만큼 크면 어디로 보내야할지 모르게 된다.

 

그래서 TextView 범위는 content 를 감쌀정도, 작게 만들고 상위요소를 기준으로 해당 컨텐츠 위치를 조정하는 의미이다

 

 

3. Main Activity 로 이동하기

버튼을 누르면 MainActivity 로 이동시켜주려고 한다.

그러려면 버튼에 클릭이벤트를 주어야한다.

 

우선 버튼에 id를 할당하자.

//activity_intro.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:text="This is Intro"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

        <Button
            android:id="@+id/btn_go_main" //// 추가 ////
            android:text="버튼"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

 

해당 xml 의 이벤트등록 및 데이터바인딩은 모두 activity_intro.xml 의 짝꿍 IntroActivity.kt 에서 할수있다.

//introActivity

class IntroActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_intro)
        
        //버튼 정의
        val goMainBtn = findViewById<Button>(R.id.btn_go_main)
        
        //버튼 클릭 이벤트
        goMainBtn.setOnClickListener {
            val intent = Intent(this, MainActivity::class.java)
            startActivity(intent)
        }
        
    }
}

 

버튼 정의 부분을 보면

findViewById<Button>(R.id.btn_go_main)

 

findViewById  : id를 기준으로 찾을건데

<Button> : 버튼 요소이며

R.id.btn_go_main : id 는 btn_go_main 

 

버튼을 goMainBtn 이라는 변수에 넣고 클릭이벤트를 준것이다.

 

Activity 간의 이동을 할때는 intent 정의 후 startActivity 로 이동한다. 이것은 공식!

 

외전) 버튼 정의 우아하게 하기

버튼정의할때 위의 방법은 기초적인 방법이고...실무에서 자주 쓰이는 binding 방법으로 수정하

 

1) build.gradle 수정

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}
android {
    namespace 'com.example.basic_mobile'
    compileSdk 34
...
..
.

    kotlinOptions {
        jvmTarget = '1.8'
    }
    
    buildFeatures { ////추가////
        viewBinding = true
        dataBinding true
    }
}

 

buildfeatrures 를 추가해준다. 이것은 데이터바인딩을 하겠다는 의미.

build.gradle 수정하고 Sync now 를 클릭해준다.

 

2) IntroActivity 수정

class IntroActivity : AppCompatActivity() {

    private lateinit var binding : ActivityIntroBinding ////추가////
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

         ////수정////
        //binding 초기화 
        binding = ActivityIntroBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        //버튼 정의
        val goMainBtn = binding.btnGoMain ////수정////

        //버튼 클릭 이벤트
        goMainBtn.setOnClickListener {
            val intent = Intent(this, MainActivity::class.java)
            startActivity(intent)
        }
    }

}

 

binding 이라는 변수를 추가해주고, activity_intro.xml과 연결시킨다.

 

본인의 나는 activity_intro.xml 이라서 ActivityIntroBinding 이지만

만약 activity_sample,xml 이라면 ActivitySampleBinding 일것이다.

 

어짜피 자동완성되므로 괜춘.

 

4. 앱 실행 시 Intro Activity 실행

AndroidManifest.xml로 가서 앱 첫진입대상을 바꿔주자

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Basic_mobile"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity" //// 수정 ////
            android:exported="false" />
        <activity
            android:name=".IntroActivity" //// 수정 ////
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

 

이렇게하면, 앱 첫진입은 intro에서하고 버튼누르면 MainActivity 로 페이지 이동하는것을볼 수 있다.

 

 

 

만약 에러가 발생한다면 Logcat 을 확인하자

좌측 상단에 현재 내 에뮬레이터 기기가 맞는지 유의할것!!

 

디버깅이 필요하다면 벌레모양 클릭~

 

원하는 코드에 breack point 생성~

 

 

최종적으로 아래와 같은 동작을 확인할 수 있다.

728x90
반응형

1.  프로젝트 생성

안드로이드 스튜디오 -> New Project -> Empty Views Activity 

 

2. 화면 생성

Empty View Activity 로 프로젝트를 생성하면 아래와 같은 프로젝트를 확인할 수 있다.

 

 

자 이제 그러면 이 괴상하게 생긴 프로젝트를 뜯어보자.

 

애뮬레이터관련 세팅은 모두 완료했다고 가정하고, 프로젝트를 시작시키면 hello world 페이지가 나온다.

이 페이지는 어떻게 나온걸까?

 

 

1) activity_main.xml

바로 이곳을 통해서 나왓다. 이곳은 프로젝트를 생성하면 기본적으로 주어지는 페이지이다.

 

아래와 같은 파일이 보일텐데 겁먹지말고 우측의 Split 을 눌러주자.

 

 

그러면 아래와 같이 코딩을 할 수 있는 화면과 우측의 미리보기 화면이 제공된다

 

앞으로 개발할때는 이 화면을 기본으로 두고 개발하면 된다.

 

왼쪽 코드영역에서 TextView 태그를 자세히 보면 android:text = "Hello World!" 가 보일텐데, 이것이 앱에 보이는 글씨다.

 

그렇다면 어떤 원리로 이 화면이 나오게 되는걸까?

 

2) MainActivity.kt

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

 

onCreate 라는 함수를 통해서 View 즉 화면을 생성한다.

 

create 를 할건데, 어떤 view를 보여줄것이냐? setContentView( ) 괄호를 보여줄것이다. 

괄호에 들어가는 파라미터는 R.layout. ~~ 이런 형식인데 R은 root 라고 생각하자.

 

프로젝트 root 에서 layout 폴더에 들어있는 xml들을 해당 파라미터에 넣을 수 있다.

현재는 activity_main 밖에없으므로 R.layout. 을 입력하면 자동완성으로 acitivity_main 이 보여질것이다.

(activity_main 을 Ctrl +우클릭 혹은 Ctrl + Alt 해보면 activity_main.xml로 이동됨)

 

이렇게 activity 를 통해서 view 화면이 앱에 보여지는데 정작 MainActivity는 어디서 실행되는걸까?

 

3) AndroidManifest.xml

 

이곳은 안드로이드 앱권한, intro 화면 설정, Activity 종류를 나열하는 곳이다.

새로운 Activity 를 생성하면 이곳에도 추가된다.

 

<activity> 태그를 보면 name 으로 MainActivity 가 적혀있는데, 이 태그는 MainActivity 에 대한 설정이라는 뜻

그리고 intent-filter 로

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

 

이것은 앱을 실행 시켰을 때 처음으로 진입하는 진입점이라는 표시다.

 

즉 해석하자면, MainActivity 가 있으며 이것은 앱을 실행했을때 가장 처음으로 노출되는 화면이라는 뜻이다.

 

 

3. 구조 정리

 

이렇게 프로젝트 생성과 어떻게 앱 화면이 노출되는지 순서대로 살펴보았다.

 

주요 개념은 아래와 같다.

 

build.gradle

dependencies 추가

 

manifests

앱권한, intro, mainActivity, splash 화면 설정

 

java

Activity 파일 위치하며, 로직을 작성하는 곳

 

res

이미지, 레이아웃, 화면을 그리는 xml 파일 등 resources가 위치함

 

728x90

+ Recent posts