Paging 分页加载(二)

1. 资源说明

  1.1 基于 Paging 分页加载(一)修改添加Paging 分页加载(一)icon-default.png?t=M85Bhttps://blog.csdn.net/u011193452/article/details/127108132​​​​​​​​​​​

  1.2 添加资源文件

    <string name="photo_tag">%d / %d</string>
    <string name="click_retry">点击重试</string>
    <string name="loading_completed">加载完毕</string>
    <string name="loading">正在加载</string>

2. 修改适配器

  2.1 添加 gallery_footer.xml 布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:gravity="center">

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="30dp" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />
</LinearLayout>

  2.2 修改 GalleryAdapter.kt

class GalleryAdapter(private val galleryViewModel: GalleryViewModel): PagedListAdapter<PhotoItem, ViewHolder>(DIFFCALLBACK) {
    private var networkStatus: NetworkStatus? = null
    private var hasFooter  = false

    init {
        galleryViewModel.retry()
    }

    //更新状态
    fun updateNetworkStatus(networkStatus: NetworkStatus){
        this.networkStatus = networkStatus
        if (networkStatus == NetworkStatus.INITIAL_LOADING||networkStatus == NetworkStatus.LOADED) {
            hideFooter()
        } else {
            showFooter()
        }
    }

    //隐藏 Footer
    private fun hideFooter() {
        if (hasFooter) {
            notifyItemRemoved(itemCount - 1)
        }
        hasFooter = false
    }

    //显示 Footer
    private fun showFooter() {
        if (hasFooter) {
            notifyItemChanged(itemCount - 1)
        }else{
            hasFooter = true
            notifyItemInserted(itemCount -1)
        }
    }

    override fun getItemCount(): Int {
        return super.getItemCount() + if (hasFooter) 1 else 0
    }

    override fun getItemViewType(position: Int): Int {
        return if (hasFooter && position == itemCount - 1) R.layout.gallery_footer else R.layout.gallery_cell
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return when(viewType){
            R.layout.gallery_cell ->
                PhotoViewHolder.newInstance(parent).also { holder ->
                    holder.itemView.setOnClickListener {
                        Bundle().apply {
                            //putParcelable("PHOTO", getItem(holder.adapterPosition))
                            //holder.itemView.findNavController().navigate(R.id.action_galleryFragment_to_photoFragment,this)
                            //putParcelableArrayList("PHOTO_LIST", ArrayList(currentList))
                            //putParcelableArrayList("PHOTO_LIST", ArrayList(snapshot()))
                            putInt("PHOTO_POSITION", holder.bindingAdapterPosition)
                            holder.itemView.findNavController().navigate(R.id.action_galleryFragment_to_pagerPhotoFragment, this)
                        }
                    }
                }
            else -> FooterViewHolder.newInstance(parent).also {
                it.itemView.setOnClickListener {
                    galleryViewModel.retry()
                }
            }
        }
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        when(holder.itemViewType){
           R.layout.gallery_footer -> (holder as FooterViewHolder).bindWithNetworkStatus(networkStatus)
           else -> {
               val photoItem = getItem(position) ?: return
               (holder as PhotoViewHolder).bindWithPhotoItem(photoItem)
           }
        }
    }

    object DIFFCALLBACK : DiffUtil.ItemCallback<PhotoItem>() {
        override fun areItemsTheSame(oldItem: PhotoItem, newItem: PhotoItem): Boolean {
            return oldItem == newItem
        }
        override fun areContentsTheSame(oldItem: PhotoItem, newItem: PhotoItem): Boolean {
            return oldItem.photoId == newItem.photoId
        }
    }
}

class PhotoViewHolder(private val binding: GalleryCellBinding) : ViewHolder(binding.root) {
    companion object{
        fun newInstance(parent: ViewGroup): PhotoViewHolder{
            val binding =  GalleryCellBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            return PhotoViewHolder(binding)
        }
    }

    fun bindWithPhotoItem(photoItem: PhotoItem) {
        with(binding){
            shimmerLayoutCell.apply {
                setShimmerColor(0x55FFFFFF)
                setShimmerAngle(0)
                startShimmerAnimation()
            }
            tvUser.text = photoItem.photoUser
            tvLikes.text = photoItem.photoLikes.toString()
            tvFavorites.text = photoItem.photoFavorites.toString()
            imageView.layoutParams.height = photoItem.photoHeight
        }

        Glide.with(itemView)
            .load(photoItem.previewUrl)
            .placeholder(R.drawable.ic_photo_place_holder)
            .listener(object : RequestListener<Drawable> {
                override fun onLoadFailed(
                    e: GlideException?,
                    model: Any?,
                    target: Target<Drawable>?,
                    isFirstResource: Boolean
                ): Boolean {
                    return false
                }
                override fun onResourceReady(
                    resource: Drawable?,
                    model: Any?,
                    target: Target<Drawable>?,
                    dataSource: DataSource?,
                    isFirstResource: Boolean
                ): Boolean {
                    return false.also { binding.shimmerLayoutCell.stopShimmerAnimation() }
                }
            })
            .into(binding.imageView)
    }
}

class FooterViewHolder(private var binding: GalleryFooterBinding) : ViewHolder(binding.root) {
    companion object {
        fun newInstance(parent: ViewGroup): FooterViewHolder {
            val binding = GalleryFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            (binding.root.layoutParams as StaggeredGridLayoutManager.LayoutParams).isFullSpan = true
            return FooterViewHolder(binding)
        }
    }

    fun bindWithNetworkStatus(networkStatus: NetworkStatus?) {
        with(binding) {
            when (networkStatus) {
                NetworkStatus.FAILED -> {
                    textView.text = binding.root.context.getString(R.string.click_retry)
                    progressBar.visibility = View.GONE
                    itemView.isClickable = true
                }
                NetworkStatus.COMPLETED -> {
                    textView.text = binding.root.context.getString(R.string.loading_completed)
                    progressBar.visibility = View.GONE
                    itemView.isClickable = false
                }
                else -> {
                    textView.text =binding.root.context.getString(R.string.loading)
                    progressBar.visibility = View.VISIBLE
                    itemView.isClickable = false
                }
            }
        }
    }
}

3. 修改获取网络数据层

  3.1 修改 PixabayDataSources.kt 文件

enum class NetworkStatus {
    INITIAL_LOADING,
    LOADING,
    LOADED,
    FAILED,
    COMPLETED
}

class PixabayDataSources(private val context: Context) : PageKeyedDataSource<Int, PhotoItem>() {
    private val mTAG: String = "MyTag"
    var retry: (() -> Any)? = null
    private val _networkStatus = MutableLiveData<NetworkStatus>()
    val  networkStatus :LiveData<NetworkStatus> = _networkStatus
    private val  queryKey =  arrayOf("cat", "beauty", "car", "dog", "phone", "computer", "flower", "animal").random()
    //Initial 最初
    override fun loadInitial(
        params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, PhotoItem>
    ) {
        retry = null
        _networkStatus.postValue(INITIAL_LOADING)
        val url = "https://pixabay.com/api/?key=30070990-cfc31c9f778ceeef4009d910d&q=${queryKey}&per_page=20&page=1"
        StringRequest(
            Request.Method.GET,
            url,
            {
                val dataList = Gson().fromJson(it, Pixabay::class.java).hits.toList()
                callback.onResult(dataList, null, 2)
                _networkStatus.postValue(LOADED)
            },
            {
                retry = { loadInitial(params, callback) }
                _networkStatus.postValue(FAILED)
                Log.e(mTAG, "loadInitial: $it")
            }
        ).also {
             VolleySingleton.getInstance(context).requestQueue.add(it)
        }
    }

    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, PhotoItem>) {
        retry = null
        val url =
            "https://pixabay.com/api/?key=30070990-cfc31c9f778ceeef4009d910d&q=${queryKey}&per_page=50&page=${params.key}"
        _networkStatus.postValue(LOADING)
        StringRequest(
            url,
            {
                val dataList = Gson().fromJson(it, Pixabay::class.java).hits.toList()
                callback.onResult(dataList, params.key + 1)
                _networkStatus.postValue(LOADED)
            },
            {
                if (it.toString() == "com.android.volley.ClientError") {
                    _networkStatus.postValue(COMPLETED)
                } else {
                    retry = { loadAfter(params, callback) }
                    _networkStatus.postValue(FAILED)
                }
                Log.e(mTAG, "loadAfter: $it")
            }
        ).also {
            VolleySingleton.getInstance(context).requestQueue.add(it)
        }
    }

    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, PhotoItem>) {
    }
}

  3.2 修改 PixabayDataSourceFactory.kt 文件

class PixabayDataSourceFactory(private val context: Context) : DataSource.Factory<Int, PhotoItem>() {
    private var _pixabayDataSource = MutableLiveData<PixabayDataSources>()
    val pixabayDataSources: LiveData<PixabayDataSources>  = _pixabayDataSource

    override fun create(): DataSource<Int, PhotoItem> {
        val pixabay = PixabayDataSources(context)
        pixabay.also {
            _pixabayDataSource.postValue(it)
        }
        return pixabay
    }
}

  3.3 修改 ViewModel,GalleryViewModel.kt 文件

class GalleryViewModel(application: Application) : AndroidViewModel(application) {
    private val factory = PixabayDataSourceFactory(application)
    val pagedListLiveData = factory.toLiveData(1)
    val networkStatus = Transformations.switchMap(factory.pixabayDataSources) {
        it.networkStatus
    }
    //重新请求
    fun resetQuery() {
       pagedListLiveData.value?.dataSource?.invalidate()
    }

    //继续调用
    fun retry(){
        factory.pixabayDataSources.value?.retry?.invoke()
    }
}

4. 图片浏览页调用,GalleryFragment.kt

class GalleryFragment : Fragment() {
    private lateinit var binding: FragmentGalleryBinding
    private lateinit var galleryAdapter: GalleryAdapter
    private val galleryViewModel by activityViewModels<GalleryViewModel>()
    private val galleryPagingViewModel by viewModels<GalleryPagingViewModel>()
    private var swipe = false

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View {
        binding = FragmentGalleryBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.swipeIndicator -> {
                binding.swipeLayoutGallery.isRefreshing = true
                Handler().postDelayed({
                    swipe = true
                    galleryViewModel.resetQuery()
                    //galleryAdapter.refresh()
                }, 500)

            }
            R.id.menuRetry -> {
                galleryViewModel.retry()
            }
        }
        return super.onOptionsItemSelected(item)
    }

    //加载菜单栏
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.menu, menu)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setHasOptionsMenu(true)
        galleryAdapter = GalleryAdapter(galleryViewModel)

        binding.recyclerView.apply {
            adapter = galleryAdapter
            //GridLayouStaager(requireContext(), 2) 对齐 StaggeredGridLayoutManager 交错感
            layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
        }
        viewModel()
        //pagingViewModel()
    }

    private fun pagingViewModel() {
       lifecycleScope.launch {
            galleryPagingViewModel.getPagingData().collect {
               // galleryAdapter.submitData(it)
                binding.swipeLayoutGallery.isRefreshing = false
            }
        }
        binding.swipeLayoutGallery.setOnRefreshListener {
            //galleryAdapter.refresh()
        }
    }

    private fun viewModel(){
        galleryViewModel.pagedListLiveData.observe(viewLifecycleOwner) {
            galleryAdapter.submitList(it)
            if (swipe) {
                binding.recyclerView.scrollToPosition(0)
                swipe = false
            }
        }
        binding.swipeLayoutGallery.setOnRefreshListener {
            galleryViewModel.resetQuery()
        }
        galleryViewModel.networkStatus.observe(viewLifecycleOwner) {
            Log.i("MyTag", "networkStatus: $it")
            galleryAdapter.updateNetworkStatus(it)
            binding.swipeLayoutGallery.isRefreshing = (it == NetworkStatus.INITIAL_LOADING)
        }
    }
}

5. 图片单张浏览,获取数据修改 PagerPhotoFragment.kt

const val REQUEST_WRITE_EXTERNAL_STORAGE = 1
class PagerPhotoFragment : Fragment() {
    private lateinit var binding: FragmentPagerPhotoBinding
    val galleryViewModel by activityViewModels<GalleryViewModel>()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View {
        binding = FragmentPagerPhotoBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
//        val photoList = arguments?.getParcelableArrayList<PhotoItem>("PHOTO_LIST")
//        PagerPhotoListAdapter().apply {
//            binding.viewPager2.adapter = this
//            submitList(photoList)
//        }

        val adapter = PagerPhotoListAdapter()
        binding.viewPager2.adapter = adapter
        galleryViewModel.pagedListLiveData.observe(viewLifecycleOwner) {
            adapter.submitList(it)
            binding.viewPager2.setCurrentItem(arguments?.getInt("PHOTO_POSITION") ?: 0, false)
        }

        binding.viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                binding.photoTag.text = getString(
                    R.string.photo_tag,
                    position + 1,
                    galleryViewModel.pagedListLiveData.value?.size
                )
            }
        })

        binding.btSave.setOnClickListener {
            if (Build.VERSION.SDK_INT < Q && ContextCompat.checkSelfPermission(
                    requireContext(), android.Manifest.permission.WRITE_EXTERNAL_STORAGE
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                requestPermissions(
                    arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE),
                    REQUEST_WRITE_EXTERNAL_STORAGE
                )
            } else {
                viewLifecycleOwner.lifecycleScope.launch {
                    savePhoto()
                }
            }
        }
    }
    //保存图片 suspend 允许挂起
    private suspend fun savePhoto() {
        //开启子线程 范围
        withContext(Dispatchers.IO) {
            val holder = (binding.viewPager2[0] as RecyclerView).findViewHolderForAdapterPosition(binding.viewPager2.currentItem) as PagerPhotoViewHolder
            val bitmap = holder.view.pagerPhoto.drawable.toBitmap()
            val saveUrl = requireContext().contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, ContentValues()) ?: kotlin.run {
                MainScope().launch {
                    Toast.makeText(requireContext(), "存储失败", Toast.LENGTH_SHORT).show()
                }
                return@withContext
            }
            requireContext().contentResolver.openOutputStream(saveUrl).use {
                if (bitmap.compress(Bitmap.CompressFormat.JPEG, 90, it)) {
                    MainScope().launch {
                        Toast.makeText(requireContext(), "存储成功", Toast.LENGTH_SHORT).show()
                    }
                } else {
                    MainScope().launch {
                        Toast.makeText(requireContext(), "存储失败", Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
    }
}

6. 效果图

         

猜你喜欢

转载自blog.csdn.net/u011193452/article/details/127263651