반응형

 

TextView 등 기타 요소를 사용하여 키보드를 활성화시키면 키보드의 화살표를 눌러야 키보드가 숨겨진다

 

하지만 보통 사용자가 사용할대는 화면을 클릭했을때 없어지길 바라는데,

Activity 에 아래의 로직을 선언하면 키보드는 감춰진다

 

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
    val imm: InputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
    imm.hideSoftInputFromWindow(currentFocus?.windowToken, 0)

    return super.dispatchTouchEvent(ev)
}

 

728x90
반응형

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

+ Recent posts