Android 微信刷新&自带下拉刷新SwipeRefreshLayout(一)

前言:不知道还能这样坚持android多久啊,不管未来怎样,努力总还是会有收获的,h5之余还是要练练android的,不然就真忘记啦~~哈哈!!

今天要研究的是android v4自带的下拉刷新SwipeRefreshLayout,本来很久很久以前就想去研究一下它的,总是说自己没时间,其实时间挤挤还是有的,不废话了,先看一下最后用SwipeRefreshLayout实现的仿微信下拉刷新的效果图:

这里写图片描述

效果图看不出来那个美女拖动的效果,真实效果是有拖动的。

总体来说效果还是不错的,我们来看看源码然后一步一步实现其效果吧。

首先我们找到android.support.v4.widget.SwipeRefreshLayout这个类,用法呢很简单,就是在viewgroup外面套一层就可以了:

比如这样的:

<android.support.v4.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/id_swipe"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        ></LinearLayout>
  </android.support.v4.widget.SwipeRefreshLayout>

然后你就可以控制刷新了,刷新的回调:

swipe= (SwipeRefreshLayout) findViewById(R.id.id_swipe);
        swipe.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        swipe.setRefreshing(false);
                    }
                },3000);
            }
        });

重置刷新状态(控制刷新动作):

swipe.setRefreshing(false);

是不是soeasy呢?

其实现的原理是:

  1. 在onInterceptTouchEvent事件中做事件的拦截,当子控件到达顶部位置的时候并且滑动的距离大于最小滑动距离,则拦截子控件事件,自己处理。
@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
     ensureTarget();
      //当子控件滑动了,或者刷新指示器view正在回到初始状态的时候
        //不拦截事件
        if (!isEnabled() || mReturningToStart || canChildScrollUp()
                || mRefreshing || mNestedScrollInProgress) {
            // Fail fast if we're not in a state where a swipe is possible
            return false;
        }
          switch (action) {
          ....
          case MotionEvent.ACTION_MOVE:
                if (mActivePointerId == INVALID_POINTER) {
                    Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
                    return false;
                }

                pointerIndex = ev.findPointerIndex(mActivePointerId);
                if (pointerIndex < 0) {
                    return false;
                }
                final float y = ev.getY(pointerIndex);
                startDragging(y);
                break;
                .....

     }

我们看到,在判断之前都会调用一个ensureTarget方法:

private void ensureTarget() {
      //找到我们的targetview,也就是第一个子控件
        if (mTarget == null) {
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                if (!child.equals(mCircleView)) {
                    mTarget = child;
                    break;
                }
        }

然后看到一个canChildScrollUp方法:

主要就是判断是否当前targetview可以滑动(没有到达顶部)

 public boolean canChildScrollUp() {
        if (mChildScrollUpCallback != null) {
            return mChildScrollUpCallback.canChildScrollUp(this, mTarget);
        }
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (mTarget instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) mTarget;
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                        .getTop() < absListView.getPaddingTop());
            } else {
                return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(mTarget, -1);
        }
    }

因为我们知道,只有控件到达顶部后,我们才能下拉刷新,这个方法是判断控件没有到达顶部的,所以返回true就拦截掉子控件的事件。

然后就是监听手指的滑动:

case MotionEvent.ACTION_MOVE:
                if (mActivePointerId == INVALID_POINTER) {
                    Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
                    return false;
                }

                pointerIndex = ev.findPointerIndex(mActivePointerId);
                if (pointerIndex < 0) {
                    return false;
                }
                final float y = ev.getY(pointerIndex);
                startDragging(y);
                break;
  @SuppressLint("NewApi")
    private void startDragging(float y) {
        final float yDiff = y - mInitialDownY;
        //当滑动的距离>系统最小滑动距离,就认为开始下拉刷新了
        if (yDiff > mTouchSlop && !mIsBeingDragged) {
            mInitialMotionY = mInitialDownY + mTouchSlop;
            mIsBeingDragged = true;
            mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
        }
    }

都知道android的事件传递机制,当父控件的onInterceptTouchEvent返回true之后,事件将会交给父控件自己处理,也就是会调用onTouchEvent方法:

2、监听onTouchEvent方法,根据手指滑动的距离,改变indicatorview的位置

 @Override
    public boolean onTouchEvent(MotionEvent ev) {
    ....
 case MotionEvent.ACTION_MOVE: {
                pointerIndex = ev.findPointerIndex(mActivePointerId);
                if (pointerIndex < 0) {
                    Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
                    return false;
                }
                final float y = ev.getY(pointerIndex);
                startDragging(y);
                if (mIsBeingDragged) {
                    final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
                    if (overscrollTop > 0) {
                        //根据手指移动的距离改变指示器view的位置
                        moveSpinner(overscrollTop);
                        setHeaderMarginOffset(overscrollTop/2);
                    } else {
                        return false;
                    }
                }
                break;
            }
            ....
            }

3、在onTouchEvent方法中,当手指抬起的时候,我们就判断指示器view移动的位置,当大于等于刷新位置的时候,此时开始刷新并回调。

case MotionEvent.ACTION_UP: {
                pointerIndex = ev.findPointerIndex(mActivePointerId);
                if (pointerIndex < 0) {
                    Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
                    return false;
                }

                if (mIsBeingDragged) {
                    final float y = ev.getY(pointerIndex);
                    final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
                    mIsBeingDragged = false;
                    //判断手指滑动的距离是否大于刷新距离,大于等于则刷新否则回到原位
                    finishSpinner(overscrollTop);
                    startHeaderBackAni();
                    mProgress.stop();
                }
                mActivePointerId = INVALID_POINTER;
                return false;
            }
 private void finishSpinner(float overscrollTop) {
        if (overscrollTop > mTotalDragDistance) {
            setRefreshing(true, true /* notify */);
        } else {
            // cancel refresh
            mRefreshing = false;
            mProgress.setProgressRotation(0);
            Animation.AnimationListener listener = null;
            if (!mScale) {
                listener = new Animation.AnimationListener() {

                    @Override
                    public void onAnimationStart(Animation animation) {
                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {
                        if (!mScale) {
                            startScaleDownAnimation(null);
                        }
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {
                    }

                };
            }
            animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);
        }
    }

好吧!!整个流程下来还是非常容易的,下面让我们来分解下SwipeRefreshLayout,为了更好的研究源码,我们直接从v4中拖一份出来:

这里写图片描述

当然会有些报错,直接修改掉就可以了,相信这个能力你还是有的。

见过SwipeRefreshLayout效果的都知道,我们下拉刷新的时候,会出现一个进度条,这个进度条就是MaterialProgressDrawable,跟我们5.0以上的手机的progressbar的效果是一样的哈~~ 但是如果需要兼容5.0以前的手机,progressbar就有点鸡肋了,既然MaterialProgressDrawable有一个这样的效果,并且还兼容低版本,好吧! 我们试着搞一下它哈,其实最终的效果也就是我们最开始效果图的屏幕中间的效果图:

这里写图片描述

好吧,我已经拖出来了,要去拷的童鞋可以去我的git项目中拖哈:

https://github.com/913453448/EleDemo/blob/master/app/src/main/java/com/yasin/eledemo/swipe/MaterialProgressDrawable.java

实现原理很简单:就是动态的修改弧形的起始角度跟扫过的角度,然后动态的修改canvas的旋转角度,我就不详解啦~~

用法很简单:

直接new一个对象,然后设置给ImageView,最后开启动画就可以了:

MaterialProgressDrawable drawable1=new MaterialProgressDrawable(this,container);
        //旋转第一遍的时候是否显示箭头
        drawable1.showArrow(true);
        //设置箭头的缩放
        drawable1.setArrowScale(1);
        //设置进度条每次旋转的颜色
        drawable1.setColorSchemeColors(Color.RED,Color.GREEN);
        //设置drawable的透明度
        drawable1.setAlpha(255);
        //把drawable设置给imageview
        mShopCart.setImageDrawable(drawable1);
        drawable1.start();

好哈!!穿插的内容哈~~~当然,小伙伴也可以直接拖走,因为毕竟是系统的控件,兼容性无可挑剔啊,比一些第三方库强多了,这算不算SwipeRefreshLayout给我们带来的福利呢?

好啦~回到我们的SwipeRefreshLayout,当SwipeRefreshLayout创建的时候,我们需要创建一个指示器view(ImageView),也就是我们改造过后的微信小图标:

public SwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
。。。
        createProgressView();
        。。。

然后我们需要把创建的MaterialProgressDrawable设置给这个指示器view(ImageView):

 private void createProgressView() {
        mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT);
        mProgress = new WechatProgressDrawable(getContext(), this);
        mCircleView.setImageDrawable(mProgress);
        mCircleView.setVisibility(View.GONE);
        addView(mCircleView);
    }

最后,指示器view是有了,我们需要把它放入SwipeRefreshLayout的正确位置(onLayout):

 @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        final int width = getMeasuredWidth();
        final int height = getMeasuredHeight();
        if (getChildCount() == 0) {
            return;
        }
        if (mTarget == null) {
            ensureTarget();
        }
        if (mTarget == null) {
            return;
        }
        final View child = mTarget;
        final int childLeft = getPaddingLeft();
        final int childTop = getPaddingTop();
        final int childWidth = width - getPaddingLeft() - getPaddingRight();
        final int childHeight = height - getPaddingTop() - getPaddingBottom();
        child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
        int circleWidth = mCircleView.getMeasuredWidth();
        int circleHeight = mCircleView.getMeasuredHeight();
        //仿微信的下拉刷新小图片摆放的位置为(父布局最左边100px的位置)
        mCircleView.layout((100), mCurrentTargetOffsetTop,
                (100+circleWidth), mCurrentTargetOffsetTop + circleHeight);
    }

好啦!!!SwipeRefreshLayout的基本流程差不多就讲完啦~当然SwipeRefreshLayout远不止我讲的这些内容哈~ 我讲的只是核心部分,具体的细节部分小伙伴还是需要自己去看看源码的啦!!!

本节就先到这里了,下一节我们就来用SwipeRefreshLayout实现一个仿微信下拉刷新的效果(看完这一节想必小伙伴也有点感觉了哈,可以自己先尝试的做一下,相信自己能行的)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值