/**
* @author : zyl
* //感覺這種方式 雖然能實(shí)現(xiàn)下啦刷新 上拉加載 但NestedScrollingParent2 這套接口感覺主要是用于嵌套滾動(dòng)
*/
val BOTTOM_MAX_HEIGHT_DEFAULT = 100.dp
val HEAD_MAX_HEIGHT_DEFAULT = 100.dp
const val SCROLL_DEFAULT_DURATION = 400
const val RESISTANCE_FACTOR_DEFAULT = 0.6
class RefreshLayout(context: Context?, attrs: AttributeSet?) : ViewGroup(context, attrs),
NestedScrollingParent2 {
private val TAG = RefreshLayout::class.java.simpleName
private var refreshState = RefreshState.NONE
private set(value) {
if (field != value) {
field = value
refreshStateChangeListener?.refreshStateChange(value)
}
}
private var loadMoreState = LoadState.NONE
private set(value) {
if (field != value && canLoadMore.get()) {
field = value
refreshStateChangeListener?.loadMoreStateChange(value)
}
}
/**
* 是否能夠觸發(fā)加載更多的回調(diào)
*/
private val canLoadMore = AtomicBoolean(true)
private var headHeight = 0
private var footHeight = 0
/**
* 阻力系數(shù)
*/
private var resistance = RESISTANCE_FACTOR_DEFAULT
private var bottomMaxHeight = BOTTOM_MAX_HEIGHT_DEFAULT
private var headMaxHeight = HEAD_MAX_HEIGHT_DEFAULT
private var targetView: View? = null
private val overScroller = OverScroller(context)
private val parentHelper = NestedScrollingParentHelper(this).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
isNestedScrollingEnabled = true
}
}
var refreshListener: OnRefreshListener? = null
/**
* 刷新狀態(tài)變化通知 方便外部頭部view 做狀態(tài)變化
*/
var refreshStateChangeListener: RefreshStateChangeListener? = null
/**
* 添加刷新頭
*/
fun addRefreshHeadView(view: View) {
removeAllViews()
addView(view)
addView(targetView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
requestLayout()
}
/**
* 添加刷新頭和尾
*/
fun addRefreshHeadAndFooter(headView: View, footerView: View) {
removeAllViews()
addView(headView)
addView(targetView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
addView(footerView)
requestLayout()
}
/**
* 布局加載完畢了 此時(shí)找到加需要刷新頭的那個(gè)targetView
*/
override fun onFinishInflate() {
super.onFinishInflate()
if (childCount > 1) throw Exception("xml only support one child")
targetView = getChildAt(0)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var selfWidth = 0
var selfHeight = 0
for (index in 0 until childCount) {
val child = getChildAt(index)
//忽略margin值的策略
measureChild(child, widthMeasureSpec, heightMeasureSpec)
selfWidth = max(child.measuredWidth, selfWidth)
selfHeight += child.measuredHeight
}
setMeasuredDimension(selfWidth, selfHeight)
//防止沒有刷新頭的情況
if (childCount > 1) headHeight = getChildAt(0).measuredHeight
//得到刷新尾部的高度
if (childCount > 2) footHeight = getChildAt(2).measuredHeight
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
//隱藏頭部布局
var top = -headHeight
//擺放子控件 豎直排列
for (index in 0 until childCount) {
val child = getChildAt(index)
//居中擺放
child.layout(
measuredWidth / 2 - child.measuredWidth / 2,
top,
measuredWidth / 2 + child.measuredWidth / 2,
top + child.measuredHeight
)
top += child.measuredHeight
}
}
/**
* 當(dāng)是刷新狀態(tài)時(shí),不允許用戶操作
*/
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
if (refreshState == RefreshState.REFRESHING
&& ev.actionMasked == MotionEvent.ACTION_DOWN
&& headHeight > 0
) return true
if (loadMoreState == LoadState.LOADING
&& ev.actionMasked == MotionEvent.ACTION_DOWN
&& footHeight > 0
) return true
return super.onInterceptTouchEvent(ev)
}
/**
* 重置列表刷新加載狀態(tài)
*/
fun onRefreshDone() {
if (scrollY > 0) {
loadMoreState = LoadState.DONE
} else {
refreshState = RefreshState.REFRESHING_COMPLETE
}
overScroller.startScroll(0, scrollY, 0, -scrollY, SCROLL_DEFAULT_DURATION)
postInvalidateOnAnimation()
}
/**
* 設(shè)置是否觸發(fā)加載更多 if true 觸發(fā)
*/
fun setTriggerLoadMore(trigger: Boolean) {
canLoadMore.set(trigger)
}
override fun computeScroll() {
if (overScroller.computeScrollOffset()) {
scrollTo(overScroller.currX, overScroller.currY)
postInvalidateOnAnimation()
}
}
// NestedScrollingParent2
override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int) {
parentHelper.onNestedScrollAccepted(child, target, axes, type)
}
override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
//目前只支持豎直方向
return nestedScrollAxes and ViewCompat.SCROLL_AXIS_VERTICAL != 0
}
//目前只支持豎直方向
override fun getNestedScrollAxes(): Int {
return ViewCompat.SCROLL_AXIS_VERTICAL
}
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray?, type: Int) {
//此方法 我的理解 是滑動(dòng)之前 需要問下父空間是否需要處理 目前沒有作為子空間的場(chǎng)景 所以不需要處理
Log.v(TAG, "onNestedPreScroll target = $target,dy = $dy,scrollY = ${scrollY},type = $type")
if (scrollY != 0) {
consumed?.let {
it[1] = dy
onNestedScroll(target, dx, 0, 0, dy, TYPE_TOUCH)
}
}
}
//控制fling
override fun onNestedPreFling(target: View, velocityX: Float, velocityY: Float): Boolean {
return scrollY != 0
}
override fun onNestedScroll(
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int
) {
if (dyUnconsumed != 0) {
//真正處理滑動(dòng)事件
var fixDy = fixYOffset(dyUnconsumed)
// scrollBy(0, fixDy)
scrollBy(0, fixDy - (fixDy*resistance).toInt())
if (scrollY < 0) {
refreshState = if (headHeight > 0 && -scrollY >= headHeight) {
RefreshState.RELEASE_TO_REFRESH
} else {
RefreshState.PULL
}
} else {
loadMoreState = if (footHeight in 1..scrollY) {
LoadState.RELEASE_TO_LOADING
} else {
LoadState.PULL
}
}
}
//處理完剩余的事件 看看父親控件 是否需要 ,此情況暫不考慮
}
override fun onStopNestedScroll(target: View, type: Int) {
Log.v(TAG, "onStopNestedScroll target = $target,scrollY = $scrollY")
if (refreshState == RefreshState.RELEASE_TO_REFRESH) {
Log.v(TAG, "onStopNestedScroll refreshing")
//觸發(fā)刷新回調(diào)
refreshState = RefreshState.REFRESHING
//滾動(dòng)到指定高度
overScroller.startScroll(0, scrollY, 0, -scrollY - headHeight, SCROLL_DEFAULT_DURATION)
postInvalidateOnAnimation()
refreshListener?.reFresh()
} else if (loadMoreState == LoadState.RELEASE_TO_LOADING) {
Log.v(TAG, "onStopNestedScroll loadMore")
loadMoreState = LoadState.LOADING
//底部滑動(dòng)到指定位置
overScroller.startScroll(
0,
scrollY,
0,
-(scrollY - footHeight),
SCROLL_DEFAULT_DURATION
)
postInvalidateOnAnimation()
refreshListener?.loadMore()
} else {
//不觸發(fā)刷新 或是加載
if (scrollY > 0) {
loadMoreState = LoadState.NONE
} else {
refreshState = RefreshState.NONE
}
overScroller.startScroll(0, scrollY, 0, -scrollY, SCROLL_DEFAULT_DURATION)
postInvalidateOnAnimation()
}
parentHelper.onStopNestedScroll(target, type)
}
/**
* 修正滑動(dòng)位置 防止滑動(dòng)越界
*/
private fun fixYOffset(dyUnconsumed: Int): Int {
var fixDy = dyUnconsumed
if (scrollY < 0) {
fixDy = if (dyUnconsumed < 0) {
if (headMaxHeight >= -(dyUnconsumed + scrollY)) {
dyUnconsumed
} else {
0
}
} else {
if (dyUnconsumed + scrollY <= 0) {
dyUnconsumed
} else {
-scrollY
}
}
} else if (scrollY > 0) {
fixDy = if (dyUnconsumed > 0) {
if (bottomMaxHeight >= scrollY + dyUnconsumed) {
dyUnconsumed
} else {
0
}
} else {
if (scrollY + dyUnconsumed >= 0) {
dyUnconsumed
} else {
-scrollY
}
}
}
return fixDy
}
}
利用NestedScrollingParent2實(shí)現(xiàn)RecycleView下拉刷新 上拉加載
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
相關(guān)閱讀更多精彩內(nèi)容
- 效果圖: 上拉加載用BaseRecyclerViewAdapterHelper,下拉刷新用SmartRefresh...
- 一.下拉刷新話不多說,前段時(shí)間做項(xiàng)目的時(shí)候剛用到過,分享出來集思廣益,歡迎指出不足。本文主要利用SwipeRefr...
- 去年在大神公眾號(hào)評(píng)論點(diǎn)贊前三名獲得了《Android App開發(fā)從入門到精通》這本書,這兩天閑來無事,就大致瀏覽一...
- 利用類別實(shí)現(xiàn)tableView自帶上拉加載以及下拉刷新效果 當(dāng)然該功能需要MJRefresh第三方庫支持 添加 M...
- 一. 實(shí)現(xiàn)下拉刷新 在google的android.support.v4包中,提供一個(gè)SwipeRefreshLa...