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

先展示效果


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'
    }

項(xiàng)目中加入本庫的依賴枫匾,配合另一個(gè)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文件中自定義屬性只有三個(gè)

1惩歉、app:refreshMode

刷新模式有四種,top只有下拉刷新撑蚌,bottom只有上拉加載,both下拉和上拉同時(shí)存在粉楚,never下拉和上拉都不存在亮垫。

2、app:refreshView

接受一個(gè)string類型的值饮潦,值的內(nèi)容是下拉刷新顯示view的全路徑

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

內(nèi)部使用反射調(diào)用只有Context參數(shù)的構(gòu)造函數(shù)來實(shí)例化一個(gè)view設(shè)置為下拉刷新的view。

3回俐、app:loadView

同設(shè)置下拉刷新view

代碼中設(shè)置刷新模式
ks_layout.setRefreshMode(RefreshMode.BOTH)
支持回調(diào)下拉上拉距離的方法,自定義View實(shí)現(xiàn)RefreshCall接口仅颇,重寫四個(gè)方法
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()
    }
}

拉動(dòng)的時(shí)候會(huì)回調(diào) refreshDiff(diffY: Int) 方法

2忘瓦、代碼分析

這個(gè)庫使用Kotlin寫的,其實(shí)Kotlin已經(jīng)用了很長一段時(shí)間了耕皮,具體說Kotlin有什么優(yōu)勢還是很泛泛的,但是用上就很難切換回去汽摹。語法糖用著確實(shí)很爽翱嘞恰!
像這種下拉刷新上拉加載的Layout無非就是在事件分發(fā)處理的時(shí)候做文章拉庶,所以重點(diǎn)也是這一塊的代碼秃励。

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,首先應(yīng)該判斷事件是否應(yīng)該攔截自己來處理夺鲜,在DOWN的時(shí)候記錄一下按下的y坐標(biāo),MOVE的時(shí)候計(jì)算一下Y軸滑動(dòng)距離慷蠕,判斷一下滑動(dòng)距離是否大于某個(gè)值食呻,這個(gè)值主要是為了處理在點(diǎn)擊的時(shí)候手抖動(dòng)會(huì)造成誤判導(dǎo)致事件被攔截。然后判斷一下是否可以刷新

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

說明一下 ViewCompat.canScrollVertically(mTargetView, -1)這個(gè)方法是SDK14才加入的每辟,但是現(xiàn)在最低版本已經(jīng)是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會(huì)把事件交給父ViewGroup處理。MOVE事件的時(shí)候判斷一下狀態(tài)断箫,把計(jì)算出來的位移的值傳遞進(jìn)去,抬起的時(shí)候判斷一下是否觸發(fā)刷新婶熬,調(diào)用相應(yīng)方法埃撵。

事件分發(fā)機(jī)制說明,參考自這個(gè)博文

onInterceptTouchEvent方法對(duì)觸屏事件的攔截處理需要和onTouchEvent方法配合使用暂刘。
1、down事件首先傳遞到onInterceptTouchEvent方法中
2募寨、onInterceptTouchEvent返回false表示將down事件交由子View來處理森缠;若某一層子View的onTouchEvent返回了true绪商,后續(xù)的move辅鲸、up等事件都將先傳遞到ViewGroup的onInterceptTouchEvent的方法,并繼續(xù)層層傳遞下去例书,交由子View處理刻炒;若子View的onTouchEvent都返回了false,則down事件將交由該ViewGroup的onTouchEvent來處理坟奥;如果ViewGroup的onTouchEvent返回false拇厢,down傳遞給父ViewGroup晒喷,后續(xù)事件不再傳遞給該ViewGroup;如果ViewGroup的onTouchEvent返回true衣盾,后續(xù)事件不再經(jīng)過該ViewGroup的onInterceptTouchEvent方法爷抓,直接傳遞給onTouchEvent方法處理
3、onInterceptTouchEvent返回ture蓝撇,down事件將轉(zhuǎn)交該ViewGroup的onTouchEvent來處理;若onTouchEvent返回true据悔,后續(xù)事件將不再經(jīng)過該ViewGroup的onInterceptTouchEvent方法耘沼,直接交由該ViewGroup的onTouchEvent方法處理;若onTouchEvent方法返回false群嗤,后續(xù)事件都將交由父ViewGroup處理,不再經(jīng)過該ViewGroup的onInterceptTouchEvent方法和onTouchEvent方

感興趣的可以看下源碼骇径,歡迎Star GitHub

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末者春,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子晰筛,更是在濱河造成了極大的恐慌拴袭,老刑警劉巖读第,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怜瞒,死亡現(xiàn)場離奇詭異般哼,居然都是意外死亡惠窄,警方通過查閱死者的電腦和手機(jī)漾橙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人臀晃,你說我怎么就攤上這事』胀铮” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵踢京,是天一觀的道長宦棺。 經(jīng)常有香客問我,道長蹈丸,這世上最難降的妖魔是什么呐芥? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮思瘟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘够话。我一直安慰自己光绕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布澡为。 她就那樣靜靜地躺著景埃,像睡著了一般顶别。 火紅的嫁衣襯著肌膚如雪拒啰。 梳的紋絲不亂的頭發(fā)上驯绎,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天剩失,我揣著相機(jī)與錄音册着,去河邊找鬼拴孤。 笑死甲捏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芒粹。 我是一名探鬼主播大溜,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼获三!你這毒婦竟也來了锨苏?” 一聲冷哼從身側(cè)響起疙教,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤贞谓,失蹤者是張志新(化名)和其女友劉穎葵诈,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體作喘,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年窖贤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滤蝠。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡授嘀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蹄皱,到底是詐尸還是另有隱情,我是刑警寧澤压鉴,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布盔几,位于F島的核電站掩幢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏际邻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一缨恒、第九天 我趴在偏房一處隱蔽的房頂上張望轮听。 院中可真熱鬧,春花似錦血巍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至螟炫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背财饥。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國打工折晦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人满着。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓风喇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親魂莫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容