上一篇博客Android 微信刷新&自带下拉刷新SwipeRefreshLayout(一)我们简单介绍了一下SwipeRefreshLayout的原理,以及对它对了一个简单的拆分,这一节我们就来改改它的代码,实现一个微信朋友圈的下拉刷新效果:
我们先看一下系统自带的下拉刷新效果:
下拉刷新的时候,可以看到一个进度条view,这个进度条view上一篇博客有抽取出来的,不懂的小伙伴可以去看看哈。
下面说一下仿微信下拉刷新的思路:
1、修改进度条view为我们微信的图片,然后给个动画让图片旋转
2、修改微信图片出现的位置为左上角
3、当下拉的时候,header图片跟着下拉
好啦!明确了我们的目的之后,我们就开始修改代码了。
首先替换掉默认的加载进度条为微信的图片
我们看到MaterialProgressDrawable这个类,这个就是进度条view(上一节也有提到),我们仿照着自己写一个叫WechatProgressDrawable的,代码比较简单,我就直接上代码了:
package com.yasin.eledemo.swipe;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import com.yasin.eledemo.R;
import java.util.ArrayList;
/**
* Created by leo on 17/5/14.
*/
public class WechatProgressDrawable extends Drawable implements Animatable{
private Resources mResources;
private View mParent;
/** The list of animators operating on this drawable. */
private final ArrayList<Animation> mAnimators = new ArrayList<Animation>();
// Maps to ProgressBar.Large style
static final int LARGE = 0;
// Maps to ProgressBar default style
static final int DEFAULT = 1;
static final float LARGE_SIZE=56;
static final float DEFAULT_SIZE=40;
static final long ANIMATION_DURATION=3000;
private float mWidth,mHeight;
private Animation mAnimation;
private float mRotation;
private Bitmap mBitmap;
public WechatProgressDrawable(Context context, View parent) {
mParent = parent;
mResources = context.getResources();
updateSizes(DEFAULT);
setupAnimators();
mBitmap= BitmapFactory.decodeResource(mResources, R.mipmap.icon_wechat1);
mBitmap=Bitmap.createScaledBitmap(mBitmap,(int)mWidth,(int)mHeight,true);
}
public void updateSizes( int size) {
if (size == LARGE) {
setSizeParameters(LARGE_SIZE, LARGE_SIZE);
} else {
setSizeParameters(DEFAULT_SIZE, DEFAULT_SIZE);
}
}
private void setSizeParameters(float width, float height) {
final DisplayMetrics metrics = mResources.getDisplayMetrics();
final float screenDensity = metrics.density;
mWidth = width * screenDensity;
mHeight = height * screenDensity;
}
private void setupAnimators() {
final Animation animation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float rotation=interpolatedTime*1.0f*360;
setProgressRotation(rotation);
}
};
animation.setRepeatCount(Animation.INFINITE);
animation.setRepeatMode(Animation.RESTART);
animation.setInterpolator(new LinearInterpolator());
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
// do nothing
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
mAnimation = animation;
}
@Override
public void draw(Canvas c) {
final Rect bounds = getBounds();
final int saveCount = c.save();
c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
c.drawBitmap(mBitmap,0,0,null);
c.restoreToCount(saveCount);
}
/**
* Set the amount of rotation to apply to the progress spinner.
*
* @param rotation Rotation is from [0..1]
*/
public void setProgressRotation(float rotation) {
this.mRotation=rotation;
invalidateSelf();
}
@Override
public int getIntrinsicHeight() {
return (int) mHeight;
}
@Override
public int getIntrinsicWidth() {
return (int) mWidth;
}
@Override
public void setAlpha(int alpha) {
}
public int getAlpha() {
return 255;
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void start() {
mAnimation.reset();
mAnimation.setDuration(ANIMATION_DURATION);
mParent.startAnimation(mAnimation);
}
@Override
public void stop() {
mParent.clearAnimation();
setProgressRotation(0);
}
@Override
public boolean isRunning() {
final ArrayList<Animation> animators = mAnimators;
final int N = animators.size();
for (int i = 0; i < N; i++) {
final Animation animator = animators.get(i);
if (animator.hasStarted() && !animator.hasEnded()) {
return true;
}
}
return false;
}
}
我们来测试一下:
WechatProgressDrawable drawable1=new WechatProgressDrawable(this,container);
drawable1.setAlpha(255);
mShopCart.setImageDrawable(drawable1);
drawable1.start();
卧槽,gif录制出问题了还是咋了,调用了start方法后中间那个图标是可以旋转的。
好啦!!搞定基础的进度条view后,我们需要把它放进SwipeRefreshLayout中,在SwipeRefreshLayout中我们看到原有的
int mSpinnerOffsetEnd;
MaterialProgressDrawable mProgress;
private Animation mScaleAnimation;
我们把 MaterialProgressDrawable对象改为
int mSpinnerOffsetEnd;
WechatProgressDrawable mProgress;
private Animation mScaleAnimation;
好啦!可能有些地方的api你得删掉了,比如说原有的:
mProgress.showArrow(true);
这些方法你就得删掉了,因为我们的WechatProgressDrawable里面没有这些方法,并且我们也不需要了。
接下来我们需要把WechatProgressDrawable摆放在左上角的位置,让它可以从左上角出来:
在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();
mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop,
(width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight);
}
改过之后:
@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();
mCircleView.layout((100), mCurrentTargetOffsetTop,
(100+circleWidth), mCurrentTargetOffsetTop + circleHeight);
}
我就简单的设置其在左边100px的位置了。
好吧~修改完毕后,我们就可以简单的玩起来了~~~我就不测试了哈!!
为了和微信几乎一样的效果,我们需要加一个header(也就是一个imageview),然后当往下拉的时候,图片也往下拉,手指松开后,图片回到原有的位置。
其实呢也很简单,
<?xml version="1.0" encoding="utf-8"?>
<com.yasin.eledemo.swipe.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"
>
<ImageView
android:id="@+id/id_head_view"
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@mipmap/icon_beaty"
android:layout_marginTop="-40dp"
android:scaleType="centerCrop"
/>
可以看到,我在LinearLayout下面直接放了一个imageview,然后设置其默认样式为 android:layout_marginTop=”-40dp。
然后我们通过下拉的距离改变android:layout_marginTop来实现我们的图片滑动操作。
首先我们需要在SwipeRefreshLayout中定义一个view叫:
private View mHeadView;
然后我们需要找到我们的 private View mHeadView;并且记录一下原始的margintop值:
private float originHeaderMargin;
private void ensureTarget() {
// Don't bother getting the parent height if the parent hasn't been laid
// out yet.
if (mTarget == null) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (!child.equals(mCircleView)) {
mTarget = child;
break;
}
}
}
if(mTarget!=null){
mHeadView=mTarget.findViewById(R.id.id_head_view);
if(mHeadView!=null&&mHeadView.getLayoutParams() instanceof MarginLayoutParams){
MarginLayoutParams lp= (MarginLayoutParams) mHeadView.getLayoutParams();
if(originHeaderMargin==0){
originHeaderMargin=lp.topMargin;
headerBackAni.setDuration(ANIMATE_TO_START_DURATION);
headerBackAni.setInterpolator(new AccelerateDecelerateInterpolator());
}
}
}
}
ensureTarget这个方法我上一节有介绍哈~~
然后当手指滑动的时候,我们需要根据滑动的距离改变margintop值,所以我们进入到SwipeRefreshLayout的onTouchEvent方法中修改下逻辑:
@Override
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) {
Log.e("TAG", "onTouchEvent:--->"+overscrollTop);
moveSpinner(overscrollTop);
//开始控制headerview的滑动位置
setHeaderMarginOffset(overscrollTop/2);
} else {
return false;
}
}
break;
}
private void setHeaderMarginOffset(float offset) {
if (mHeadView != null && mHeadView.getLayoutParams() instanceof MarginLayoutParams) {
MarginLayoutParams lp = (MarginLayoutParams) mHeadView.getLayoutParams();
lp.topMargin = (int) (originHeaderMargin+offset);
mHeadView.setLayoutParams(lp);
}
}
然后手指放开的时候,动画回到原来的位置:
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);
//header动画回到原来的位置
startHeaderBackAni();
mProgress.stop();
}
mActivePointerId = INVALID_POINTER;
return false;
}
}
private void startHeaderBackAni(){
mHeadView.clearAnimation();
mHeadView.startAnimation(headerBackAni);
}
private final Animation headerBackAni=new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (mHeadView != null && mHeadView.getLayoutParams() instanceof MarginLayoutParams) {
MarginLayoutParams lp = (MarginLayoutParams) mHeadView.getLayoutParams();
float startMargin=lp.topMargin;
float endMargin= originHeaderMargin;
float margin=startMargin+(endMargin-startMargin)*interpolatedTime;
lp.topMargin= (int) margin;
mHeadView.setLayoutParams(lp);
}
}
};
好啦!!我们的改造就撸完了~~
附上项目的github链接:
https://github.com/913453448/EleDemo
欢迎入群~~~~