美文网首页Android开发经验谈Android开发谈安卓开发博客
用于任何View的下拉刷新上拉加载Layout

用于任何View的下拉刷新上拉加载Layout

作者: yask | 来源:发表于2018-07-03 10:27 被阅读19次

    先展示效果


    recycleview下拉刷新 textview上拉加载

    1、用法

    Step 1. Add the JitPack repository to your build file
    Add it in your root build.gradle at the end of repositories:
        allprojects {
            repositories {
                ...
                maven { url 'https://jitpack.io' }
            }
        }
    Step 2. Add the dependency
    
        dependencies {
                implementation 'com.github.PMMKing:KSwipeRefreshLayout:0.9.9'
        }
    

    项目中加入本库的依赖,配合另一个recycleview通用adapter食用更佳。

    layout文件代码如下

    <?xml version="1.0" encoding="utf-8"?>
    <com.yuan.library.KSwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/ks_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:refreshMode="both"
        app:refreshView="com.yuan.kswiperefreshlayout.CustomView">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </com.yuan.library.KSwipeRefreshLayout>
    

    Activity中代码

     val view = LayoutInflater.from(this).inflate(R.layout.image, null, false)
     ks_layout.setLoadView(view)
     ks_layout.setOnRefreshListener(object : OnRefreshListener {
           override fun onRefresh() {
               Handler().postDelayed({ ks_layout.setRefresh(false) }, 1500)
           }
    
         override fun onLoad() {
              Handler().postDelayed({ ks_layout.setRefresh(false) }, 1500)
         }
    })
    

    Adapter代码

            val adapter = MultiAdapter<String>(this).addTypeView(object : ITypeView<String> {
                override fun isForViewType(p0: String?, p1: Int): Boolean {
                    return true
                }
    
                override fun createViewHolder(p0: Context, p1: ViewGroup): BaseViewHolder<*> {
                    return ViewHolder(p0, LayoutInflater.from(p0).inflate(R.layout.image, p1, false))
                }
            })
            rv_list.layoutManager = LinearLayoutManager(this)
            rv_list.adapter = adapter
            val list = mutableListOf<String>()
            for (i in 1..2) {
                list.add("")
            }
            adapter.data = list
            adapter.setOnItemClickListener { view, any, i ->
                startActivity(Intent(this, TestActivity::class.java))
            }
    

    更多功能

    layout文件中自定义属性只有三个

    1、app:refreshMode

    刷新模式有四种,top只有下拉刷新,bottom只有上拉加载,both下拉和上拉同时存在,never下拉和上拉都不存在。

    2、app:refreshView

    接受一个string类型的值,值的内容是下拉刷新显示view的全路径

    app:refreshView="com.yuan.kswiperefreshlayout.CustomView"
    

    内部使用反射调用只有Context参数的构造函数来实例化一个view设置为下拉刷新的view。

    3、app:loadView

    同设置下拉刷新view

    代码中设置刷新模式
    ks_layout.setRefreshMode(RefreshMode.BOTH)
    
    支持回调下拉上拉距离的方法,自定义View实现RefreshCall接口,重写四个方法
    class CustomView : LinearLayout, RefreshCall {
    
        constructor(context: Context?) : this(context, null)
        constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
        constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
            initView()
        }
    
        fun initView() {
            LayoutInflater.from(context).inflate(R.layout.refresh_header, this, true)
            setBackgroundColor(Color.GRAY)
        }
    
        override fun startRefresh() {
            tv.text = "开始下拉刷新"
        }
    
        override fun refreshing() {
            tv.text = "下拉刷新中..."
            thread {
                while (true) {
                    Thread.sleep(6)
                    iv.post {
                        iv.rotation++
                    }
                }
            }
        }
    
        override fun endRefresh() {
            tv.text = "下拉刷新成功"
        }
    
        override fun refreshDiff(diffY: Int) {
            iv.rotation = diffY.toFloat()
        }
    }
    

    拉动的时候会回调 refreshDiff(diffY: Int) 方法

    2、代码分析

    这个库使用Kotlin写的,其实Kotlin已经用了很长一段时间了,具体说Kotlin有什么优势还是很泛泛的,但是用上就很难切换回去。语法糖用着确实很爽啊!
    像这种下拉刷新上拉加载的Layout无非就是在事件分发处理的时候做文章,所以重点也是这一块的代码。

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
            val action = ev.action
            when (action) {
                MotionEvent.ACTION_DOWN -> {
                    rawY = ev.rawY
                }
                MotionEvent.ACTION_MOVE -> {
                    val diffY = ev.rawY - rawY
                    val can = (diffY > 10 && canRefresh()) || (diffY < -10 && canLoad())
                    if (can) return true
                }
            }
            return super.onInterceptTouchEvent(ev)
        }
    

    layout继承的ViewGroup,首先应该判断事件是否应该拦截自己来处理,在DOWN的时候记录一下按下的y坐标,MOVE的时候计算一下Y轴滑动距离,判断一下滑动距离是否大于某个值,这个值主要是为了处理在点击的时候手抖动会造成误判导致事件被拦截。然后判断一下是否可以刷新

    private fun canRefresh(): Boolean {
            val canScrollVertically = ViewCompat.canScrollVertically(mTargetView, -1)
            return !canScrollVertically && canRefresh && mRefreshView != null
        }
    

    说明一下 ViewCompat.canScrollVertically(mTargetView, -1)这个方法是SDK14才加入的,但是现在最低版本已经是14了,所以不用兼容以前的版本了。
    返回true的话说明事件可以拦截,交给自己的onTouchEvent方法处理。

    override fun onTouchEvent(event: MotionEvent): Boolean {
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    rawY = event.rawY
                    return true
                }
                MotionEvent.ACTION_MOVE -> {
                    val diffY = event.rawY - rawY
                    val diff = diff(diffY)
                    when (refreshState) {
                        RefreshState.DEFAULT -> {
                            layoutChildren(diff(diffY))
                            refreshState = if (diffY >= 0) RefreshState.DOWNPULL else RefreshState.UPPULL
                            when (refreshState) {
                                RefreshState.DOWNPULL -> {
                                    mRefreshCall?.startRefresh()
                                    mRefreshCall?.refreshDiff(diff)
                                }
                                RefreshState.UPPULL -> {
                                    mLoadCall?.startRefresh()
                                    mLoadCall?.refreshDiff(diff)
                                }
                            }
                        }
                        RefreshState.DOWNPULL -> {
                            if (diffY >= 0 && canRefresh()) {
                                layoutChildren(diff)
                                mRefreshCall?.refreshDiff(diff)
                            } else {
                                layoutChildren()
                                rawY = event.rawY
                            }
                        }
                        RefreshState.UPPULL -> {
                            if (diffY <= 0 && canLoad()) {
                                layoutChildren(diff)
                                mLoadCall?.refreshDiff(diff)
                            } else {
                                layoutChildren()
                                rawY = event.rawY
                            }
                        }
                    }
                }
                MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                    val diffY = Math.abs(event.rawY - rawY)
                    val diff = diff(diffY)
                    when (refreshState) {
                        RefreshState.DOWNPULL -> {
                            val height = mRefreshView?.measuredHeight ?: 200
                            if (diff > height) {
                                downRefresh(height)
                            } else {
                                refreshState = RefreshState.DEFAULT
                                layoutChildren()
                            }
                        }
                        RefreshState.UPPULL -> {
                            val height = mLoadView?.measuredHeight ?: 200
                            if (diff > height) {
                                upLoad(height)
                            } else {
                                refreshState = RefreshState.DEFAULT
                                layoutChildren()
                            }
                        }
                    }
    
                }
    
            }
            return super.onTouchEvent(event)
        }
    

    交给自身onTouchEvent处理,接收到Down事件一定要返回true,表示接下来的事件都要交给我来处理,返回false会把事件交给父ViewGroup处理。MOVE事件的时候判断一下状态,把计算出来的位移的值传递进去,抬起的时候判断一下是否触发刷新,调用相应方法。

    事件分发机制说明,参考自这个博文

    onInterceptTouchEvent方法对触屏事件的拦截处理需要和onTouchEvent方法配合使用。
    1、down事件首先传递到onInterceptTouchEvent方法中
    2、onInterceptTouchEvent返回false表示将down事件交由子View来处理;若某一层子View的onTouchEvent返回了true,后续的move、up等事件都将先传递到ViewGroup的onInterceptTouchEvent的方法,并继续层层传递下去,交由子View处理;若子View的onTouchEvent都返回了false,则down事件将交由该ViewGroup的onTouchEvent来处理;如果ViewGroup的onTouchEvent返回false,down传递给父ViewGroup,后续事件不再传递给该ViewGroup;如果ViewGroup的onTouchEvent返回true,后续事件不再经过该ViewGroup的onInterceptTouchEvent方法,直接传递给onTouchEvent方法处理
    3、onInterceptTouchEvent返回ture,down事件将转交该ViewGroup的onTouchEvent来处理;若onTouchEvent返回true,后续事件将不再经过该ViewGroup的onInterceptTouchEvent方法,直接交由该ViewGroup的onTouchEvent方法处理;若onTouchEvent方法返回false,后续事件都将交由父ViewGroup处理,不再经过该ViewGroup的onInterceptTouchEvent方法和onTouchEvent方

    感兴趣的可以看下源码,欢迎Star GitHub

    相关文章

      网友评论

        本文标题:用于任何View的下拉刷新上拉加载Layout

        本文链接:https://www.haomeiwen.com/subject/bewluftx.html