Skip to content

Latest commit

ย 

History

History
542 lines (399 loc) ยท 18.9 KB

README.md

File metadata and controls

542 lines (399 loc) ยท 18.9 KB

THE-SOPT-30th Android Part

[ seminar ๋ชฉ์ฐจ ]

1๏ธโƒฃ Seminar

2๏ธโƒฃ Seminar

3๏ธโƒฃ Seminar


1๏ธโƒฃ Week

seminar1-level1

  • SignInActivity

     private fun loginEvent() {
        binding.btnLogin.setOnClickListener {
            if (!binding.etId.text.isNullOrBlank() && !binding.etPw.text.isNullOrBlank()) {
                showToast("๋กœ๊ทธ์ธ ์„ฑ๊ณต")
                goHome()
            } else
                showToast("์•„์ด๋””/๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”")
         }
      }
    
    • isNullOrBlank()์„ ํ†ตํ•ด ์ž…๋ ฅ์—ฌ๋ถ€ ํ™•์ธํ•˜๊ธฐ , ์กฐ๊ฑด์ด ๋งž๋‹ค๋ฉด goHome() ํ˜ธ์ถœํ•˜์—ฌ HomeActivity๋กœ ์ด๋™
    • ContextUtil์„ ์‚ฌ์šฉํ•˜์—ฌ Toast ๋ฉ”์„ธ์ง€ ๊ฐ„๋‹จํžˆ ๋‚˜ํƒ€๋‚ด๊ธฐ : showToast()
  • SignUpActivity

     private fun signupEvent() {
        with(binding) {
            btnComplete.setOnClickListener {
                if (!binding.etId.text.isNullOrBlank() && !binding.etName.text.isNullOrBlank() && !binding.etPw.text.isNullOrBlank()) {
                    val intent = Intent(this@SignUpActivity, SignInActivity::class.java)
                    intent.putExtra("id", etId.text.toString())
                    intent.putExtra("password", etPw.text.toString())
                    setResult(RESULT_OK, intent)
                    finish()
                } else {
                    showToast("์ž…๋ ฅํ•˜์ง€ ์•Š์€ ์ •๋ณด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.")
    
                }
            }
        }
    }
    
    • isNullOrBlank()์„ ํ†ตํ•ด ์ž…๋ ฅ์—ฌ๋ถ€ ํ™•์ธํ•˜๊ธฐ , finish()๋กœ ์Šคํƒ์—์„œ ๋‚˜์˜ค๊ธฐ
    • intent, putExtra๋ฅผ ํ†ตํ•ด ์ž…๋ ฅํ•œ ์•„์ด๋””, ๋น„๋ฐ€๋ฒˆํ˜ธ ์ •๋ณด๋ฅผ intent ํ™”๋ฉด์ „ํ™˜์„ ํ†ตํ•ด ๊ฐ’์„ ์ „๋‹ฌํ•ด์คŒ -> "Result_OK" result_code ์ „๋‹ฌ
    • ContextUtil์„ ์‚ฌ์šฉํ•˜์—ฌ Toast ๋ฉ”์„ธ์ง€ ๊ฐ„๋‹จํžˆ ๋‚˜ํƒ€๋‚ด๊ธฐ : showToast()

seminar1 level2 , level3-1

  • SignInActivity

         private fun setSignUp() {
       resultLauncher =
           registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
               if (result.resultCode == Activity.RESULT_OK) {
                   val id = result.data?.getStringExtra("id") ?: ""
                   val password = result.data?.getStringExtra("password") ?: ""
                   binding.etId.setText(id)
                   binding.etPw.setText(password)
               }
           }
        }
    
       private fun signupEvent() {
           binding.btnSignup.setOnClickListener {
               val intent = Intent(this, SignUpActivity::class.java)
               resultLauncher.launch(intent)
           }
       }
    
    • registerForActivityResult()๋ฅผ ํ†ตํ•ด ํšŒ์›๊ฐ€์ž…์—์„œ ์ž…๋ ฅํ–ˆ๋˜ ์•„์ด๋””, ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ž…๋ ฅ๋˜๊ฒŒ ํ•จ
  • ScrollView, ImageView, DataBinding

    <data>
    
       <variable
           name="user"
           type="org.sopt.seminar.User" />
    </data>
    
    <ScrollView
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       tools:context=".HomeActivity">
    
       <androidx.constraintlayout.widget.ConstraintLayout
           android:layout_width="match_parent"
           android:layout_height="wrap_content">
    
           <ImageView
               android:id="@+id/img_profile"
               android:layout_width="0dp"
               android:layout_height="0dp"
               android:layout_marginHorizontal="120dp"
               android:layout_marginTop="50dp"
               android:src="@drawable/profile"
               app:layout_constraintDimensionRatio="1:1"
               app:layout_constraintEnd_toEndOf="parent"
               app:layout_constraintStart_toStartOf="parent"
               app:layout_constraintTop_toTopOf="parent" />
           <TextView
               .
               .
               android:text="@{user.age}"
               .
               ./>
    • constraintDimensionRatio๋กœ ์‚ฌ์ง„ ๋น„์œจ 1:1 , DataBinding์œผ๋กœ User ๋ฐ์ดํ„ฐ ์ƒ์„ฑ, ScrollView ๊ตฌํ˜„
  • ๐ŸคŸ ViewBinding๊ณผ DataBinding์˜ ๊ฐœ๋…

    [DataBinding]
    
    -๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์˜ ์ฃผ๋ชฉ์ ์€ UI ๋ ˆ์ด์•„์›ƒ์˜ ๋ทฐ๋ฅผ ์•ฑ ์ฝ”๋“œ์— ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ์™€ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์„ ์ œ๊ณต
    -๋ฐ์ดํ„ฐ์™€ ๋ทฐ๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ์ž‘์—…์„ ๋ ˆ์ด์•„์›ƒ์—์„œ 
    -๋ฒ„ํŠผ๊ณผ ๊ฐ™์€ UI ์ปจํŠธ๋กค์„ UI์ปจํŠธ๋กค๋Ÿฌ ๋˜๋Š” ViewModel ์ธ์Šคํ„ด์Šค์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ๊ฐ์ฒด์˜ ์ด๋ฒคํŠธ๋‚˜ ๋ฆฌ์Šค๋„ˆ ํ•จ์ˆ˜์— ์—ฐ๊ฒฐ ์‹œํ‚ค๋Š” ํŽธ๋ฆฌํ•œ ๋ฐฉ๋ฒ•๋„ ์กด์žฌ
    -ํŠนํžˆ LiveData ์ปดํฌ๋„ŒํŠธ์™€ ๊ฐ™์ด ์‚ฌ์šฉ ๋  ๋•Œ ์ด์ ์ด ๋ฐฐ๊ฐ€ ๋จ
    -xml๋‹จ์—์„œ <layout> ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋งŒ๋“  ๊ฒƒ๋งŒ ์ฒ˜๋ฆฌ
    
    [ViewBinding]
    
    -findViewById()์‚ฌ์šฉ๋ณด๋‹ค Null ์•ˆ์ „์— ์žˆ์–ด ์•„๋ž˜์™€ ๊ฐ™์€ ์žฅ์ ์ด ์กด์žฌ
    -๋ทฐ ๊ฒฐํ•ฉ์€ ๋ทฐ์˜ ์ง์ ‘ ์ฐธ์กฐ๋ฅผ ์ƒ์„ฑํ•˜๋ฏ€๋กœ ์œ ํšจํ•˜์ง€ ์•Š์€ ๋ทฐ ID๋กœ ์ธํ•ด null ํฌ์ธํ„ฐ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์œ„ํ—˜์ด ์—†์Œ
    -๋ ˆ์ด์•„์›ƒ์˜ ์ผ๋ถ€ ๊ตฌ์„ฑ์—๋งŒ ๋ทฐ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ๊ฒฐํ•ฉ ํด๋ž˜์Šค์—์„œ ์ฐธ์กฐ๋ฅผ ํฌํ•จํ•˜๋Š” ํ•„๋“œ๊ฐ€ @Nullable๋กœ ํ‘œ์‹œ
    -๋ทฐ๋ฐ”์ธ๋”ฉ์€ ๋ฐ์ดํ„ฐ๋ฐ”์ธ๋”ฉ์— ๋น„ํ•ด ์ฃผ์„์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋” ๋น ๋ฅธ ์ปดํŒŒ์ผ ์†๋„๋ฅผ ๊ฐ€์ง
    -ํ•˜์ง€๋งŒ ๋ทฐ๋ฐ”์ธ๋”ฉ์€ ๋ ˆ์ด์•„์›ƒ ๋ณ€์ˆ˜ ๋˜๋Š” ๋ ˆ์ด์•„์›ƒ ํ‘œํ˜„์‹์„ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ XML ๋ ˆ์ด์•„์›ƒ ํŒŒ์ผ์—์„œ ์ง์ ‘ ๋™์  UI ์ฝ˜ํ…์ธ ๋ฅผ ์„ ์–ธํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ ๋ถˆ๊ฐ€๋Šฅ
    -์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๊ฒฐํ•ฉ ์ง€์› ๋ถˆ๊ฐ€
    
    

๐Ÿ“ธ ์‹คํ–‰ ํ™”๋ฉด

๋กœ๊ทธ์ธ ํšŒ์›๊ฐ€์ž…

2๏ธโƒฃ Week

seminar2-level1

  • HomeActivity

      supportFragmentManager.beginTransaction().add(R.id.fragment_home, followerFragment).commit()
    
        binding.btnFollower.setOnClickListener {
            val transaction = supportFragmentManager.beginTransaction()
            if (position == REPO_POSITION) {
                transaction.replace(R.id.fragment_home, followerFragment).commit()
                position = FOLLOWER_POSITION
            }
        }
        binding.btnRepo.setOnClickListener {
            val transaction = supportFragmentManager.beginTransaction()
            if (position == FOLLOWER_POSITION) {
                transaction.replace(R.id.fragment_home, repoFragment).commit()
                position = REPO_POSITION
            }
        }
    
    • initTransactionEvent()๊ตฌํ˜„
    • ํŒ”๋กœ์›Œ ๋ชฉ๋ก Fragment๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์ด๋ฉฐ ๋ ˆํฌ์ง€ํ† ๋ฆฌ ๋ชฉ๋ก ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ์‹œ Fragment ์ „ํ™˜๋จ
  • item_repo_list.xml

         <TextView
            ...
            android:ellipsize="end"
            android:ems="8"
            android:maxLines="1"
            android:text="@{repo.repo}"
            android:textColor="@color/black"
            android:textSize="17sp"
            android:textStyle="bold"
            ... />
    
        <TextView
            ...
            android:ellipsize="end"
            android:ems="7"
            android:maxLines="1"
            android:text="@{repo.introduction}"
            android:textColor="@color/black"
            android:textSize="15sp"
            ... />
    
    • ์„ค๋ช…์ด ๊ธธ์–ด ๊ธ€์”จ๊ฐ€ ๊ธธ์–ด์งˆ ๋•Œ ๋’ค์— "..."์œผ๋กœ ํ‘œ์‹œํ•˜๊ธฐ
    • ems , maxLines , ellipsize ์†์„ฑ ์‚ฌ์šฉ
  • fragment_repo.xml

        app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
    
    • xml์—์„œ GridLayoutManager์„ค์ • -> .ktํŒŒ์ผ์—์„œ๋„ ์„ค์ • ๊ฐ€๋Šฅ!!!!

seminar2-level2

  • FollowerFragment : ํŒ”๋กœ์›Œ ์ด๋ฆ„๊ณผ ์„ค๋ช…์„ name, introduce๋กœ DetailActivity๋กœ ์ „๋‹ฌ

        private fun itemClickEvent() {
        followerAdapter.setItemClickListener(object : FollowerAdapter.OnItemClickListener {
            override fun onClick(view: View, position: Int) {
                val name = followerAdapter.currentList[position].name
                val introduce = followerAdapter.currentList[position].introduction
                val intent = Intent(context, DetailActivity::class.java)
                    .putExtra("name", name)
                    .putExtra("introduction", introduce)
                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                startActivity(intent)
            }
    
          })
        }
    
  • ItemDecoration์œผ๋กœ ๋ฆฌ์ŠคํŠธ ๊ฐ„ ๊ฐ„๊ฒฉ ๋ฐ ๊ตฌ๋ถ„์„  ์ฃผ๊ธฐ

        private fun recyclerViewDecoration() {
                with(binding) {
                    rvFollower.addItemDecoration(VerticalItemDecorator(10))
                    rvFollower.addItemDecoration(HorizontalItemDecorator(10))
                }
         }
         
  • RecyclerView Item ์ด๋™ ๊ตฌํ˜„ : ItemTouchHelper() ์‚ฌ์šฉ

        val callback = MyTouchHelperCallback(followerAdapter)
        val touchHelper = ItemTouchHelper(callback)
        touchHelper.attachToRecyclerView(binding.rvFollower)
        binding.rvFollower.adapter = followerAdapter
        followerAdapter.startDrag(object : FollowerAdapter.OnStartDragListener {
            override fun onStartDrag(viewHolder: FollowerAdapter.FollowerViewHolder) {
                touchHelper.startDrag(viewHolder)
            }
        })
         

seminar2-level3

  • BaseFragment : ๋ณด์ผ๋Ÿฌ ํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ ๊ฐœ์„ 

        abstract class BaseFragment<B : ViewDataBinding>(@LayoutRes private val layoutRes: Int) :
        Fragment() {
        private var _binding: B? = null
        val binding get() = _binding!!
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            _binding = DataBindingUtil.inflate(inflater, layoutRes, container, false)
            return binding.root
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            binding.lifecycleOwner = this
            initAdapter()
        }
    
        abstract fun initAdapter()
    
        override fun onDestroyView() {
            _binding = null
            super.onDestroyView()
            }
    
        }
    
    • Fragment ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ์ด์šฉํ•˜์—ฌ onCreateView์—์„œ๋Š” view์— ๋Œ€ํ•œ ์ดˆ๊ธฐํ™” ์ž‘์—…์„ ์ˆ˜ํ–‰
    • onViewCreated์—์„œ๋Š” recyclerView์— ์‹ค์ œ adapter๋ฅผ ๋ถ™์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ๊ฒŒ๋” ๊ตฌํ˜„
  • ListAdapter, DIFFUTIL : notifyDataSetChanged ๋ฌธ์ œ์  ํ•ด๊ฒฐ

      class RepoViewHolder(
        private val binding: ItemRepoListBinding
      ) : RecyclerView.ViewHolder(binding.root) {
        fun onBind(repoData: RepoData) {
            binding.repo = repoData
        }
      }
    
    
        companion object {
        val DIFFUTIL = object : DiffUtil.ItemCallback<RepoData>() {
            override fun areItemsTheSame(
                oldItem: RepoData,
                newItem: RepoData
            ): Boolean {
                return oldItem.repo == newItem.repo
            }
    
            override fun areContentsTheSame(
                oldItem: RepoData,
                newItem: RepoData
            ): Boolean {
                return oldItem == newItem
            }
          }
        }
    
    • Fragment,item_xxx_list ๋ชจ๋‘ DataBinding์œผ๋กœ : repo
    • DIFFUTIL๋กœ ๊ธฐ์กด์˜ ๋ฐ์ดํ„ฐ ๋ฆฌ์ŠคํŠธ์™€ ๊ต์ฒดํ•  ๋ฐ์ดํ„ฐ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋น„๊ตํ•˜์—ฌ ์‹ค์งˆ์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•œ ์•„์ดํ…œ์„ ์ถ”๋ ค notifyDataSetChanged ๋ฌธ์ œ์  ๋ณด์™„
  • ๐Ÿ™€ notifyDataSetChanged ๋ฌธ์ œ์ 

    ์ˆ˜์ฒœ๊ฐœ์˜ ๋ฐ์ดํ„ฐ ์ค‘ ๋‹จ ํ•œ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋งŒ ๋ฐ”๋€๋‹ค๋ฉด notifyDataSetChanged()์˜ ์‚ฌ์šฉ์€ ๋น„ํšจ์œจ์ ์ด๋‹ค.

    ๋ณ€๊ฒฝ๋œ ๋ฐ์ดํ„ฐ์˜ position์„ ์ธ์ž๋กœ ๋„˜๊ฒจ์ฃผ์–ด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋งŒ ๋ณ€๊ฒฝํ•˜๋Š” notifyItemChanged๊ฐ€ ์žˆ์ง€๋งŒ ์—ญ์‹œ๋‚˜ position์„ ์ฐพ์•„ ๋„˜๊ฒจ์ฃผ๋ฉฐ ํ•˜๋‚˜ํ•˜๋‚˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์šด ์ผ์ด ๋ฐœ์ƒํ•œ๋‹ค.

    ์ด๋•Œ ์•„์ดํ…œ์˜ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๊ณ  ๊ฐฑ์‹ ํ•˜๋Š” ์—ญํ•  "DIFFUTIL" ์„ ์‚ฌ์šฉํ•œ๋‹ค. DIFFUTIL์€ oldList์™€ newList๋ฅผ ๋น„๊ตํ•˜์—ฌ ์ฐจ์ด๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ , newList๋กœ ๊ฐฑ์‹ ํ•ด์ฃผ๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค์ด๋‹ค.

    ์ด ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•„์ดํ…œ ๋ณ€๊ฒฝ์˜ ๊ตฌ์ฒด์ ์ธ ์ƒํ™ฉ์— ๋”ฐ๋ผ Adapter์˜ ์ ์ ˆํ•œ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

  • ListAdapter ,DIFFUTIL ๊ด€๋ จ ๋‚ด์šฉ

    • ListAdapter๋Š” DiffUtil์„ ํ™œ์šฉํ•˜์—ฌ ๋ฆฌ์ŠคํŠธ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•œ Adapter, ๊ธฐ์กด ์–ด๋Œ‘ํ„ฐ์™€ ๋น„๊ตํ•ด์„œ ์ถ”๊ฐ€๋กœ DiffUtil ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ์ฝœ๋ฐฑ ๊ธฐ๋Šฅ ํด๋ž˜์Šค๋งŒ ๊ตฌํ˜„ํ•˜๋ฉด ๋˜๋ฏ€๋กœ ์ƒ์‚ฐ์„ฑ, ํšจ์œจ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.

    • https://velog.io/@l2hyunwoo/Android-RecyclerView-DiffUtil-ListAdapter

    • https://velog.io/@deepblue/RecyclerView%EC%9D%98-notifyDataSetChanged

๐Ÿ“ธ ์‹คํ–‰ ํ™”๋ฉด

DetailActivity ์•„์ดํ…œ ์ด๋™

๐Ÿ“ seminar2 ์•Œ๊ฒŒ๋œ ์  ๐Ÿ“

โ€ฃ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ ๊ฐœ์„  : BaseActivity, BaseFragment
โ€ฃ ItemTouchHelper() , ItemDecoration() ๋‚ด์šฉ
โ€ฃ ListAdapter, DIFFUTIL, notifyDataSetChanged ๋ณต์Šต ๋ฐ ์ฐจ์ด์ 
โ€ฃ Fragment ์ƒ๋ช…์ฃผ๊ธฐ : onViewCreated()

3๏ธโƒฃ Week

seminar3-level1

  • ProFileFragment

       private fun initTransactionEvent() {
        val followerFragment = FollowerFragment()
        val repositoryFragment = RepoFragment()
    
        childFragmentManager.beginTransaction().add(R.id.fragment_profile, followerFragment)
            .commit()
        binding.btnFollower.isSelected = true //์ฒ˜์Œ ํ™”๋ฉด ๋ณด์—ฌ์งˆ ์‹œ์—
        binding.btnFollower.setTextColor(Color.BLACK)
    
        binding.btnFollower.setOnClickListener {
            childFragmentManager.beginTransaction()
                .replace(R.id.fragment_profile, followerFragment)
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
                .commit()
            binding.btnFollower.isSelected = true;
            binding.btnRepo.isSelected = false;
            binding.btnRepo.setTextColor(Color.GRAY)
            binding.btnFollower.setTextColor(Color.BLACK)
        }
        binding.btnRepo.setOnClickListener {
            childFragmentManager.beginTransaction()
                .replace(R.id.fragment_profile, repositoryFragment)
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
                .commit()
            binding.btnRepo.isSelected = true
            binding.btnFollower.isSelected = false
            binding.btnFollower.setTextColor(Color.GRAY)
            binding.btnRepo.setTextColor(Color.BLACK)
         }
      }
    
    • button, textView ํด๋ฆญ ์‹œ ์ƒ‰ ๋ณ€๊ฒฝ ๋กœ์ง ์ฝ”๋“œ
    • childFragmentManager()๋กœ ํ•œ ๋ทฐ์— follower, repository ํ”„๋ž˜๊ทธ๋จผํŠธ ์—ฐ๊ฒฐ
  • HomeFragment

         private fun initTabLayout() {
            val tabLable = listOf("ํŒ”๋กœ์ž‰", "ํŒ”๋กœ์›Œ")
    
            TabLayoutMediator(binding.homeTablayout, binding.vpHome) { tab, position ->
                tab.text = tabLable[position]
            }.attach()
        }
        

    fragment_home.xml

        <com.google.android.material.tabs.TabLayout
        android:id="@+id/home_tablayout"
        ...
        app:tabIndicatorColor="@color/sopt_main_purple"
        app:tabMode="fixed"
        app:tabSelectedTextColor="@color/sopt_main_purple" />
    
    • TabLayout ์„ค์ •
    • indicator ์ƒ‰์ƒ ์„ค์ • ๋ฐ ํด๋ฆญ ์‹œ text ์ƒ‰์ƒ ๋ณ€๊ฒฝ
  • ViewPagerAdapter(Main)

    class ViewPagerAdapter(fragment: FragmentActivity) : FragmentStateAdapter(fragment) {
    override fun getItemCount() = 3
    
    override fun createFragment(position: Int): Fragment {
        return when (position) {
            0 -> ProfileFragment()
            1 -> HomeFragment()
            else -> CameraFragment()
            }
        }
    }
    
    • viewPagerAdapter ํŒŒ์ผ ๋ถ„๋ฆฌ ํ›„ ์‚ฌ์šฉ

seminar3-level2

  • NestedScrollableHost - framgent_home.xml

      <org.sopt.seminar.NestedScrollableHost
          android:layout_width="match_parent"
          android:layout_height="0dp"
          app:layout_constraintBottom_toBottomOf="parent"
          app:layout_constraintEnd_toEndOf="parent"
          app:layout_constraintStart_toStartOf="parent"
          app:layout_constraintTop_toBottomOf="@+id/home_tablayout">
    
          <androidx.viewpager2.widget.ViewPager2
              android:id="@+id/vp_home"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:background="#FAFAFA" />
      </org.sopt.seminar.NestedScrollableHost>
    
    • NestedScrollableHost ํŒŒ์ผ ์ƒ์„ฑ ํ›„ fragment_home์— ์ ์šฉ -> ViewPager2 ์ค‘์ฒฉ ์Šคํฌ๋กค ๋ฌธ์ œ ํ•ด๊ฒฐ

    seminar3-level3

  • CameraFragment

       <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    • ์นด๋ฉ”๋ผ ๊ฐค๋Ÿฌ๋ฆฌ ์ ‘๊ทผ์„ ์œ„ํ•œ ๊ถŒํ•œ์„ AndroidManifest.xml์— ์ถ”๊ฐ€
          private val activityLauncher: ActivityResultLauncher<Intent> =
          registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
              if (it.resultCode == RESULT_OK && it.data != null) {
                  var currentImageUri = it.data?.data
                  Glide.with(requireActivity()).load(currentImageUri).into(binding.ivGalleryImage)
    
              } else if (it.resultCode == RESULT_CANCELED) {
                  requireActivity().showToast("์‚ฌ์ง„ ์„ ํƒ ์ทจ์†Œ")
              } else {
                  requireActivity().showToast("์‚ฌ์ง„ ์ฒจ๋ถ€ ์‹คํŒจ")
            }
       }
    • resultCode, registerForActivityResult()๋ฅผ ํ†ตํ•ด ์‚ฌ์ง„์„ ๊ฐค๋Ÿฌ๋ฆฌ์—์„œ ๊ฐ€์ ธ์˜จ ํ›„ "ivGalleryImage"์— ๋„ฃ๊ธฐ

๐Ÿ“ธ ์‹คํ–‰ ํ™”๋ฉด

Profile,Home Camera

๐Ÿ“ seminar3 ์•Œ๊ฒŒ๋œ ์  ๐Ÿ“

โ€ฃ nestedscrollableHost ๊ฐœ๋…
โ€ฃ shapeDrawable์œผ๋กœ ๊ฐ„๋‹จํ•œ ๋„ํ˜•์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ณ  ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
โ€ฃ ๊ฐค๋Ÿฌ๋ฆฌ์—์„œ ์‚ฌ์ง„๊ฐ€์ ธ์™€์„œ ์ด๋ฏธ์ง€๋ทฐ๋กœ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค.