背景:列表控件在Android App开发中用到的场景很多。在以前我们用ListView,GradView,现在应该大多数开发者都已经在选择使用RecyclerView了,谷歌给我们提供了这些方便的列表控件,我们可以很容易的使用它们。但是在实际的场景中,我们可能还想要更多的能力,比如最常见的列表下拉刷新,上拉加载。上拉刷新和下拉加载应该是列表的标配吧,基本上有列表的地方都要具体这个能力。虽然刷新这个功能已经有各种各样的第三方框架可以选择,但是毕竟不是自己的嘛,今天我们就来实现一个自己的下拉刷新控件,多动手才能更好的理解。
效果图:
原理分析:
在coding之前,我们先分析一下原理,原理分析出来之后,我们才可以确定实现方案。
先上一张图,来个直观的认识:
在列表上面有个刷新头,随着手指向下拉,逐渐把顶部不可见的刷新头拉到屏幕中来,用户能看到刷新的状态变化,达到下拉刷新的目的。
通过分析,我们确定一种实现方案:我们自定义一个容器,容器里面包含两个部分。
1. 顶部刷新头。
2. 列表区域。
确定好布局容器之后,我们来分析刷新头的几种状态
把下拉刷新分为5中状态,通过不同状态间的切换实现下拉刷新能力。
状态间的流程图如下:
整个下拉刷新的流程就如图中所示。
流程清楚了之后,接下来就是编写代码实现了。
代码实现:
public class PullRefreshView extends LinearLayout {
public static final String HEADER_TAG = "HEADER_TAG";
public static final String LIST_TAG = "LIST_TAG";
private static final String TAG = "PullRefreshView";
private @State
int mState = State.INIT;
private boolean mIsDragging = false;
private Context mContext;
private RecyclerView mRecyclerView;
private View mHeaderView;
private int mInitMotionY;
private int mLastMotionY;
private int mSlopTouch;
private int mRefreshHeight = 200;
private int mDuring = 300;
private onRefreshListener mOnRefreshListener;
private TextView mRefreshTip;
private boolean mIsCanDrag = true;
private LayoutParams mHeaderLayoutParams;
private LayoutParams mListLayoutParams;
private ValueAnimator mValueAnimator;
/// 分割 ///
public PullRefreshView(Context context) {
this(context, null);
}
public PullRefreshView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PullRefreshView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initView();
}
public RecyclerView getRecyclerView() {
return mRecyclerView;
}
public void addRecyclerView(RecyclerView recyclerView) {
if (recyclerView == null) {
return;
}
View view = findViewWithTag(LIST_TAG);
if (view != null) {
removeView(view);
}
this.mRecyclerView = recyclerView;
this.mRecyclerView.setTag(LIST_TAG);
addView(recyclerView, mListLayoutParams);
}
public void addHeaderView(View headerView) {
if (headerView == null) {
return;
}
View view = findViewWithTag(HEADER_TAG);
if (view != null) {
removeView(view);
}
this.mHeaderView = headerView;
this.mHeaderView.setTag(HEADER_TAG);
addView(mHeaderView, mHeaderLayoutParams);
}
public void setonRefreshListener(onRefreshListener onRefreshListener) {
monRefreshListener = onRefreshListener;
}
private void initView() {
setOrientation(LinearLayout.VERTICAL);
Context context = getContext();
mHeaderView = LayoutInflater.from(context).inflate(R.layout.layout_refresh_header, null);
mHeaderView.setTag(HEADER_TAG);
mRefreshTip = mHeaderView.findViewById(R.id.content);
mHeaderLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
DensityUtil.dip2px(mContext, 500)
);
this.addView(mHeaderView, mHeaderLayoutParams);
mRecyclerView = new RecyclerView(context);
mRecyclerView.setTag(LIST_TAG);
mListLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
this.addView(mRecyclerView, mListLayoutParams);
setPadding(0, -DensityUtil.dip2px(mContext, 500), 0, 0);
ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
mSlopTouch = viewConfiguration.getScaledTouchSlop();
setState(State.INIT);
}
private void setState(@State int state) {
switch (state) {
case State.INIT:
initState();
break;
case State.DRAGGING:
dragState();
break;
case State.READY:
readyState();
break;
case State.REFRESHING:
refreshState();
break;
case State.FLING:
flingState();
break;
default:
break;
}
mState = state;
}
private void initState() {
// 只有在初始状态时,恢复成可拖拽
mIsCanDrag = true;
mIsDragging = false;
mRefreshTip.setText("下拉刷新");
}
private void dragState() {
mIsDragging = true;
}
private void readyState() {
mRefreshTip.setText("松手刷新");
}
private void refreshState() {
if (monRefreshListener != null) {
mOnRefreshListener.onRefresh();
}
mIsCanDrag = false;
mRefreshTip.setText("正在刷新,请稍后...");
}
private void flingState() {
mIsDragging = false;
mIsCanDrag = false;
if (mState == State.READY) {
Log.e(TAG, "flingState: 从Ready状态开始自由滑动");
// 从准备状态进入,刷新头滑到 200 的位置
smoothScroll(getScrollY(), -mRefreshHeight);
}
else {
Log.e(TAG, "flingState: 松手后,从其他状态开始自由滑动");
// 从刷新状态进入,刷新头直接回到最初默认的位置
// 即: 滑出界面,ScrollY 变成 0
smoothScroll(getScrollY(), 0);
}
}
private void smoothScroll(int startPos, final int targetPos) {
// 如果有动画正在播放,先停止
if (mValueAnimator != null && mValueAnimator.isRunning()) {
mValueAnimator.cancel();
mValueAnimator.end();
mValueAnimator = null;
}
// 然后开启动画
mValueAnimator = ValueAnimator.ofInt(getScrollY(), targetPos);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int value = (int) valueAnimator.getAnimatedValue();
scrollTo(0, value);
if (getScrollY() == targetPos) {
if (targetPos != 0) {
setState(State.REFRESHING);
}
else {
setState(State.INIT);
}
}
}
});
mValueAnimator.setDuration(mDuring);
mValueAnimator.start();
}
private boolean isReadyToPull() {
if (mRecyclerView == null) {
return false;
}
LinearLayoutManager manager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
if (manager == null) {
return false;
}
if (mRecyclerView != null && mRecyclerView.getAdapter() != null) {
View child = mRecyclerView.getChildAt(0);
int height = child.getHeight();
if (height > mRecyclerView.getHeight()) {
return child.getTop() == 0 && manager.findFirstVisibleItemPosition() == 0;
}
else {
return manager.findFirstCompletelyVisibleItemPosition() == 0;
}
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
Log.e(TAG, "onInterceptTouchEvent: action = " + action);
if (!mIsCanDrag) {
return true;
}
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mIsDragging = false;
return false;
}
if (mIsDragging && action == MotionEvent.ACTION_MOVE) {
return true;
}
switch (action) {
case MotionEvent.ACTION_MOVE:
int diff = (int) (ev.getY() - mLastMotionY);
if (Math.abs(diff) > mSlopTouch && diff > 1 && isReadyToPull()) {
mLastMotionY = (int) ev.getY();
mIsDragging = true;
}
break;
case MotionEvent.ACTION_DOWN:
if (isReadyToPull()) {
setState(State.INIT);
mInitMotionY = (int) ev.getY();
mLastMotionY = (int) ev.getY();
}
break;
default:
break;
}
return mIsDragging;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
Log.e(TAG, "onTouchEvent: action = " + action);
if (!mIsCanDrag) {
return false;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
if (isReadyToPull()) {
setState(State.INIT);
mInitMotionY = (int) event.getY();
mLastMotionY = (int) event.getY();
}
break;
case MotionEvent.ACTION_MOVE:
if (mIsDragging) {
mLastMotionY = (int) event.getY();
setState(State.DRAGGING);
pullScroll();
return true;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsDragging = false;
setState(State.FLING);
break;
default:
break;
}
return true;
}
private void pullScroll() {
int scrollValue = (mInitMotionY - mLastMotionY) / 3;
if (scrollValue > 0) {
scrollTo(0, 0);
return;
}
if (Math.abs(scrollValue) > mRefreshHeight
&& mState == State.DRAGGING) {
// 约定:如果偏移量超过 200(这个值,表示是否可以启动刷新的临界值,可任意定),
// 那么状态变成 State.READY
Log.e(TAG, "pullScroll: 超过了触发刷新的临界值");
setState(State.READY);
}
scrollTo(0, scrollValue);
}
public void refreshComplete() {
mRefreshTip.setText("刷新完成!");
setState(State.FLING);
}
@IntDef({
State.INIT
, State.DRAGGING
, State.READY
, State.REFRESHING
, State.FLING,
})
@Retention(RetentionPolicy.SOURCE)
public @interface State {
int INIT = 1;
int DRAGGING = 2;
int READY = 3;
int REFRESHING = 4;
int FLING = 5;
}
public interface onRefreshListener {
void onRefresh();
}
}
实现的逻辑并不复杂,新手都能看懂,先理解了整个流程,代码就是水到渠成的事。
思想第一,最后代码。
完整DEMO直通车
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。



