这段时间在高仿微信,图方便就把整体的效果也展示了,读者关注刷新页面即可。布局主要就是一个SwipeRefreshLayout内嵌一个RecyclerView,滑动到顶端向下拖动时,出来的进度圈是朋友圈的那个彩虹圈,位置在左边,而且随着向下拖动会不断绕中心转啊转,此外,进度圈在到达某个位置后就不会再往下了。跟默认效果不同的还有recyclerview,默认是主布局是不会跟着拖动的,而微信的有一个拖动反弹效果,背景是黑色。开始刷新后,主布局反弹到头部,进度圈在那里转啊转,刷新完毕后进度圈就消失了,整个过程就是这样。那么就一步一步来.
1. 调整进度圈位置
首先要将进度圈调整到左边,根据View的绘制原理,进度圈的位置应该是由父布局也就是SwipeRefreshLayout里的onLayout方法决定的,看看源码:
@Overrideprotected 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();mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop,(width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight);}其中的mTarget就是主布局也就是recyclerview,而mCircleView就是转载进度圈的View,因此应该把最后一句注释掉,改为:
//mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop,//(width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight);//修改进度圈的X坐标使之位于左边mCircleView.layout(childLeft, mCurrentTargetOffsetTop,childLeft+circleWidth, mCurrentTargetOffsetTop + circleHeight);这样你就会很高兴地发现进度圈已经调到左边了。
case MotionEvent.ACTION_MOVE: {pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);if (pointerIndex < 0) {Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");return false;}final float y = MotionEventCompat.getY(ev, pointerIndex);//记录手指移动的距离,mInitialMotionY是初始的位置,DRAG_RATE是拖拽因子,默认为0.5。final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;//赋值给mTarget的top使之产生拖动效果mTarget.setTranslationY(overscrollTop);if (mIsBeingDragged) {if (overscrollTop > 0) {moveSpinner(overscrollTop);} else {return false;}}break;}case MotionEvent.ACTION_UP: {//手指松开时启动动画回到头部mTarget.animate().translationY(0).setDuration(200).start();pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);if (pointerIndex < 0) {Log.e(LOG_TAG, "Got ACTION_UP event but don"t have an active pointer id.");return false;}final float y = MotionEventCompat.getY(ev, pointerIndex);final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;mIsBeingDragged = false;finishSpinner(overscrollTop);mActivePointerId = INVALID_POINTER;return false;}不相关的我都略过了,修改的地方我也注释了,很清晰。这样就解决了拖动反弹的问题,得益于SwipeRefreshLayout的框架,不用考虑冲突问题,修改起来还是很简单的。
public void setProgressView(MaterialProgressDrawable mProgress){this.mProgress = mProgress;mCircleView.setImageDrawable(mProgress);}要在CustomProgressDrawable中绘制自定义的图标,就需要暴露一个setBitmap的方法以便绘制。上篇文章提到,手指移动时会调用moveSpinner方法,并把移动的距离传进去,该方法内首先会经过一堆数学的处理得出一个rotation,再把它传入mProgress的setProgressRotation,也就是说setProgressRotation方法是通过传入的角度来转圈圈的。朋友圈的效果就是一直让中心转,所以很容易改写:
private float rotation;private Bitmap mBitmap;public void setBitmap(Bitmap mBitmap) {this.mBitmap = mBitmap;}@Overridepublic void setProgressRotation(float rotation) {//取负号是为了和微信保持一致,下拉时逆时针转加载时顺时针转,旋转因子是为了调整转的速度。this.rotation = -rotation*ROTATION_FACTOR;invalidateSelf();}@Overridepublic void draw(Canvas c) {Rect bound = getBounds();c.rotate(rotation,bound.exactCenterX(),bound.exactCenterY());Rect src = new Rect(0,0,mBitmap.getWidth(),mBitmap.getHeight());c.drawBitmap(mBitmap,src,bound,paint);}就是不断旋转canvas再绘制bitmap。这样你就会很高兴地发现下拉的时候圈圈也转起来了。
private void moveSpinner(float overscrollTop) {…//setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */);//最终刷新的位置int endTarget;if (!mUsingCustomStart) {//没有修改使用默认的值endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsetTop));} else {//否则使用定义的值endTarget = (int) mSpinnerFinalOffset;}if(targetY>=endTarget){//下移的位置超过最终位置后就不再下移,第一个参数为偏移量setTargetOffsetTopAndBottom(0, true /* requires update */);}else{//否则继续继续下移setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */);}}这里先计算出一个endTarget,就是最终的位置,其他注释的比较详细不说了,这样就限制住了下移的位置。
@Overridepublic void start() {mAnimation.reset();mRing.storeOriginals();// Already showing some part of the ringif (mRing.getEndTrim() != mRing.getStartTrim()) {mFinishing = true;mAnimation.setDuration(ANIMATION_DURATION/2);// 将转圈圈的动画传入mParent.startAnimation(mAnimation);} else {mRing.setColorIndex(0);mRing.resetOriginals();mAnimation.setDuration(ANIMATION_DURATION);// 将转圈圈的动画传入mParent.startAnimation(mAnimation);}}主要其实就最后一句,将转圈圈的动画传入,mAnimation就是默认的转动动画,感兴趣可以自己去看看,我们只需要自定义转圈圈的动画并传入该方法就可以了。有了刚才的setProgressRotation方法,只需要定义一个动画并不断改变rotation的值并执行这个方法就好了,代码如下:
private void setupAnimation() {//初始化旋转动画mAnimation = new Animation(){@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {setProgressRotation(-interpolatedTime);}};mAnimation.setDuration(5000);//无限重复mAnimation.setRepeatCount(Animation.INFINITE);mAnimation.setRepeatMode(Animation.RESTART);//均匀转速mAnimation.setInterpolator(new LinearInterpolator());}@Overridepublic void start() {mParent.startAnimation(mAnimation);}这样就OK了!
private void startScaleDownAnimation(Animation.AnimationListener listener) {//mScaleDownAnimation = new Animation() {//@Override//public void applyTransformation(float interpolatedTime, Transformation t) {//setAnimationProgress(1 - interpolatedTime);//}//};//最终的偏移量就是mCircleView距离顶部的高度final int deltaY = -mCircleView.getBottom();mScaleDownAnimation = new TranslateAnimation(0,0,0,deltaY);//mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION);mScaleDownAnimation.setDuration(500);mCircleView.setAnimationListener(listener);mCircleView.clearAnimation();mCircleView.startAnimation(mScaleDownAnimation);}也就是一个偏移动画~
CustomProgressDrawable drawable = new CustomProgressDrawable(this,mRefreshLayout);Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.moments_refresh_icon);drawable.setBitmap(bitmap);mRefreshLayout.setProgressView(drawable);mRefreshLayout.setBackgroundColor(Color.BLACK);mRefreshLayout.setProgressBackgroundColorSchemeColor(Color.BLACK);mRefreshLayout.setOnRefreshListener(new CustomSwipeRefreshLayout.OnRefreshListener(){@Overridepublic void onRefresh() {final Handler handler = new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);mRefreshLayout.setRefreshing(false);}};new Thread(new Runnable() {@Overridepublic void run() {try {//在子线程睡眠三秒后发送消息停止刷新。Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}handler.sendEmptyMessage(0);}}).start();}});以上就基本通过修改SwipeRefreshLayout的源码仿照了朋友圈的下拉刷新效果了。从源码可以看出SwipeRefreshLayout确实是写得比较封闭的,不修改源码是基本没法自定义样式的,不过这样跟着源码过了一遍思路就比较清晰了。以后如果有机会再试着封装一下吧~
public class CustomProgressDrawable extends MaterialProgressDrawable{//旋转因子,调整旋转速度private static final int ROTATION_FACTOR = 5*360;//加载时的动画private Animation mAnimation;private View mParent;private Bitmap mBitmap;//旋转角度private float rotation;private Paint paint;public CustomProgressDrawable(Context context, View parent) {super(context, parent);mParent = parent;paint = new Paint();setupAnimation();}private void setupAnimation() {//初始化旋转动画mAnimation = new Animation(){@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {setProgressRotation(-interpolatedTime);}};mAnimation.setDuration(5000);//无限重复mAnimation.setRepeatCount(Animation.INFINITE);mAnimation.setRepeatMode(Animation.RESTART);//均匀转速mAnimation.setInterpolator(new LinearInterpolator());}@Overridepublic void start() {mParent.startAnimation(mAnimation);}public void setBitmap(Bitmap mBitmap) {this.mBitmap = mBitmap;}@Overridepublic void setProgressRotation(float rotation) {//取负号是为了和微信保持一致,下拉时逆时针转加载时顺时针转,旋转因子是为了调整转的速度。this.rotation = -rotation*ROTATION_FACTOR;invalidateSelf();}@Overridepublic void draw(Canvas c) {Rect bound = getBounds();c.rotate(rotation,bound.exactCenterX(),bound.exactCenterY());Rect src = new Rect(0,0,mBitmap.getWidth(),mBitmap.getHeight());c.drawBitmap(mBitmap,src,bound,paint);}}以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。