zoukankan      html  css  js  c++  java
  • Android ListView 的使用 Kotlin

    ListView的简单使用

       编辑布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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"
        android:orientation="vertical">
       <ListView
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:id="@+id/listview"/>
    </LinearLayout>

      接下来修改MainActivity代码

    class MainActivity : AppCompatActivity() {
    
        private val data= listOf("数据1","数据2","数据1","数据1","数据1","数据1","数据1","数据1","数据1","数据1")
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    //        supportActionBar?.hide()
            val adapter=ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data)
            listview.adapter=adapter
        }
    }

     可以看到这里添加了一个initFruits方法。用于初始化数据。

    其中 repeat是Kotlin相对于Java新加入的特性,取代for(int i=0;i<5;i++)用于简单的重复工作。所以这里有两个循环。呃。。不过不要在意这些细节。

    因为ListView一般是用来显示大量数据的,所以我们应该先将数据准备好。这些数据一般是从网上获取,或者读取本地数据库,但是这里为了方便就直接创建一个集合就好了。

    不过,集合中的数据无法直接传递给ListView。我们需要借助适配器来完成。

    使用simple_list_item_1只能单独显示一段数据,显然不符合我们在开发过程中的需求。因此我们需要进行对ListView的界面进行定制。

    接下来,我们定义一个水果(Fruit)实体类。

    class Fruits(val name:String,val imageId:String) {
    }

    水果类中有两个字段,名字和图片。

    然后我们为ListView 的子项指定一个自定义的布局。在Layout先新建一个fruit_item.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">
    
        <ImageView
            android:layout_width="35dp"
            android:layout_height="35dp"
            android:id="@+id/fruitImage"
            android:layout_gravity="center"
            android:layout_marginLeft="10dp"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/fruitName"
            android:layout_gravity="center_vertical"
            android:layout_margin="10dp"/>
            
    </LinearLayout>

    接下来创建一个适配器FruitAdapter

    代码如下

    class FruitAdapter(activity: Activity,val resourceId:Int,data:List<Fruits>) : ArrayAdapter<Fruits>(activity,resourceId,data){
    
        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
            val view =LayoutInflater.from(context).inflate(resourceId,parent,false)
            val fruitImage:ImageView=view.findViewById(R.id.fruitImage)//绑定布局得图片
            val fruitName:TextView=view.findViewById(R.id.fruitName)//绑定布局中得名字
            val fruit=getItem(position)//获取当前项得Fruit实例
            if (fruit!=null){
                fruitImage.setImageResource(fruit.imageId)
                fruitName.text=fruit.name
            }
            return  view
        }
    }

    在使用getView中,使用Layout Inflater来为这个子项价值我们传入得布局。Layout Inflater 的inflater()接收三个参数,第三个参数设置为false 表示只让我们在父布局中声明的layout属性生效。但不会为这个View添加父布局。因为View一旦有了父布局它就就不能添加

    到ListView中。

    最后修改MainActivity中的代码。

    class MainActivity : AppCompatActivity() {
    
       private val fruits=ArrayList<Fruits>()
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    //        supportActionBar?.hide()
            initFruits() //初始化列表数据
            val adapter=FruitAdapter(this,R.layout.fruit_item,fruits)
            listview.adapter=adapter
        }
        private fun initFruits(){
            repeat(2){
               for (i in 0..20){
                   fruits.add(Fruits("测试一",R.mipmap.ic_launcher))
               }
            }
        }
    }

      

    如何提升ListView 的运行效率。这个问题,去面试的时候经常会问到。

    目前我们的List View运行效率是很低的。因为在FruitAdapter的getView()方法中,每次都将布局重新加载一遍。当ListView快速滚动的时候,就会成为性能的瓶颈。

    getView中还有个convertView的参数。这个参数用于将之前的加载好的布局进行缓存。以便之后进行重用。我们可以借助这个参数进行性能优化。接下来修改这个适配器的代码:

    class FruitAdapter(activity: Activity,val resourceId:Int,data:List<Fruits>) : ArrayAdapter<Fruits>(activity,resourceId,data){
    
        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
            var view:View
            if (convertView==null){
                view=LayoutInflater.from(context).inflate(resourceId,parent,false)
            }else{
                view=convertView
            }
            val fruitImage:ImageView=view.findViewById(R.id.fruitImage)//绑定布局得图片
            val fruitName:TextView=view.findViewById(R.id.fruitName)//绑定布局中得名字
            val fruit=getItem(position)//获取当前项得Fruit实例
            if (fruit!=null){
                fruitImage.setImageResource(fruit.imageId)
                fruitName.text=fruit.name
            }
            return  view
        }
    }

    我们对getView中的方法进行了判断,如果converView为空,则私用LayoutInflater去加载布局,如果不为空则对convertVIew进行重用。这样可以提高ListView的效率。但是现在每次在getView中还是会调用View 中的findViewById方法来获取控件实例。

    我们可以借助一个ViewHolder来对这部分性能进行优化。继续修改FruitAdapter中代码。如下:

    class FruitAdapter(activity: Activity,val resourceId:Int,data:List<Fruits>) : ArrayAdapter<Fruits>(activity,resourceId,data){
    
        inner class ViewHolder(val fruitImage:ImageView,val fruitName:TextView)
        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
            var view:View
            val viewHolder:ViewHolder
            if (convertView==null){
                view=LayoutInflater.from(context).inflate(resourceId,parent,false)
                val fruitImage:ImageView=view.findViewById(R.id.fruitImage)//绑定布局得图片
                val fruitName:TextView=view.findViewById(R.id.fruitName)//绑定布局中得名字
                viewHolder=ViewHolder(fruitImage,fruitName)
                view.tag=viewHolder
            }else{
                view=convertView
                viewHolder=view.tag as ViewHolder
            }
    
            val fruit=getItem(position)//获取当前项得Fruit实例
            if (fruit!=null){
                viewHolder.fruitImage.setImageResource(fruit.imageId)
                viewHolder.fruitName.text=fruit.name
            }
            return  view
        }
    }

    可以看到我们新增一个ViewHolder的内部类。用于对子项布局的控件进行实例缓存。

    经过这番操作,ListView的运行效率提升了不少。

    除此之外还可以

    4.将ListView的scrollingCache和animateCache设置为false

    scrollingCache: scrollingCache本质上是drawing cache,你能够让一个View将他自己的drawing保存在cache中(保存为一个bitmap),这样下次再显示View的时候就不用重画了,而是从cache中取出。默认情况下drawing cahce是禁用的。由于它太耗内存了,可是它确实比重画来的更加平滑。

    而在ListView中,scrollingCache是默认开启的,我们能够手动将它关闭。

    animateCache: ListView默认开启了animateCache,这会消耗大量的内存,因此会频繁调用GC,我们能够手动将它关闭掉

    优化前的ListView

    <ListView
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:cacheColorHint="#00000000"
            android:divider="@color/list_background_color"
            android:dividerHeight="0dp"
            android:listSelector="#00000000"
            android:smoothScrollbar="true"
            android:visibility="gone" /> 

    优化后:

    <ListView
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:divider="@color/list_background_color"
            android:dividerHeight="0dp"
            android:listSelector="#00000000"
            android:scrollingCache="false"
            android:animationCache="false"
            android:smoothScrollbar="true"
            android:visibility="gone" />

    这个方法的原文地址 

    ListVIew的点击事件。

    使用setOnItemClickListener()方法注册一个监听器,用户点击时,回调到Lambda表达式中,通过position参数判断用户点击哪个item

    在MainActivity中修改,代码如下:

    class MainActivity : AppCompatActivity() {
    
       private val fruits=ArrayList<Fruits>()
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    //        supportActionBar?.hide()
            initFruits() //初始化列表数据
            val adapter=FruitAdapter(this,R.layout.fruit_item,fruits)
            listview.adapter=adapter
            listview.setOnItemClickListener { parent,view,position,id ->
                val fruit=fruits[position]
                Toast.makeText(this,fruit.name,Toast.LENGTH_SHORT).show()
            }
        }
        private fun initFruits(){
            repeat(2){
               for (i in 0..20){
                   fruits.add(Fruits("测试一",R.mipmap.ic_launcher))
               }
            }
        }
    }

    重新运行,就可以看到,每点击一个item,就会弹出一个Toast提升你点击的那个item的name。

    观察上面代码,我们发现声明4个参数,但我们只用了一个,这个时候我们可以将item的监听事件改写为

            listview.setOnItemClickListener { _,_ ,position,_ ->
                val fruit=fruits[position]
                Toast.makeText(this,fruit.name,Toast.LENGTH_SHORT).show()
            }

    更强大的滚动控件 RecyclerView

    recyclerView 可以轻松实现LIstView的效果,还优化lListVIew的各种不足之处。

    在app/build.gradle 中添加一行依赖,将RecyclerView添加到项目中。点击同步

    implementation 'androidx.recyclerview:recyclerview:1.1.0'

    在activity_main.xml修改

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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"
        android:orientation="vertical">
       <androidx.recyclerview.widget.RecyclerView
           android:id="@+id/recyclerView"
           android:layout_width="match_parent"
           android:layout_height="match_parent"/>
    </LinearLayout>

    这里为了方便就直接在原来代码修改了,只是将ListVIew换成RecyclerView。item项就用原来的。

    接下来,我们创建一个RecyclerView的适配器。

    class FruitAdapter2(val fruitList:List<Fruits>):RecyclerView.Adapter<FruitAdapter2.ViewHolder>(){
        inner class ViewHolder(view:View):RecyclerView.ViewHolder(view){
            val fruitImage:ImageView=view.findViewById(R.id.fruitImage)
            val fruitName:TextView=view.findViewById(R.id.fruitName)
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val view =LayoutInflater.from(parent.context).inflate(R.layout.fruit_item,parent,false)
    
            return ViewHolder(view)
        }
    
        override fun getItemCount()=fruitList.size
    
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            val fruit=fruitList[position]
            holder.fruitImage.setImageResource(fruit.imageId)
            holder.fruitName.text=fruit.name
        }
    
    }

    这里定义了一个内部类 ViewHolder,它要继承RecyclerView.ViewHolder、ViewHolder的主构造函数要传入一个View参数。这个参数通常是RecyclerView子项的最外层布局。我们可以通过findViewById来获取布局控件的Id。

      继承RecyclerView.Adapter就必须重写onCreateViewHolder()、onBindVIewHolder()和getItemCount()这三个方法。

    onCreateViewHolder() 加载布局文件,创建ViewHolder实例
    onBindVIewHolder() 对RecyclerView的子项进行赋值
    getItemCount() 直接告诉RecyclerView有多少子项,直接返回数据长度即可

    随后将MainActivity代码改成

    class MainActivity : AppCompatActivity() {
    
       private val fruits=ArrayList<Fruits>()
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    //        supportActionBar?.hide()
            initFruits() //初始化列表数据
    //        val adapter=FruitAdapter(this,R.layout.fruit_item,fruits)
    //        listview.adapter=adapter
    //        listview.setOnItemClickListener { _,_ ,position,_ ->
    //            val fruit=fruits[position]
    //            Toast.makeText(this,fruit.name,Toast.LENGTH_SHORT).show()
    //        }
            val layoutManager=LinearLayoutManager(this)
            recyclerView.layoutManager=layoutManager
            val adapter=FruitAdapter2(fruits)
            recyclerView.adapter=adapter
        }
        private fun initFruits(){
            repeat(2){
               for (i in 0..20){
                   fruits.add(Fruits("测试一",R.mipmap.ic_launcher))
               }
            }
        }
    }

    此时运行效果和ListView一样,就不放图。

    实现横向滚动和瀑布流布局

    ListVIew  只能实现纵向滚动效果,横向是做不到的,如果我们需要横向滚动就需要借助RecyclerView了。

    我们只需要将fruit_item进行修改如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="80dp"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
    
        <ImageView
            android:layout_width="35dp"
            android:layout_height="35dp"
            android:id="@+id/fruitImage"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/fruitName"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"/>
    
    </LinearLayout>

    然后在MainActivity中修改:

    class MainActivity : AppCompatActivity() {
    
       private val fruits=ArrayList<Fruits>()
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    //        supportActionBar?.hide()
            initFruits() //初始化列表数据
    //        val adapter=FruitAdapter(this,R.layout.fruit_item,fruits)
    //        listview.adapter=adapter
    //        listview.setOnItemClickListener { _,_ ,position,_ ->
    //            val fruit=fruits[position]
    //            Toast.makeText(this,fruit.name,Toast.LENGTH_SHORT).show()
    //        }
            val layoutManager=LinearLayoutManager(this)
            recyclerView.layoutManager=layoutManager
            layoutManager.orientation=LinearLayoutManager.HORIZONTAL
            val adapter=FruitAdapter2(fruits)
            recyclerView.adapter=adapter
        }
        private fun initFruits(){
            repeat(2){
               for (i in 0..20){
                   fruits.add(Fruits("测试一",R.mipmap.ic_launcher))
               }
            }
        }
    }
     layoutManager.orientation=LinearLayoutManager.HORIZONTAL

    可以看到这里只添加了一行就实现了,效果如下:

    那么接下来实现瀑布流布局(瀑布流的效果好像多用于短视频一类的比较多),还是来改fruit_item.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="wrap_content"
        android:orientation="vertical"
        android:layout_margin="5dp"
        >
    
        <ImageView
            android:layout_width="35dp"
            android:layout_height="35dp"
            android:id="@+id/fruitImage"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/fruitName"
            android:layout_gravity="left"
            android:layout_marginTop="10dp"/>
    
    </LinearLayout>

    瀑布流布局应该是根据布局宽度来进行适配2,而不是固定值,所以将80dp改为match_parent。

    将MainActive改为

    class MainActivity : AppCompatActivity() {
    
       private val fruits=ArrayList<Fruits>()
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    //        supportActionBar?.hide()
            initFruits() //初始化列表数据
    //        val adapter=FruitAdapter(this,R.layout.fruit_item,fruits)
    //        listview.adapter=adapter
    //        listview.setOnItemClickListener { _,_ ,position,_ ->
    //            val fruit=fruits[position]
    //            Toast.makeText(this,fruit.name,Toast.LENGTH_SHORT).show()
    //        }
            val layoutManager=StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL)
            recyclerView.layoutManager=layoutManager
    //        layoutManager.orientation=LinearLayoutManager.HORIZONTAL
            val adapter=FruitAdapter2(fruits)
            recyclerView.adapter=adapter
        }
        private fun initFruits(){
            repeat(2){
               for (i in 0..20){
                   fruits.add(Fruits(getRandomLengthString("测试文字"),R.mipmap.ic_launcher))
               }
            }
        }
        private fun getRandomLengthString(str:String):String{
            val n=(1..20).random()
            val builder=StringBuilder()
            repeat(n){
                builder.append(str)
            }
            return builder.toString()
        }
    }

    gitee地址

  • 相关阅读:
    比原空投问答题库题解(一)
    RXD, tree and sequence IN HDU6065
    python爬虫——对爬到的数据进行清洗的一些姿势(5)
    hdu 5934 Bomb
    HDU 3360 National Treasures
    HDU 1845 Jimmy’s Assignment
    HDU 1528 Card Game Cheater
    HDU 1507 Uncle Tom's Inherited Land*
    HDU 1281 棋盘游戏
    WampServer出现You don’t have permission to access/on this server提示
  • 原文地址:https://www.cnblogs.com/inthecloud/p/13252492.html
Copyright © 2011-2022 走看看