1. 资源说明
1.1 基于 Paging 分页加载(一)修改添加Paging 分页加载(一)https://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. 效果图