Android中构建多视图 RecyclerView的正确打开方式

news2024/12/26 10:38:00

Android中构建多视图 RecyclerView的正确打开方式

简介

漂亮的UI能极大提高用户留存率,相反糟糕的UI将导致App安装率下降。
漂亮的UI设计
UI体验对用户留存率有特别大的影响,较差的体验app我可能用不了2s就要卸载掉。


你需要学习内容如下:

  1. 使用单个RecyclerView来处理多种视图类型
  2. 整洁的代码 - MVVM架构
  3. 显示来自外部API的数据
  4. 使用Motion Layout进行动画

完整的代码:
https://github.com/ibrajix/NftApp

DATA

我们使用了一个外部API,我通过https://mockapi.io/ 进行了模拟。

1、2、3和4是上图显示的布局的一部分,可以是动态的。因此,我们将为每个创建一个布局项。请查看完整代码以获取各种RecyclerView布局文件。

NftData.kt

sealed class NftData {

    class Title(
        val id: Int,
        val title: String,
        val viewAll: String,
    ) : NftData()

    class Featured(
        val image: String,
        val title: String
    ) : NftData()

    class Top(
        val id: Int,
        val image: String
    ) : NftData()

    class Trending(
        val id: Int,
        val image: String,
        val name: String,
        val category: String
    ) : NftData()

}

RETROFIT

我们正在使用retrofit来向外部 API 发送请求。
ApiService.kt

interface ApiService {

    //get top nft
    @GET(EndPoints.TOP_NFT)
    suspend fun getTopNft() : List<NftData.Top>

    //get trending nft
    @GET(EndPoints.TRENDING_NFT)
    suspend fun getTrendingNft() : List<NftData.Trending>

}

ApiDataSource.kt

class ApiDataSource @Inject constructor(private val apiService: ApiService) {

    //get top nft
    suspend fun getTopNft() = apiService.getTopNft()

    //get trending nft
    suspend fun getTrendingNft() = apiService.getTrendingNft()

}

Repository

正如你所看到的,我正在使用Hilt进行依赖注入,以注入所需的类。请查看NetworkModule.kt的完整代码,了解我如何提供所需的Retrofit类和依赖项。

NftRepository.kt

class NftRepository @Inject constructor(private val apiDataSource: ApiDataSource) : SafeApiCall {

    suspend fun getTopNft() = safeApiCall { apiDataSource.getTopNft() }
    suspend fun getTrendingNft() = safeApiCall { apiDataSource.getTrendingNft() }

}

Recyclerview

一个RecyclerView需要一个ViewHolder和一个Adapter。这个应用程序由一个单独的RecyclerView组成,请检查activity_main.xml文件。

NftViewHolder.kt

  • 这将是一个密封类,因为我们希望对继承有更多控制。
  • 我们将使用viewBinding与每个RecyclerView布局文件进行交互。
  • 我使用一个叫做coil的库从外部API中加载图像,同时还提供了一些转换(例如圆角等)(再见Glide)。
sealed class NftViewHolder(binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) {

    var itemClickListener: ((view: View, item: NftData, position: Int) -> Unit)? = null

    class TitleViewHolder(private val binding: RcvLytTitleBinding) : NftViewHolder(binding){
        fun bind(title: NftData.Title) {
            binding.txtFeatured.text = title.title
            binding.txtViewAll.text = title.viewAll
            binding.txtViewAll.setOnClickListener {
                itemClickListener?.invoke(it, title, adapterPosition)
            }
        }
    }

    class FeaturedViewHolder(private val binding: RcvLytFeaturedBinding) : NftViewHolder(binding){
        fun bind(featured: NftData.Featured){
            binding.imgFeatured.load(FEATURED_IMAGE){
                crossfade(true)
                transformations(RoundedCornersTransformation(20F))
            }
            binding.imgFeatured.setOnClickListener {
                itemClickListener?.invoke(it, featured, adapterPosition)
            }
            binding.txtFeaturedTitle.text = FEATURED_IMAGE_TITLE
        }
    }

    class TopPicksViewHolder(private val binding: RcvLytTopPicksBinding) : NftViewHolder(binding){
        fun bind(topPicks: NftData.Top){
          binding.imgTopPicks.load(topPicks.image){
             crossfade(true)
             transformations(RoundedCornersTransformation(20F))
          }
          binding.imgTopPicks.setOnClickListener {
              itemClickListener?.invoke(it, topPicks, adapterPosition)
          }
        }
    }

    class TrendingViewHolder(private val binding: RcvLytTrendingBinding) : NftViewHolder(binding){
        fun bind(trending: NftData.Trending){

            binding.imgTrending.load(trending.image){
                crossfade(true)
                transformations(CircleCropTransformation())
            }

            binding.topNftContainer.setOnClickListener {
                itemClickListener?.invoke(it, trending, adapterPosition)
            }

            binding.txtNftTitle.text = trending.name
            binding.txtCategory.text = trending.category
        }
    }

}

NftAdapter.kt

  • 我们的适配器继承ListAdapter类,这是现在推荐的方法。
  • 我们使用DiffUtil来避免使用recyclerview的adapter的notifyDataSetChanged(),因为当可能只有几件事情发生变化时,它重新绘制整个UI是不高效的。
  • onCreateViewHolder():检查存在的视图类型并填充相应的布局文件。
  • onBindViewHolder():根据viewHolder与视图绑定数据。
  • getItemViewType():如名称所示,在recyclerview中确定在特定位置显示哪种类型的视图。

NftAdapter.kt

class NftAdapter : ListAdapter<NftData, NftViewHolder>(NftDiffCallBack()) {

    var itemClickListener: ((view: View, item: NftData, position: Int) -> Unit)? = null


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NftViewHolder {
       return when(viewType){
            R.layout.rcv_lyt_title -> NftViewHolder.TitleViewHolder(
                RcvLytTitleBinding.inflate(LayoutInflater.from(parent.context), parent, false
                )
            )
           R.layout.rcv_lyt_featured -> NftViewHolder.FeaturedViewHolder(
               RcvLytFeaturedBinding.inflate(LayoutInflater.from(parent.context), parent, false
               )
           )
           R.layout.rcv_lyt_top_picks -> NftViewHolder.TopPicksViewHolder(
               RcvLytTopPicksBinding.inflate(LayoutInflater.from(parent.context), parent, false
               )
           )
           R.layout.rcv_lyt_trending -> NftViewHolder.TrendingViewHolder(
               RcvLytTrendingBinding.inflate(LayoutInflater.from(parent.context), parent, false
               )
           )
           else -> throw IllegalArgumentException("Invalid view type")
       }
    }

    override fun onBindViewHolder(holder: NftViewHolder, position: Int) {

        holder.itemClickListener = itemClickListener

        val item = getItem(position)
        when(holder){
            is NftViewHolder.FeaturedViewHolder -> holder.bind(item as NftData.Featured)
            is NftViewHolder.TitleViewHolder -> holder.bind(item as NftData.Title)
            is NftViewHolder.TopPicksViewHolder -> holder.bind(item as NftData.Top)
            is NftViewHolder.TrendingViewHolder -> holder.bind(item as NftData.Trending)
        }
    }

    override fun getItemViewType(position: Int): Int {
        return when(getItem(position)){
            is NftData.Title -> R.layout.rcv_lyt_title
            is NftData.Featured -> R.layout.rcv_lyt_featured
            is NftData.Top -> R.layout.rcv_lyt_top_picks
            is NftData.Trending -> R.layout.rcv_lyt_trending
        }
    }

    class NftDiffCallBack : DiffUtil.ItemCallback<NftData>(){

        override fun areItemsTheSame(oldItem: NftData, newItem: NftData): Boolean {
            return when {
                oldItem is NftData.Top && newItem is NftData.Top -> {
                    oldItem.id == newItem.id
                }
                oldItem is NftData.Trending && newItem is NftData.Trending -> {
                    oldItem.id == newItem.id
                }
                else -> {
                    false
                }
            }
        }

        override fun areContentsTheSame(oldItem: NftData, newItem: NftData): Boolean {
            return when {
                oldItem is NftData.Top && newItem is NftData.Top -> {
                    oldItem == newItem
                }
                oldItem is NftData.Trending && newItem is NftData.Trending -> {
                    oldItem == newItem
                }
                else -> {
                    false
                }
            }
        }

    }
    
}

UI

NftViewModel.kt

使用状态流(state flow)我们可以获得一个可观测的流,从数据源中发出当前和新的状态更新。

@HiltViewModel
class NftViewModel @Inject constructor(private val nftRepository: NftRepository) : ViewModel() {

    private val _nft = MutableStateFlow<Resource<List<NftData>>>(Resource.Loading)
    val nft: StateFlow<Resource<List<NftData>>> get() = _nft

    init {
        getNft()
    }

    private fun getNft() = viewModelScope.launch {

        _nft.emit(Resource.Loading)

        val topNftDeferred = async { nftRepository.getTopNft() }
        val trendingNftDeferred = async { nftRepository.getTrendingNft() }

        val topNft = topNftDeferred.await()
        val trendingNft = trendingNftDeferred.await()

        val nftList = mutableListOf<NftData>()

        if(topNft is Resource.Success && trendingNft is Resource.Success){
            nftList.add(NftData.Title(1, "Featured", ""))
            nftList.add(NftData.Featured(FEATURED_IMAGE, FEATURED_IMAGE_TITLE))
            nftList.add(NftData.Title(2, "Top Pick", "View all"))
            nftList.addAll(topNft.value)
            nftList.add(NftData.Title(2, "Trending", ""))
            nftList.addAll(trendingNft.value)
            _nft.emit(Resource.Success(nftList))
        }else{
            Resource.Failure(false, null, null)
        }

    }

}

MainActivity.kt

我们设置了我们的RecyclerView布局管理器,并在UI层使用了推荐的新方法来收集流。

 //set up recycler view
        binding.rcvNft.apply {

            val gridLayoutManager = GridLayoutManager(this@MainActivity, 6)
            gridLayoutManager.spanSizeLookup = object : SpanSizeLookup() {
                override fun getSpanSize(position: Int): Int {
                    return when (nftAdapter.getItemViewType(position)) {
                        R.layout.rcv_lyt_title -> 6
                        R.layout.rcv_lyt_featured -> 6
                        R.layout.rcv_lyt_top_picks -> 3
                        R.layout.rcv_lyt_trending -> 6
                        else -> 1
                    }
                }
            }

            layoutManager = gridLayoutManager
            setHasFixedSize(true)
            adapter = nftAdapter

        }

        //handle clicks
        nftAdapter.itemClickListener = { view, item, position ->

            when(item) {
                is NftData.Title -> Toast.makeText(this, "View all clicked", Toast.LENGTH_LONG).show()
                is NftData.Featured -> Toast.makeText(this, "Featured nft clicked", Toast.LENGTH_LONG).show()
                is NftData.Top -> Toast.makeText(this, "Top nft clicked", Toast.LENGTH_LONG).show()
                is NftData.Trending -> Toast.makeText(this, "Trending nft clicked", Toast.LENGTH_LONG).show()
            }

        }

        //best way to collect flows in UI layer
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                nftViewModel.nft.collect{ result ->
                    when (result) {
                        Resource.Loading ->  binding.loading.changeVisibility(View.VISIBLE)
                        is Resource.Failure -> {
                            binding.loading.changeVisibility(View.GONE)
                        }
                        is Resource.Success -> {
                            binding.loading.changeVisibility(View.GONE)
                            nftAdapter.submitList(result.value)
                        }
                    }

                }
            }
        }

GitHub

https://github.com/ibrajix/NftApp

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/702058.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32F407开发板DS18B20应用案例

【1】DS18B20介绍 DS18B20是一种数字温度传感器&#xff0c;由Maxim Integrated公司生产。它采用单总线接口&#xff0c;能够在广泛的温度范围内测量温度&#xff0c;并通过数字方式输出温度值&#xff0c;具有较高的精度和稳定性。 以下是DS18B20温度传感器的主要特点和操作…

拯救者Lenovo Legion Y9000X IAH7 2022款(82TF)原装出厂Windows11系统恢复原厂OEM系统

Lenovo联想拯救者笔记本电脑 Legion Y9000X IAH7 2022款(82TF)出厂状态原装Win11系统&#xff0c;恢复原厂系统 系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;16G或以上的U盘 文件格式&#xff1a;ISO 文件大小&am…

人类最新版去水印+外卖CPS小程序源码+独立后台微擎模块

最新版去水印外卖CPS小程序源码 本版本为目前最新版本 修复上个版本后台用户列表加载失败问题 新增轮播图跳转小程序 新增外卖CPS系统

layui学习

官网&#xff1a;Layui镜像站-经典开源模块化前端 UI 框架(官方文档完整镜像) 下载&#xff1a;可以在首页进行下载 快速入门Layui | 枫桥夜泊 如果不知道样式在哪个模块下&#xff0c;引入总的核心样式文件&#xff1b;如果知道样式在哪个模块下&#xff0c;直接引入module…

线程间通信

1、需求 现在两个线程操作一个初始值为0的变量实现一个线程对变量增加1&#xff0c;一个线程对变量减少1交替&#xff0c;来10轮 2、多线程编程模板中 1&#xff09;判断 2&#xff09;干活 3&#xff09;通知 线程间通信&#xff1a; 1.生产者消费者 2.通知等待唤醒机制 3…

Aski AI: 基于人工智能的在线AI工具平台

【产品介绍】 Aski AI是一个基于人工智能的在线AI工具平台&#xff0c;它可以帮助用户快速、准确、全面地解决各种问题。无论是学习、工作、生活、娱乐还是其他领域&#xff0c;只要输入你的问题&#xff0c;Aski AI就会为你提供最合适的答案。此外还提供AI文章写作&#xff0c…

BHQ1 Mal,BHQ2 Maleimide,BHQ3 Mal,马来酰亚胺修饰的BHQ试剂有哪些特点?

一、BHQ-1 Maleimide 产品描述&#xff1a; BHQ-1 Maleimide黑洞猝灭剂-1(BHQ-1)被归类为暗猝灭剂&#xff0c;该淬灭剂能够将一定距离内荧光基团发出的光全部吸收&#xff0c;实现对荧光信号的淬灭&#xff0c;所以可得到更强的特异性&#xff0c;更优化的信噪比。 中文名&a…

【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

第四十五章 FLASH模拟EEPROM实验 STM32本身没有自带EEPROM&#xff0c;但是STM32具有IAP&#xff08;在应用编程&#xff09;功能&#xff0c;所以我们可以把它的FLASH当成EEPROM来使用。本章&#xff0c;我们将利用STM32内部的FLASH来实现第三十六章实验类似的效果&#xff0…

设计模式第18讲——中介者模式(Mediator)

目录 一、什么是中介者模式 二、角色组成 三、优缺点 四、应用场景 4.1 生活场景 4.2 java场景 五、代码实现 5.0 代码结构 5.1 抽象中介者&#xff08;Mediator&#xff09;——LogisticsCenter 5.2 抽象同事类&#xff08;Colleague&#xff09;——Participant 5…

nvm安装node

使用 Windows 系统的我选择使用其推荐的 nvm-windows 来管理 Node.js 版本。 在安装 nvm-windows 前&#xff0c;如果你的电脑中已经安装了 Node.js&#xff0c;那么可以选择卸载&#xff0c;也可以选择不卸载。因为在安装 nvm-windows 的过程中其会询问你是否需要将已安装的 N…

mysql数据库迁移到kingbase人大金仓

1. 启动数据迁移工具 2. 浏览器打开网址[http://localhost:8080/]进入可视化操作界面&#xff0c;在源数据库添加人大金仓数据库信息&#xff0c;测试成功后保存 3.在目标数据库填写需要同步的mysql数据库&#xff0c;添加对应的mysql数据库信息&#xff0c;测试成功后保存 4.在…

The Company Requires Superficial StudyPHP 打开执行PHP ②

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; PHP MYSQL &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f44…

自制游戏引擎

这是一个玩具 1. 引擎使用流程 SmallEngine是引擎的核心模块,封装渲染功能和场景管理功能等Editor是编辑器,类似unity和ue编辑器,能够动态添加对象和组件Sandbox是游戏播放器,能够运行游戏 2. SmallEngine 参考 https://www.bilibili.com/video/BV1KE41117BD/?spm_id_from333…

ARM实验-ARM主程序调用ARM/C语言子程序

一、实验名称&#xff1a;ARM主程序调用ARM/C语言子程序 二、实验目的&#xff1a; 了解ARM应用程序框架。了解ARM汇编程序函数和C语言程序函数相互调用时&#xff0c;遵循的ATPCS标准&#xff1b;了解和掌握ARM汇编程序调用C语言程序函数的基本方法&#xff1b;了解和掌握AR…

操作系统第4章 文件系统 知识点

UNIX系统不存一些具体的指针了 只存文件名和指向i结点的指针 这个删除和截断有什么区别 目录本来放在外存的&#xff0c;有文件打开表&#xff0c;从外存复制到内存的文件打开表中&#xff0c;用户想继续读的时候&#xff0c;不用再去外存搜索目录 访问文件打开表的索引叫文…

基于“SRP模型+”多技术融合在生态环境脆弱性评价模型构建、时空格局演变分析与RSEI 指数的生态质量评价及拓展应用

近年来&#xff0c;国内外学者在生态系统的敏感性、适应能力和潜在影响等方面开展了大量的生态脆弱性研究&#xff0c;他们普遍将生态脆弱性概念与农牧交错带、喀斯特地区、黄土高原区、流域、城市等相结合&#xff0c;评价不同类型研究区的生态脆弱特征&#xff0c;其研究内容…

大数据Doris(五十二):Doris数据导出案例和注意事项

文章目录 Doris数据导出案例和注意事项 一、Doris数据导出到HDFS案例 1、创建Doris表并插入数据 2、创建Export ,数据导出到 HDFS 3、查看任务 4、查看导出结果 二、Doris数据导出到本地案例 1、配置 fe.conf 2、Doris 数据导出到本地 三、注意事项 Doris数据导出案例…

kettle源码远程debug调试

一、kettle启动时指定debug端口号 windows下&#xff0c;修改bat执行文件&#xff0c;同理 linux修改sh执行文件 在java执行参数的末尾添加debug参数 address为debug端口 -Xdebug -Xnoagent -Djava.compilerNONE -Xrunjdwp:transportdt_socket,servery,suspendn,address9080然…

从渲染流程、数据处理结构聊聊Flutter性能优化

不可否认 Flutter 是一个非常强大的移动应用开发框架&#xff0c;我们在技术架构选型时就是选用的 Flutter&#xff0c;特别是跨端能力属实很优秀&#xff0c;but 也逐渐发现在复杂的应用程序实现中&#xff0c;App 的性能会受到一些影响。 其实这个问题&#xff0c;我们内部也…

SpringCloud入门实战(九)- SpringCloud Config配置中心

&#x1f4dd; 学技术、更要掌握学习的方法&#xff0c;一起学习&#xff0c;让进步发生 &#x1f469;&#x1f3fb; 作者&#xff1a;一只IT攻城狮 &#xff0c;关注我&#xff0c;不迷路 。 &#x1f490;学习建议&#xff1a;1、养成习惯&#xff0c;学习java的任何一个技术…