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地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM