Gallery 简易图片浏览

1. build.gradle, AndroidManifast.xml, 配置文件添加引用

  1.1 dependencies 中添加引用库

dependencies {
    //http 请求
    implementation 'com.android.volley:volley:1.2.1'
    //上拉刷新
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
    //加载网络地址图片
    implementation 'com.github.bumptech.glide:glide:4.13.2'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.13.2'
    def nav_version = "2.5.2"
    //Kotlin navigation 导航
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
    //gson 序列化
    implementation 'com.google.code.gson:gson:2.9.1'
    //加载图片时动画效果
    implementation 'io.supercharge:shimmerlayout:2.1.0'
    //图片缩放操作
    implementation 'com.github.chrisbanes:PhotoView:2.3.0'
}

  1.2 添加 com.github.chrisbanes:PhotoView:2.3.0 库,settings.gradle 需要添加引用

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        //添加引用
        maven { url "https://www.jitpack.io" }
    }
}

  1.3 视图与类绑定

android {
    buildFeatures {
        viewBinding = true
    }
}

  1.4 添加 @Parcelize 注解, 不用实现 Parcelable 里的方法

plugins {
    id 'kotlin-parcelize'
}

  1.5 AndroidManifest.xml 添加网络权限

   <uses-permission android:name="android.permission.INTERNET" />

   <application  android:usesCleartextTraffic="true">

  1.6 占位图 ic_baseline_photo_24.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:tint="#D8D9D9"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
</vector>

  1.7 占位图 ic_photo_place_holder.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <solid android:color="#99999999" />
            <size android:width="40dp" android:height="30dp" />
        </shape>
    </item>
</selector>

  1.8 占位图喜欢 ic_baseline_thumb_up.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="12dp"
    android:height="12dp"
    android:tint="#F50606"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M1,21h4L5,9L1,9v12zM23,10c0,-1.1 -0.9,-2 -2,-2h-6.31l0.95,-4.57 0.03,-0.32c0,-0.41 -0.17,-0.79 -0.44,-1.06L14.17,1 7.59,7.59C7.22,7.95 7,8.45 7,9v10c0,1.1 0.9,2 2,2h9c0.83,0 1.54,-0.5 1.84,-1.22l3.02,-7.05c0.09,-0.23 0.14,-0.47 0.14,-0.73v-2z" />
</vector>

  1.9 占位图收藏 ic_baseline_favorite.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="12dp"
    android:height="12dp"
    android:tint="#F50606"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z" />
</vector>

2. 创建 Gallery 主页

  2.1 布局文件 fragment_gallery.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/swipeLayoutGallery"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="2dp"
    tools:context=".GalleryFragment">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

  2.2 创建实体类,存放字段 Pixabay.kt

data class Pixabay(
    val total: Int,
    val totalHits: Int,
    val hits: Array<PhotoItem>
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false
        other as Pixabay
        if (total != other.total) return false
        if (totalHits != other.totalHits) return false
        if (!hits.contentEquals(other.hits)) return false
        return true
    }

    override fun hashCode(): Int {
        var result = total
        result = 31 * result + totalHits
        result = 31 * result + hits.contentHashCode()
        return result
    }
}

//@Parcelize:注解 不用实现 Parcelable 里的方法
@Parcelize data class PhotoItem(
    @SerializedName("id") val photoId: Int,
    @SerializedName("webformatURL") val previewUrl: String,
    @SerializedName("largeImageURL") val fullUrl: String,
    @SerializedName("webformatHeight") val photoHeight: Int,
    @SerializedName("user") val photoUser:String,
    @SerializedName("likes") val photoLikes: Int,
    @SerializedName("collections") val photoFavorites: Int
):Parcelable

  2.3 创建 RecyclerView 子 View,gallery_cell.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="2dp"
    app:cardCornerRadius="2dp">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_user"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:text="TextView"
            android:textSize="14sp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/shimmerLayoutCell" />

        <ImageView
            android:id="@+id/imageView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_user"
            app:srcCompat="@drawable/ic_baseline_thumb_up" />

        <TextView
            android:id="@+id/tv_likes"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:text="TextView"
            android:textSize="10sp"
            app:layout_constraintBottom_toBottomOf="@+id/imageView2"
            app:layout_constraintStart_toEndOf="@+id/imageView2"
            app:layout_constraintTop_toTopOf="@+id/imageView2" />

        <ImageView
            android:id="@+id/imageView3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            app:layout_constraintBottom_toBottomOf="@+id/tv_likes"
            app:layout_constraintStart_toEndOf="@+id/tv_likes"
            app:layout_constraintTop_toTopOf="@+id/tv_likes"
            app:srcCompat="@drawable/ic_baseline_favorite" />

        <TextView
            android:id="@+id/tv_favorites"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:text="TextView"
            android:textSize="10sp"
            app:layout_constraintBottom_toBottomOf="@+id/imageView3"
            app:layout_constraintStart_toEndOf="@+id/imageView3"
            app:layout_constraintTop_toTopOf="@+id/imageView3" />

        <io.supercharge.shimmerlayout.ShimmerLayout
            android:id="@+id/shimmerLayoutCell"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <ImageView
                android:id="@+id/imageView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:adjustViewBounds="true"
                android:scaleType="centerCrop"
                tools:srcCompat="@tools:sample/avatars" />
        </io.supercharge.shimmerlayout.ShimmerLayout>
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.cardview.widget.CardView>

2.4 创建 RecyclerView 适配器, GalleryAdapter.kt

class GalleryAdapter: ListAdapter<PhotoItem, MyViewHolder>(DIFFCALLBACK) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val holder = MyViewHolder(
            GalleryCellBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        )
        holder.itemView.setOnClickListener {
            Bundle().apply {
                //putParcelable("PHOTO", getItem(holder.adapterPosition))
                //holder.itemView.findNavController().navigate(R.id.action_galleryFragment_to_photoFragment,this)
                //val list = ArrayList(snapshot())
                putParcelableArrayList("PHOTO_LIST", ArrayList(currentList))
                //putParcelableArrayList("PHOTO_LIST", ArrayList(snapshot()))
                putInt("PHOTO_POSITION", holder.adapterPosition)
                holder.itemView.findNavController().navigate(R.id.action_galleryFragment_to_pagerPhotoFragment, this)
            }
        }
        return holder
    }
    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {

        val photoItem = getItem(position)?: return
        val cellBinding = holder.binding as GalleryCellBinding
        with(cellBinding){
            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(holder.itemView)
            .load(getItem(position)?.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 { cellBinding.shimmerLayoutCell.stopShimmerAnimation() }
                }
            })
            .into(cellBinding.imageView)
    }

    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 MyViewHolder(binding: ViewBinding) : ViewHolder(binding.root) {
    val binding = binding
}

  2.5 封装网络请求类,VolleySingleton.kt

//单例模式
class VolleySingleton private constructor(context: Context) {
    companion object {
        private var INSTANCE: VolleySingleton? = null
        fun getInstance(context: Context) =
            INSTANCE ?: synchronized(this) {
                VolleySingleton(context).also { INSTANCE = it }
            }
    }

    val requestQueue: RequestQueue by lazy {
        Volley.newRequestQueue(context.applicationContext)
    }
}

  2.6 创建菜单栏布局,刷新图片内容 menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/swipeIndicator"
        android:title="swipe to refresh" />
</menu>

  2.7 创建 ViewModel 监听数据,GalleryViewModel.kt

class GalleryViewModel(application: Application) : AndroidViewModel(application) {
    private val TAG = GalleryViewModel::class.java.simpleName
    private var _photoListLive = MutableLiveData<List<PhotoItem>>()
    val photoListLive: LiveData<List<PhotoItem>>
        get() = _photoListLive

    fun fetchData() {
        val stringRequest = StringRequest(
            Request.Method.GET,
            getUrl(),
            {
                _photoListLive.value = Gson().fromJson(it, Pixabay::class.java).hits.toList();
            },
            {
                Log.e(TAG, "onErrorResponse", it)
            }
        )
        VolleySingleton.getInstance(getApplication()).requestQueue.add(stringRequest)
    }

    private fun getUrl(): String {
        //&per_page=100
        return "https://pixabay.com/api/?key=30070990-cfc31c9f778ceeef4009d910d&q=${keyWords.random()}"
    }
    
    private val keyWords =
        arrayOf("cat", "dog", "car", "beauty", "phone", "computer", "flower", "animal")
}

  2.8 主页调用 GalleryFragment.kt

class GalleryFragment : Fragment() {
    private lateinit var binding: FragmentGalleryBinding
    private lateinit var galleryViewModel: GalleryViewModel

    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(Runnable { galleryViewModel.fetchData() }, 700)
            }
        }
        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)
        val galleryAdapter = GalleryAdapter()
        binding.recyclerView?.apply {
            adapter = galleryAdapter
            //GridLayouStaager(requireContext(), 2) 对齐 / StaggeredGridLayoutManager 交错感
            layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
        }
        //,ViewModelProvider.AndroidViewModelFactory(requireActivity().application)
        galleryViewModel = ViewModelProvider(this)[GalleryViewModel::class.java]
        galleryViewModel.photoListLive.observe(viewLifecycleOwner, Observer {
            galleryAdapter.submitList(it)
            binding.swipeLayoutGallery.isRefreshing = false
        })
        galleryViewModel.photoListLive.value ?: galleryViewModel.fetchData()

        binding.swipeLayoutGallery?.setOnRefreshListener {
            galleryViewModel.fetchData()
        }
    }
}

3. Photo 详情页

  3.1 布局文件 fragment_photo.xml

<?xml version="1.0" encoding="utf-8"?>
<io.supercharge.shimmerlayout.ShimmerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/shimmerLayoutPhoto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".PhotoFragment" >
    <com.github.chrisbanes.photoview.PhotoView
        android:id="@+id/photoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:adjustViewBounds="true"
        tools:src="@tools:sample/avatars" />
</io.supercharge.shimmerlayout.ShimmerLayout>

  3.2 调用类,PhotoFragment.kt

class PhotoFragment : Fragment() {
    private lateinit var binding: FragmentPhotoBinding

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.shimmerLayoutPhoto.apply {
            setShimmerColor(0x55FFFFFF)
            setShimmerAngle(0)
            startShimmerAnimation()
        }
        val item = arguments?.getParcelable<PhotoItem>("PHOTO")
        Glide.with(requireContext()).load(item?.fullUrl)
            .placeholder(R.drawable.ic_baseline_photo_24)
            .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.shimmerLayoutPhoto.stopShimmerAnimation() }
                }
            }).into(binding.photoView)
    }
}

4. Activity 调用

  4.1 Fragment 导航布局, navigation.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"
    app:startDestination="@id/galleryFragment">
    <fragment
        android:id="@+id/galleryFragment"
        android:name="com.example.gallery.GalleryFragment"
        android:label="Gallery"
        tools:layout="@layout/fragment_gallery">
        <action
            android:id="@+id/action_galleryFragment_to_photoFragment"
            app:destination="@id/photoFragment" />
    </fragment>
    <fragment
        android:id="@+id/photoFragment"
        android:name="com.example.gallery.PhotoFragment"
        android:label="Photo"
        tools:layout="@layout/fragment_photo" />
</navigation>

  4.2 布局文件 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragmentContainerView"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>

  4.3 调用页 MainActivity.kt

class MainActivity : AppCompatActivity() {
    private lateinit var controller:NavHostFragment
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //添加返回按键
        controller = supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
        NavigationUI.setupActionBarWithNavController(this,controller.navController)
    }
    
    //返回按键监听
    override fun onSupportNavigateUp(): Boolean {
        return super.onSupportNavigateUp()||controller.navController.navigateUp()
    }
}

5. 效果图

    

猜你喜欢

转载自blog.csdn.net/u011193452/article/details/127006679
今日推荐