打造Android微信朋友圈下拉刷新控件

微信朋友圈我们都经常用,朋友圈的下拉刷新比较有意思,我们今天将要模仿打造微信朋友圈的下拉刷新控件,当然微信的这种刷新设计可能不是最好的,实际项目中你可以用V4包里面的SwipeRefreshView或者Chris Banes的AndroidPullRerfresh,看产品经理的设计。
思路
我们初步分析下,界面上主要有二个控件,一个彩虹状的圆形LoadingView,一个是ListView,那么我大致可以有下面三个步骤:

第一步:需要自定义一个ViewGroup,把上面的2个控件add进来。
第二步:利用ViewDragHelper处理控件拖动。当ListView处于顶部时,如果继续向下拖动,就拦截触摸事件,将触摸事件传递给ViewDragHelper处理,这里比较关键,主要是是否拦截触摸事件的判断条件要处理好,否则如果ListView的点击和滚动事件被我们拦截了,那就悲剧了。
第三步:在ViewDragHelper的拖动回调方法里面,设置listView和彩虹LoadingView的位置,调用requestLayout。
第四步:手势松开后,开始刷新,LoadingView在固定位置做旋转动画。
第五步:如果设置了onRefreshListener,执行onRefresh接口。
第六步:调用stopRefresh,完成刷新,这一步需要控件使用者手动去调用,控件本身不自动触发。

代码实现
篇幅关系,我还是贴出部分关键代码,项目我惯例共享到Github了,大家可以去直接下载 https://github.com/aliouswang/FriendRefreshView

public class FriendRefreshView extends ViewGroup{

//圆形指示器
private ImageView mRainbowView;
private ListView mContentView;

//控件宽,高
private int sWidth;
private int sHeight;

private ViewDragHelper mDragHelper;

//contentView的当前top属性
private int currentTop;
//listView首个item
private int firstItem;
private boolean bScrollDown = false;
private boolean bDraging = false;

//圆形加载指示器最大top
private int rainbowMaxTop = 80;
//圆形加载指示器刷新时的top
private int rainbowStickyTop = 80;
//圆形加载指示器初始top
private int rainbowStartTop = -120;
//圆形加载指示器的半径
private int rainbowRadius = 100;
private int rainbowTop = - 120;
//圆形加载指示器旋转的角度
private int rainbowRotateAngle = 0;
private boolean bViewHelperSettling = false;

//刷新接口listener
private OnRefreshListener mRefreshLisenter;

private AbsListView.OnScrollListener onScrollListener;
private com.sw.library.widget.friendrefreshview.OnDetectScrollListener onDetectScrollListener;

public enum State {
NORMAL,
REFRESHING,
DRAGING
}

//控件当前状态
private State mState = State.NORMAL;

public FriendRefreshView(Context context) {
this(context, null);
}

public FriendRefreshView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public FriendRefreshView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHandler();
initDragHelper();
initListView();
initRainbowView();
setBackgroundColor(Color.parseColor(“#000000”));
onDetectScrollListener = this;
}
….
}
这里我们还是利用handler来处理LoadingView 执行刷新时的转动动画和stopRefresh时滚动到初始位置的位移动画。

/**
* 初始化handler,当ViewDragHelper释放了mContentView时,
* 我们通过循环发送消息刷新mRainbowView的位置和角度
*/
private void initHandler() {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
if (rainbowTop > rainbowStartTop) {
rainbowTop -= 10;
requestLayout();
mHandler.sendEmptyMessageDelayed(0, 15);
}
break;
case 1:
if (rainbowTop <= rainbowStickyTop) {
if (rainbowTop < rainbowStickyTop) {
rainbowTop += 10;
if (rainbowTop > rainbowStickyTop) {
rainbowTop = rainbowStickyTop;
}
}
mRainbowView.setRotation(rainbowRotateAngle -= 10);
}else {
mRainbowView.setRotation(rainbowRotateAngle += 10);
}

                requestLayout();

                mHandler.sendEmptyMessageDelayed(1, 15);
                break;
        }
    }
};

}
初始化ViewDragHelper,已经是我们的老朋友了,有不熟悉的朋友可以参考我上一篇分享–实现小米应用我的小米

/**
* 初始化mDragHelper,我们处理拖动的核心类
*/
private void initDragHelper() {
mDragHelper = ViewDragHelper.create(this, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View view, int i) {
return view == mContentView && !bViewHelperSettling;
}

    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        return 0;
    }

    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        return top;
    }

    @Override
    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
        super.onViewPositionChanged(changedView, left, top, dx, dy);
        if (changedView == mContentView) {
            int lastContentTop = currentTop;
            if (top >= 0) {
                currentTop = top;
            }else {
                top = 0;
            }
            int lastTop = rainbowTop;
            int rTop = top + rainbowStartTop;
            if (rTop >= rainbowMaxTop) {
                if (!isRefreshing()) {
                    rainbowRotateAngle += (currentTop - lastContentTop) * 2;
                    rTop = rainbowMaxTop;
                    rainbowTop = rTop;
                    mRainbowView.setRotation(rainbowRotateAngle);
                }else {
                    rTop = rainbowMaxTop;
                    rainbowTop = rTop;
                }

            }else {
                if (isRefreshing()) {
                    rainbowTop = rainbowStickyTop;
                }else {
                    rainbowTop = rTop;
                    rainbowRotateAngle += (rainbowTop - lastTop) * 3;
                    mRainbowView.setRotation(rainbowRotateAngle);
                }
            }

            requestLayout();

        }
    }

    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        super.onViewReleased(releasedChild, xvel, yvel);
        mDragHelper.settleCapturedViewAt(0, 0);
        ViewCompat.postInvalidateOnAnimation(FriendRefreshView.this);
        //如果手势释放时,拖动的距离大于rainbowStickyTop,开始刷新
        if (currentTop >= rainbowStickyTop) {
            startRefresh();
        }

    }
});

}

@Override
public void computeScroll() {
super.computeScroll();
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
bViewHelperSettling = true;
}else {
bViewHelperSettling = false;
}
}
触摸事件的分发和拦截,核心部分。

/**
* 我们invoke 方法shouldIntercept来判断是否需要拦截事件,
* 拦截事件是为了将事件传递给mDragHelper来处理,我们这里只有当mContentView滑动到顶部
* 且mContentView没有处于滑动状态时才触发拦截。
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
mDragHelper.shouldInterceptTouchEvent(ev);
return shouldIntercept();
}

@Override
public boolean onTouchEvent(MotionEvent event) {
mDragHelper.processTouchEvent(event);
final int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_UP:
mLastMotionY = 0;
bDraging = false;
bScrollDown = false;
rainbowRotateAngle = 0;
break;
case MotionEvent.ACTION_MOVE:
int index = MotionEventCompat.getActionIndex(event);
int pointerId = MotionEventCompat.getPointerId(event, index);
if (shouldIntercept()) {
mDragHelper.captureChildView(mContentView, pointerId);
}
break;
}
return true;
}

/**
* 判断是否需要拦截触摸事件
* @return
*/
private boolean shouldIntercept() {
if (bDraging) return true;
int childCount = mContentView.getChildCount();
if (childCount > 0) {
View firstChild = mContentView.getChildAt(0);
if (firstChild.getTop() >= 0
&& firstItem == 0 && currentTop == 0
&& bScrollDown) {
return true;
}else return false;
}else {
return true;
}
}

/**
* 判断mContentView是否处于顶部
* @return
*/
private boolean checkIsTop() {
int childCount = mContentView.getChildCount();
if (childCount > 0) {
View firstChild = mContentView.getChildAt(0);
if (firstChild.getTop() >= 0
&& firstItem == 0 && currentTop == 0) {
return true;
}else return false;
}else {
return false;
}
}
measure和layout,我们的老朋友了,不多解释。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
sWidth = MeasureSpec.getSize(widthMeasureSpec);
sHeight = MeasureSpec.getSize(heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
LayoutParams contentParams = (LayoutParams) mContentView.getLayoutParams();
contentParams.left = 0;
contentParams.top = 0;
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
LayoutParams contentParams = (LayoutParams) mContentView.getLayoutParams();
mContentView.layout(contentParams.left, currentTop,
contentParams.left + sWidth, currentTop + sHeight);

mRainbowView.layout(rainbowRadius, rainbowTop,
        rainbowRadius * 2 , rainbowTop + rainbowRadius);

}
自定义ListView,处理触摸事件

private float mLastMotionX;
private float mLastMotionY;

/**
* 对ListView的触摸事件进行判断,是否处于滑动状态
*/
private class FriendRefreshListView extends ListView {

public FriendRefreshListView(Context context) {
    this(context, null);
}

public FriendRefreshListView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public FriendRefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    setBackgroundColor(Color.parseColor("#ffffff"));
}

/*当前活动的点Id,有效的点的Id*/
protected int mActivePointerId = INVALID_POINTER;

/*无效的点*/
private static final int INVALID_POINTER = -1;

@Override
public boolean onTouchEvent(MotionEvent ev) {
    final int action = ev.getActionMasked();
    switch (action) {
        case MotionEvent.ACTION_DOWN:
            int index = MotionEventCompat.getActionIndex(ev);
            mActivePointerId = MotionEventCompat.getPointerId(ev, index);
            if (mActivePointerId == INVALID_POINTER)
                break;
            mLastMotionX = ev.getX();
            mLastMotionY = ev.getY();
            break;

        case MotionEvent.ACTION_MOVE:
            int indexMove = MotionEventCompat.getActionIndex(ev);
            mActivePointerId = MotionEventCompat.getPointerId(ev, indexMove);

            if (mActivePointerId == INVALID_POINTER) {

            }else {
                final float y = ev.getY();
                float dy = y - mLastMotionY;
                if (checkIsTop() && dy >= 1.0f) {
                    bScrollDown = true;
                    bDraging = true;
                }else {
                    bScrollDown = false;
                    bDraging = false;
                }
                mLastMotionX = y;
            }
            break;

        case MotionEvent.ACTION_UP:
            mLastMotionY = 0;
            break;
    }
    return super.onTouchEvent(ev);
}

}

public void setAdapter(BaseAdapter adapter) {
if (mContentView != null) {
mContentView.setAdapter(adapter);
}
}
暴露onRefreshListener接口,和startRefresh和stopRefresh方法,供外部调用。

Handler mHandler;
public void startRefresh() {
if (!isRefreshing()) {
mHandler.removeMessages(0);
mHandler.removeMessages(1);
mHandler.sendEmptyMessage(1);
mState = State.REFRESHING;
invokeListner();
}

}

private void invokeListner() {
if (mRefreshLisenter != null) {
mRefreshLisenter.onRefresh();
}
}

public void stopRefresh() {
mHandler.removeMessages(1);
mHandler.sendEmptyMessage(0);
mState = State.NORMAL;
}

public void setOnRefreshListener(OnRefreshListener listener) {
this.mRefreshLisenter = listener;
}

public interface OnRefreshListener {
public void onRefresh();
}
更多的细节,大家可以下源码参考,最后还是提供最终的运行效果图,因为附件有容量限制,我只好分成2部分上传了。

下拉刷新.gif

下拉刷新.gif

文/ALIOUS(简书作者)
原文链接:http://www.jianshu.com/p/1ca0caf5fd8b
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值