728x90

HomeFragment 상단 뷰 페이저

 

HomeFragment 

더보기
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.google.android.material.tabs.TabLayoutMediator
import com.nbc.shownect.R
import com.nbc.shownect.databinding.FragmentHomeBinding
import com.nbc.shownect.fetch.network.retrofit.RetrofitClient.fetch
import com.nbc.shownect.fetch.repository.impl.FetchRepositoryImpl
import com.nbc.shownect.presentation.home.adapter.GenreAdapter
import com.nbc.shownect.presentation.home.adapter.KidShowAdapter
import com.nbc.shownect.presentation.home.adapter.PosterClickListener
import com.nbc.shownect.presentation.home.adapter.TopRankAdapter
import com.nbc.shownect.presentation.home.adapter.UpcomingShowAdapter
import com.nbc.shownect.presentation.main.MainViewModel
import com.nbc.shownect.presentation.main.MainViewModelFactory
import com.nbc.shownect.presentation.ticket.TicketDialogFragment
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class HomeFragment : Fragment(), PosterClickListener {
    private var _binding: FragmentHomeBinding? = null
    private val binding get() = _binding!!
    private val viewModel: HomeViewModel by viewModels {
        HomeViewModelFactory(
            fetchRemoteRepository = FetchRepositoryImpl(fetch),
        )
    }
    private val sharedViewModel: MainViewModel by activityViewModels<MainViewModel> {
        MainViewModelFactory(
            fetchRemoteRepository = FetchRepositoryImpl(
                fetch
            )
        )
    }
    private val upComingShowAdapter: UpcomingShowAdapter by lazy { UpcomingShowAdapter(this) }
    private val topRankAdapter: TopRankAdapter by lazy { TopRankAdapter(this) }
    private val genreAdapter: GenreAdapter by lazy { GenreAdapter(this) }
    private val kidShowAdapter: KidShowAdapter by lazy { KidShowAdapter(this) }
    private var isPaging = false
    private var pagingJob: Job? = null
    private val onPageChangeCallback: OnPageChangeCallback = object : OnPageChangeCallback() {
        //페이지 선택 될 때마다 호출
        override fun onPageSelected(position: Int) {
            super.onPageSelected(position)
            binding.tvPageIndicator.text = "${position + 1} / 10"
        }
    }
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        initViews()
        setUpObserve()
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        with(viewModel) {
            //공연 예정작
            fetchUpcoming()
            //TOP 10 공연
            fetchTopRank()
            //장르 스피너 선택
            binding.spinnerHomeGenre.setOnSpinnerItemSelectedListener<String> { _, _, newIndex, _ ->
                fetchGenre(newIndex)
            }
            //어린이 관람 가능 공연 목록
            fetchKidShow()
        }
    }

    //화면 초기 설정
    private fun initViews() {
        //어뎁터 초기화
        upComingShowAdapter
        topRankAdapter
        kidShowAdapter
        initRecyclerView()

        //viewPager 연결
        with(binding.viewPager) {
            adapter = upComingShowAdapter
            //viewPager PageTransformer 세팅
            offscreenPageLimit = 1
            setPageTransformer(SliderTransformer(requireContext()))
            val itemDecoration = HorizontalMarginItemDecoration(
                requireContext(),
                R.dimen.viewpager_current_item_horizontal_margin
            )
            addItemDecoration(itemDecoration)


            registerOnPageChangeCallback(onPageChangeCallback)

            //tab 연결
            TabLayoutMediator(binding.tabPosterIndicator, this) { tab, position ->
                currentItem = tab.position
            }.attach()
        }
        //장르 연극 초기화
        viewModel.fetchGenre(0)
    }

    //옵저브 세팅
    private fun setUpObserve() {
        with(viewModel) {
            showList.observe(viewLifecycleOwner) {
                upComingShowAdapter.submitList(it)
                if (!isPaging) startPaging()
            }
            topRank.observe(viewLifecycleOwner) {
                topRankAdapter.submitList(it?.take(10))
            }
            genre.observe(viewLifecycleOwner) {
                genreAdapter.submitList(it)
            }
            kidShow.observe(viewLifecycleOwner) {
                kidShowAdapter.submitList(it)
            }
            //로딩 화면 처리
            isLoadingGenre.observe(viewLifecycleOwner) {
                binding.skeletonGenreLoading.isVisible = !it
            }
            isLoadingRecommend.observe(viewLifecycleOwner) {
                binding.skeletonTopRankLoading.isVisible = !it
            }
            isLoadingKid.observe(viewLifecycleOwner) {
                binding.skeletonKidLoading.isVisible = !it
            }

        }
    }

    //리사이클러뷰 초기화
    private fun initRecyclerView() {
        with(binding) {
            //HOT 추천 리사이클러뷰
            rvHomeTopRank.apply {
                adapter = topRankAdapter
                layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
            }
            //장르별 리사이클러뷰
            rvHomeGenre.apply {
                adapter = genreAdapter
                layoutManager =
                    LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
            }
            //어린이 공연 리사이클러뷰
            rvHomeKidShow.apply {
                adapter = kidShowAdapter
                layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
            }
        }
    }

    //3초 후 자동 페이징
    private fun nextPage() {
        runCatching {
            with(binding) {
                if (viewPager.currentItem == 9) {
                    lifecycleScope.launch {
                        delay(3000)
                    }
                    viewPager.currentItem = 0
                } else {
                    viewPager.currentItem++
                }
            }
        }.onFailure {
            Toast.makeText(context, "Exception nextPage()", Toast.LENGTH_SHORT).show()
        }
    }

    //페이징 스타트 함수
    private fun startPaging() {
        isPaging = true
        pagingJob = lifecycleScope.launch {
            while (true) {
                delay(3000)
                nextPage()
            }
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
        isPaging = false
        pagingJob?.cancel()
    }

    //포스터 클릭 시 티켓
    override fun posterClicked(id: String) {
        val ticketDialog = TicketDialogFragment()
        sharedViewModel.sharedShowId(id) //해당 공연의 id를 MainViewModel로 보내줌
        ticketDialog.setStyle(
            DialogFragment.STYLE_NORMAL,
            R.style.RoundCornerBottomSheetDialogTheme
        )
        ticketDialog.show(childFragmentManager, ticketDialog.tag)
    }
}

offscreenPageLimit = 1 

  • 미리 로딩할 페이지 수

SliderTransformer - 페이지 변환 효과, 스크롤될 때 각 페이지에 적용되는 시각적 변환

  • transformPage 메소드는 페이지의 위치(position)에 따라 페이지의 변환을 결정
  • position 값은 현재 중앙에 위치한 페이지를 기준으로 왼쪽에 있는 페이지는 음수(-1, -0.5 등), 오른쪽에 있는 페이지는 양수(0.5, 1 등)으로 표현
  • page.translationX는 페이지의 가로 위치를 조정하여, 스크롤 시 페이지가 어떻게 이동할지 정의 (페이지 사이의 간격을 조정하는 데 사용)
  • page.scaleY는 페이지의 세로 축 스케일을 조정하여, 페이지가 어느 정도 떨어져 있을 때 크기가 줄어들게 함
  • page.alpha는 투명도 설정 (페이지가 중앙에 있을 때(position == 0)) , 최대 100% 불투명도(1.0f) 중앙에서 멀어질 수록 투명 (0.25f 는 최소 투명도)

리소스에서 다음 페이지가 보이는 부분(nextItemVisiblePx)과 현재 페이지의 수평 마진(currentItemHorizontalMarginPx)을 가져오고, 이 두 값을 합산하여 pageTranslationX를 계산 (스크롤 시 페이지의 위치 변환에 사용)

더보기
import android.content.Context
import android.view.View
import androidx.viewpager2.widget.ViewPager2
import com.nbc.shownect.R
import kotlin.math.abs

class SliderTransformer(context: Context) :
    ViewPager2.PageTransformer {
    override fun transformPage(page: View, position: Float) {
        page.translationX = -pageTranslationX * position
        page.scaleY = 1 - (0.25f * abs(position))
        page.alpha = 0.25f + (1 - abs(position))
    }

    val nextItemVisiblePx =
        context.resources.getDimension(R.dimen.viewpager_next_item_visible)
    val currentItemHorizontalMarginPx =
        context.resources.getDimension(R.dimen.viewpager_current_item_horizontal_margin)
    val pageTranslationX = nextItemVisiblePx + currentItemHorizontalMarginPx
}

 

HorizontalMarginItemDecoration - 리사이클러뷰 항목에 수평 마진을 추가하는 기능 (주로 리사이클러 뷰 내부의 아이템 간격을 조정하는 데 사용)

  • 생성자는 Context와 마진 값을 DP 단위로 받아 해당 값을 픽셀 단위로 변환
  • context.resources.getDimension() 리소스 ID를 통해 해당 마진 값의 실제 픽셀 크기를 가져옴
  • getItemOffsets() 메소드는 리사이클러뷰 내 각 항목에 대한 오프셋(여기서는 마진)을 설정, outRect.left와 outRect.right를 설정하여 각 항목의 왼쪽과 오른쪽에 마진을 적용
더보기
import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.annotation.DimenRes
import androidx.recyclerview.widget.RecyclerView

class HorizontalMarginItemDecoration(context: Context, @DimenRes horizontalMarginInDp: Int) :
    RecyclerView.ItemDecoration() {
    private val horizontalMarginInPx: Int =
        context.resources.getDimension(horizontalMarginInDp).toInt()

    override fun getItemOffsets(
        outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State
    ) {
        outRect.right = horizontalMarginInPx
        outRect.left = horizontalMarginInPx
    }
}

res/values/dimens/dimens.xml

더보기
<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
    //추가된 부분 
    <dimen name="viewpager_next_item_visible">26dp</dimen>
    <dimen name="viewpager_current_item_horizontal_margin">42dp</dimen>
</resources>

viewpager_next_item_visible

  • ViewPager2를 사용할 때, 슬라이드되고 있는 다음 항목이 얼마나 보여질지를 정의하는 값
  • SliderTransformer Class에서 사용 

viewpager_current_item_horizontal_margin

  • 현재 활성화된 ViewPager2 항목의 수평 마진 크기를 정의
  • 현재 보여지는 항목과 양 옆의 항목 사이의 간격을 결정
  • HorizontalMarginItemDecoration Class 에서 사용 

 

참고 

https://github.com/unaisulhadi/ViewPager2-Carousel

 

GitHub - unaisulhadi/ViewPager2-Carousel: A simple Carousel for ViewPager2

A simple Carousel for ViewPager2. Contribute to unaisulhadi/ViewPager2-Carousel development by creating an account on GitHub.

github.com

https://stackoverflow.com/questions/71108306/viewpager2-left-and-right-preview-item-scrolling-not-working

 

Viewpager2 left and right preview item scrolling not working

I am trying to create an image slider using ViewPager2 with left and right previews. Everything is working perfectly, but scrolling on left and right preview items is not working. There is a similar

stackoverflow.com

 

+ Recent posts