如何使用 lambda 而不是 kotlin 接口

Posted

技术标签:

【中文标题】如何使用 lambda 而不是 kotlin 接口【英文标题】:How to use a lambda instead of a kotlin interface 【发布时间】:2019-06-21 19:43:27 【问题描述】:

我在 android 中有一个回收器视图适配器。我的适配器类的一部分如下所示:

private lateinit var itemLongClick: ItemLongClick

override fun onCreateViewHolder(parent: ViewGroup, a: Int): RecyclerAdapter.ViewHolder 

      // Define And Initialize The Custom View And Its Holder//
      val myView = LayoutInflater.from(parent.context).inflate(customLayout, parent, false)
      val viewHolder = ViewHolder(myView)

      // What Happens When A List Item Is Long Clicked//
      myView.setOnLongClickListener  view ->

          // Differ Action To Class Instance//
          itemLongClick.longClicked(context, viewHolder.layoutPosition, view)

          // End Function//
          true
      

      // Returns The Custom View//
      return viewHolder


fun setItemLongClick(itemLongClick: ItemLongClick) 

    // Sets The Value For this.itemLongClick//
    this.itemLongClick = itemLongClick
    

我创建了一个界面,看起来像这样:

interface ItemLongClick 

    // Function Declaration For When An Item Is Long Clicked//
    fun longClicked(context: Context, position: Int, view: View)

我不想在适配器类中编写我的长按代码,而是想将它与调用适配器类的活动区分开来。我知道这样做的一种方法是制作一个 kotlin 接口,然后像这样在另一个类中调用它

  userAdapter.setItemLongClick(object: ItemLongClick 
        override fun longClicked(context: Context, position: Int, view: View) 

        
    )

但这看起来很乱。我知道 java 接口可以与 SAM 一起使用,但我也不想这样做。我想要的是将 onLongClick 设为 Lambda,但我不确定如何设置 Kotlin lambda 表达式以使其正常工作,而且我在任何地方都找不到好的示例。

提前致谢

【问题讨论】:

【参考方案1】:

你有两个选择:

1.) 用 typealias 替换接口

typealias ItemLongClick = (Context, Int, View) -> Unit

2.) 添加一个扩展函数,用于将接口设置为 lambda 而不是匿名对象

inline fun UserAdapter.setItemLongClick(crossinline longClick: (Context, Int, View) -> Unit) 
    setItemLongClick(object: ItemLongClick 
        override fun longClicked(context: Context, position: Int, view: View) 
            longClick(context, position, view)
        
    )

现在你可以打电话了

userAdapter.setItemLongClick  context, position, view -> 
    ...

【讨论】:

【参考方案2】:

我有一个适配器,我需要根据开关更改数据,我做了这样的事情:

ListAdapter(private val context: Context, private val switchListener: (Boolean) -> Unit)

然后我绑定了我的分段列表的标题:

private fun bindHeader(holder: HeaderViewHolder) 
        holder.switch.setOnCheckedChangeListener  _, isChecked ->
            callbackSwitchListener(isChecked)
        
    

在我的片段中:

private fun setupRecyclerView() 
        fabricationDataListAdapter =
                FabricationDataListAdapter(context!!)  isChecked: Boolean -> switchControl(isChecked) 
        val layoutManager = ListLayoutManager(context!!)
        this.recycler_view_all.layoutManager = layoutManager
        this.recycler_view_all.adapter = fabricationDataListAdapter
    

有趣的 switchControl 根据布尔值更改数据的地方。

我不确定这是否是你需要的,我有点着急,但如果我没记错的话,这在 kotlin 中称为高阶函数。

【讨论】:

【参考方案3】:

正如 Kotlin 1.4 版本的 Kotlin documentation 指出的那样:

在 Kotlin 1.4.0 之前,您只能在使用来自 Kotlin 的 Java 方法和 Java 接口时应用 SAM(单一抽象方法)转换。从现在开始,您也可以对 Kotlin 接口使用 SAM 转换。为此,请使用 fun 修饰​​符将 Kotlin 接口显式标记为函数式。

fun interface Operation1 
    operator fun invoke(x: String): String


fun interface Operation2 
    fun doSomething(x: Int): String


val operation1 = Operation1  "$it world!" 
val operation2 = Operation2  "$it world!" 

fun main() 
    // Usage: First sample.
    println(operation1("Hello"))
    println(operation2.doSomething(0))
    // Usage: Second sample.
    println(Operation1  "$it world!" ("Hello"))
    println(Operation2  "$it!" .doSomething(0))

您可以阅读更多关于功能接口的信息here。

【讨论】:

【参考方案4】:

在下面的代码中,我使用可过滤适配器在列表中进行搜索。在这里,我使用 lambda 作为回调,以在没有找到搜索数据时通知视图模型。

在 ViewModel 中实例化适配器。并通过 lambda

var matterAdapter = MatterAdapter(matterList) 
    //todo - got callback

适配器

class MatterAdapter (var filteredList : MutableList<AndroidViewModel>, val funcNoSearchData : () -> Unit) : DataBindingRecyclerViewAdapter(filteredList), Filterable 

private var mViewModelMap: MutableMap<Class<*>, Int> = mutableMapOf()

private var originalList : MutableList<AndroidViewModel> = mutableListOf()

private val mFilter = ItemFilter()

init 
    mViewModelMap.put(MatterRowViewModel::class.java, R.layout.row_matter)


override fun getViewModelLayoutMap(): MutableMap<Class<*>, Int> 
    return mViewModelMap


override fun getFilter(): Filter 
    return mFilter


private inner class ItemFilter : Filter() 
    override fun performFiltering(constraint: CharSequence): FilterResults 

        val filterString = constraint.toString().toLowerCase()

        val results = FilterResults()

        val list = originalList

        val count = list.size
        val nlist = ArrayList<AndroidViewModel>(count)

        var filterableString: String

        for (i in 0 until count) 
            filterableString = (list.get(i) as MatterRowViewModel).matter.casestitle!!
            if (filterableString.toLowerCase().contains(filterString)) 
                nlist.add(list.get(i))
            
        

        results.values = nlist
        results.count = nlist.size

        return results
    

    override fun publishResults(constraint: CharSequence, results: Filter.FilterResults) 
        filteredList.clear()
        filteredList.addAll(results.values as ArrayList<AndroidViewModel>)
        // sends empty search callback to viewmodel
        if(filteredList.size == 0) 
            funcNoSearchData()
        
        notifyDataSetChanged()
    
  

  fun resetSearch() 
      filteredList.clear()
      filteredList.addAll(originalList)
      notifyDataSetChanged()
  

  fun refreshData() 
      originalList = ArrayList(filteredList)
      notifyDataSetChanged()
  

【讨论】:

以上是关于如何使用 lambda 而不是 kotlin 接口的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 中接口的 Lambda 实现

如何在 Kotlin 中将函数接收器类型与 SAM 接口一起使用

当我使用 Kotlin 功能类型而不是 Java 功能接口来使用来自 Kafka 的消息时,缓存不起作用

在 Kotlin lambda 表达式中覆盖多个接口方法

Kotlin lambda学习

Kotlin学习与实践 带接收者的lambda及Java的函数式接口