();
float offsetForWidth = 0;
for (int i = 0; i < str.length(); i++) {
int pos = str.charAt(i);
int key = sPointList.indexOfKey(pos);
if (key == -1) {
continue;
}
float[] points = sPointList.get(pos);
int pointCount = points.length / 4;
for (int j = 0; j < pointCount; j++) {
float[] line = new float[4];
for (int k = 0; k < 4; k++) {
float l = points[j * 4 + k];
// x
if (k % 2 == 0) {
line[k] = (l + offsetForWidth) * scale;
}
// y
else {
line[k] = l * scale;
}
}
list.add(line);
}
offsetForWidth += 57 + gapBetweenLetter;
}
return list;
}
}
================================================
FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/waterdrop/Circle.java
================================================
package com.scwang.smartrefresh.header.waterdrop;
/**
* Created by xiayong on 2015/6/25.
* 实心圆
*/
public class Circle {
public float x;//圆x坐标
public float y;//圆y坐标
public float radius;//圆半径
public int color;//圆的颜色
}
================================================
FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/waterdrop/WaterDropView.java
================================================
package com.scwang.smartrefresh.header.waterdrop;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import com.scwang.smartrefresh.layout.util.DensityUtil;
/**
* 下拉头中间的“水滴”
* Created by xiayong on 2015/6/23.
*/
public class WaterDropView extends View {
private Circle topCircle;
private Circle bottomCircle;
private Path mPath;
private Paint mPaint;
private int mMaxCircleRadius;//圆半径最大值
private int mMinCircleRaidus;//圆半径最小值
private static int STROKE_WIDTH = 2;//边线宽度
private final static int BACK_ANIM_DURATION = 180;
public WaterDropView(Context context) {
super(context);
initView(context, null);
}
public WaterDropView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context, attrs);
}
public WaterDropView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs);
}
private void initView(Context context, AttributeSet attrs) {
// setBackgroundColor(0xffbbff11);
topCircle = new Circle();
bottomCircle = new Circle();
mPath = new Path();
mPaint = new Paint();
mPaint.setColor(Color.GRAY);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(STROKE_WIDTH = DensityUtil.dp2px(0.5f));
mPaint.setShadowLayer(STROKE_WIDTH, 0, 0, 0xCC000000);
setLayerType(LAYER_TYPE_SOFTWARE, null);
int padding = 4 * STROKE_WIDTH;
setPadding(padding, padding, padding, padding);
mPaint.setColor(Color.GRAY);
mMaxCircleRadius = DensityUtil.dp2px(20);
mMinCircleRaidus = mMaxCircleRadius / 5;
topCircle.radius = (mMaxCircleRadius);
bottomCircle.radius = (mMaxCircleRadius);
topCircle.x = (STROKE_WIDTH + mMaxCircleRadius);
topCircle.y = (STROKE_WIDTH + mMaxCircleRadius);
bottomCircle.x = (STROKE_WIDTH + mMaxCircleRadius);
bottomCircle.y = (STROKE_WIDTH + mMaxCircleRadius);
}
public int getMaxCircleRadius() {
return mMaxCircleRadius;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//宽度:上圆和下圆的最大直径
int width = (int) ((mMaxCircleRadius + STROKE_WIDTH) * 2);
//高度:上圆半径 + 圆心距 + 下圆半径
int height = (int) Math.ceil(bottomCircle.y+bottomCircle.radius + STROKE_WIDTH * 2);
setMeasuredDimension(width + getPaddingLeft() + getPaddingRight(),
resolveSize(height + getPaddingTop() + getPaddingBottom(), heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateComleteState(getHeight());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingTop = getPaddingTop();
final int paddingLeft = getPaddingLeft();
final int paddingBottom = getPaddingBottom();
final int height = getHeight();
canvas.save();
if (height <= topCircle.radius * 2 + paddingTop + paddingBottom) {
canvas.translate(paddingLeft, height - topCircle.radius * 2 - paddingBottom);
canvas.drawCircle(topCircle.x, topCircle.y, topCircle.radius, mPaint);
} else {
canvas.translate(paddingLeft, paddingTop);
makeBezierPath();
canvas.drawPath(mPath, mPaint);
// canvas.drawCircle(topCircle.x, topCircle.y, topCircle.radius, mPaint);
// canvas.drawCircle(bottomCircle.x, bottomCircle.y, bottomCircle.radius, mPaint);
}
canvas.restore();
}
private void makeBezierPath() {
mPath.reset();
mPath.addCircle(topCircle.x, topCircle.y, topCircle.radius, Path.Direction.CCW);
if (bottomCircle.y > topCircle.y + DensityUtil.dp2px(1)) {
mPath.addCircle(bottomCircle.x, bottomCircle.y, bottomCircle.radius, Path.Direction.CCW);
//获取两圆的两个切线形成的四个切点
double angle = getAngle();
float top_x1 = (float) (topCircle.x - topCircle.radius * Math.cos(angle));
float top_y1 = (float) (topCircle.y + topCircle.radius * Math.sin(angle));
float top_x2 = (float) (topCircle.x + topCircle.radius * Math.cos(angle));
float top_y2 = top_y1;
float bottom_x1 = (float) (bottomCircle.x - bottomCircle.radius * Math.cos(angle));
float bottom_y1 = (float) (bottomCircle.y + bottomCircle.radius * Math.sin(angle));
float bottom_x2 = (float) (bottomCircle.x + bottomCircle.radius * Math.cos(angle));
float bottom_y2 = bottom_y1;
mPath.moveTo(topCircle.x, topCircle.y);
mPath.lineTo(top_x1, top_y1);
mPath.quadTo((bottomCircle.x - bottomCircle.radius),
(bottomCircle.y + topCircle.y) / 2,
bottom_x1,
bottom_y1);
mPath.lineTo(bottom_x2, bottom_y2);
mPath.quadTo((bottomCircle.x + bottomCircle.radius),
(bottomCircle.y + top_y2) / 2,
top_x2,
top_y2);
}
mPath.close();
}
/**
* 获得两个圆切线与圆心连线的夹角
*/
private double getAngle() {
if (bottomCircle.radius > topCircle.radius) {
throw new IllegalStateException("bottomCircle's radius must be less than the topCircle's");
}
return Math.asin((topCircle.radius - bottomCircle.radius) / (bottomCircle.y - topCircle.y));
}
/**
* 创建回弹动画
* 上圆半径减速恢复至最大半径
* 下圆半径减速恢复至最大半径
* 圆心距减速从最大值减到0(下圆Y从当前位置移动到上圆Y)。
*/
public Animator createAnimator() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(1, 0.001f).setDuration(BACK_ANIM_DURATION);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator1) {
WaterDropView.this.updateComleteState((float) valueAnimator1.getAnimatedValue());
WaterDropView.this.postInvalidate();
}
});
return valueAnimator;
}
/**
* 完成的百分比
*/
public void updateComleteState(int offset, int maxHeight) {
// float space = mMaxCircleRadius * 2 + getPaddingTop() + getPaddingBottom();
// updateComleteState(Math.max(0, 1f * (offset - space) / (maxHeight - space)));
}
/**
* 完成的百分比
*/
public void updateComleteState(float percent) {
float top_r = (float) (mMaxCircleRadius - 0.25 * percent * mMaxCircleRadius);
float bottom_r = (mMinCircleRaidus - mMaxCircleRadius) * percent + mMaxCircleRadius;
float bottomCricleOffset = 4 * percent * mMaxCircleRadius;
topCircle.radius = (top_r);
bottomCircle.radius = (bottom_r);
bottomCircle.y = (topCircle.y + bottomCricleOffset);
}
/**
* 完成的百分比
*/
public void updateComleteState(int height) {
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
float space = mMaxCircleRadius * 2 + paddingTop + paddingBottom;
if (height < space) {
topCircle.radius = mMaxCircleRadius;
bottomCircle.radius = mMaxCircleRadius;
bottomCircle.y = topCircle.y;
} else {
float limit = mMaxCircleRadius - mMinCircleRaidus;
float x = Math.max(0, height - space);
float y = (float) (limit * (1 - Math.pow(100, -x / DensityUtil.dp2px(200))));
topCircle.radius = mMaxCircleRadius - y / 4;
bottomCircle.radius = mMaxCircleRadius - y;
int validHeight = height - paddingTop - paddingBottom;
bottomCircle.y = validHeight - bottomCircle.radius;
}
}
public Circle getTopCircle() {
return topCircle;
}
public Circle getBottomCircle() {
return bottomCircle;
}
public void setIndicatorColor(int color) {
mPaint.setColor(color);
}
public int getIndicatorColor() {
return mPaint.getColor();
}
}
================================================
FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/waveswipe/AnimationImageView.java
================================================
/*
* Copyright (C) 2015 RECRUIT LIFESTYLE CO., LTD.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.scwang.smartrefresh.header.waveswipe;
import android.content.Context;
import android.view.animation.Animation;
import android.widget.ImageView;
/**
* @author amyu
*/
public class AnimationImageView extends ImageView {
/**
* AnimationのStartとEnd時にListenerにアレする
*/
private Animation.AnimationListener mListener;
/**
* コンストラクタ
* {@inheritDoc}
*/
public AnimationImageView(Context context) {
super(context);
}
/**
* {@link AnimationImageView#mListener} のセット
*
* @param listener {@link android.view.animation.Animation.AnimationListener}
*/
public void setAnimationListener(Animation.AnimationListener listener) {
mListener = listener;
}
/**
* ViewのAnimationのStart時にセットされたListenerの {@link android.view.animation.Animation.AnimationListener#onAnimationStart(Animation)}
* を呼ぶ
*/
@Override public void onAnimationStart() {
super.onAnimationStart();
if (mListener != null) {
mListener.onAnimationStart(getAnimation());
}
}
/**
* ViewのAnimationのEnd時にセットされたListenerの {@link android.view.animation.Animation.AnimationListener#onAnimationEnd(Animation)}
* (Animation)} を呼ぶ
*/
@Override public void onAnimationEnd() {
super.onAnimationEnd();
if (mListener != null) {
mListener.onAnimationEnd(getAnimation());
}
}
}
================================================
FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/waveswipe/DisplayUtil.java
================================================
/*
* Copyright (C) RECRUIT LIFESTYLE CO., LTD.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.scwang.smartrefresh.header.waveswipe;
import android.content.Context;
import android.content.res.Resources;
import android.util.DisplayMetrics;
/**
* @author amyu
*/
public class DisplayUtil {
private DisplayUtil(){}
/**
* 現在の向きが600dpを超えているかどうか
*
* @param context {@link Context}
* @return 600dpを超えているかどうか
*/
public static boolean isOver600dp(Context context) {
Resources resources = context.getResources();
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
return displayMetrics.widthPixels / displayMetrics.density >= 600;
}
}
================================================
FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/waveswipe/DropBounceInterpolator.java
================================================
package com.scwang.smartrefresh.header.waveswipe;
/*
* Copyright (C) 2015 RECRUIT LIFESTYLE CO., LTD.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context;
import android.util.AttributeSet;
import android.view.animation.Interpolator;
/**
* @author amyu
*
* {@link WaveView#mDropBounceHorizontalAnimator} と {@link WaveView#mDropVertexAnimator} にセットするInterpolator
* WavePullToRefresh/DropBounceInterpolator.gcxにグラフの詳細
*/
public class DropBounceInterpolator implements Interpolator {
public DropBounceInterpolator() {
}
@SuppressWarnings({"UnusedDeclaration"})
public DropBounceInterpolator(Context context, AttributeSet attrs) {
}
/**
* {@inheritDoc}
*
* @param v
* @return
*/
@Override
public float getInterpolation(float v) {
//y = -19 * (x - 0.125)^2 + 1.3 (0 <= x < 0.25)
//y = -6.5 * (x - 0.625)^2 + 1.1 (0.5 <= x < 0.75)
//y = 0 (0.25 <= x < 0.5 && 0.75 <= x <=1)
if (v < 0.25f) {
return -38.4f * (float) Math.pow(v - 0.125, 2) + 0.6f;
} else if (v >= 0.5 && v < 0.75) {
return -19.2f * (float) Math.pow(v - 0.625, 2) + 0.3f;
} else {
return 0;
}
}
}
================================================
FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/waveswipe/WaveView.java
================================================
/*
* Copyright (C) 2015 RECRUIT LIFESTYLE CO., LTD.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.scwang.smartrefresh.header.waveswipe;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.BounceInterpolator;
/**
* @author amyu
*
* 波と落ちる円を描画するView
*/
public class WaveView extends View implements ViewTreeObserver.OnPreDrawListener {
/**
* {@link WaveView#mDropCircleAnimator} のDuration
*/
private static final long DROP_CIRCLE_ANIMATOR_DURATION = 500;
/**
* {@link WaveView#mDropBounceVerticalAnimator} のDuration
*/
private static final long DROP_VERTEX_ANIMATION_DURATION = 500;
/**
* {@link WaveView#mDropBounceVerticalAnimator} と {@link WaveView#mDropBounceHorizontalAnimator}
* のDuration
*/
private static final long DROP_BOUNCE_ANIMATOR_DURATION = 500;
/**
* {@link WaveView#mDisappearCircleAnimator} のDuration
*/
private static final int DROP_REMOVE_ANIMATOR_DURATION = 200;
/**
* 波がくねくねしているDuration
*/
private static final int WAVE_ANIMATOR_DURATION = 1000;
/**
* 波の最大の高さ
*/
private static final float MAX_WAVE_HEIGHT = 0.2f;
/**
* 影の色
*/
private static final int SHADOW_COLOR = 0x99000000;
/**
* 円のRadius
*/
private float mDropCircleRadius = 100;
/**
* すべてを描画するPaint
*/
private Paint mPaint;
/**
* 画面の波を描画するためのPath
*/
private Path mWavePath;
/**
* 落ちる円の接線を描画するためのPath
*/
private Path mDropTangentPath;
/**
* 落ちる円を描画するためのPath
*/
private Path mDropCirclePath;
/**
* 影のPaint
*/
// private Paint mShadowPaint;
/**
* 影のPath
*/
private Path mShadowPath;
/**
* 落ちる円の座標を入れているRectF
*/
private RectF mDropRect;
/**
* Viewの横幅
*/
private int mWidth;
/**
* {@link WaveView#mDropCircleAnimator} でアニメーションしてる時の円の中心のY座標
*/
private float mCurrentCircleCenterY;
/**
* 円が落ちる最大の高さ
*/
private int mMaxDropHeight;
private boolean mIsManualRefreshing = false;
/**
* 落ちる円の高さが更新されたかどうか
*/
private boolean mDropHeightUpdated = false;
/**
* {@link WaveView#mMaxDropHeight} を更新するための一時的な値の置き場
*/
private int mUpdateMaxDropHeight;
/**
* 落ちてくる円についてくる三角形の一番上の頂点のAnimator
*/
private ValueAnimator mDropVertexAnimator;
/**
* 落ちた円が横に伸びるときのAnimator
*/
private ValueAnimator mDropBounceVerticalAnimator;
/**
* 落ちた縁が縦に伸びるときのAnimator
*/
private ValueAnimator mDropBounceHorizontalAnimator;
/**
* 落ちる円の中心座標のAnimator
*/
private ValueAnimator mDropCircleAnimator;
/**
* 落ちた円を消すためのAnimator
*/
private ValueAnimator mDisappearCircleAnimator;
/**
* 帰ってくる波ののAnimator
*/
private ValueAnimator mWaveReverseAnimator;
/**
* ベジェ曲線を引く際の座標
* 左側の2つのアンカーポイントでいい感じに右側にも
*/
private static final float[][] BEGIN_PHASE_POINTS = {
//1
{0.1655f, 0}, //ハンドル
{0.4188f, -0.0109f}, //ハンドル
{0.4606f, -0.0049f}, //アンカーポイント
//2
{0.4893f, 0.f}, //ハンドル
{0.4893f, 0.f}, //ハンドル
{0.5f, 0.f} //アンカーポイント
};
private static final float[][] APPEAR_PHASE_POINTS = {
//1
{0.1655f, 0.f}, //ハンドル
{0.5237f, 0.0553f}, //ハンドル
{0.4557f, 0.0936f}, //アンカーポイント
//2
{0.3908f, 0.1302f}, //ハンドル
{0.4303f, 0.2173f}, //ハンドル
{0.5f, 0.2173f} //アンカーポイント
};
private static final float[][] EXPAND_PHASE_POINTS = {
//1
{0.1655f, 0.f}, //ハンドル
{0.5909f, 0.0000f}, //ハンドル
{0.4557f, 0.1642f}, //アンカーポイント
//2
{0.3941f, 0.2061f}, //ハンドル
{0.4303f, 0.2889f}, //ハンドル
{0.5f, 0.2889f} //アンカーポイント
};
/**
* 各AnimatorのAnimatorUpdateListener
*/
private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
WaveView.this.postInvalidate();
}
};
/**
* Constructor
* {@inheritDoc}
*/
public WaveView(Context context) {
super(context);
getViewTreeObserver().addOnPreDrawListener(this);
initView();
}
/**
* Viewのサイズが決まったら {@link WaveView#mWidth} に横幅
* {@inheritDoc}
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWidth = w;
mDropCircleRadius = w / 14.4f;
updateMaxDropHeight((int) Math.min(Math.min(w, h), getHeight() - mDropCircleRadius));
super.onSizeChanged(w, h, oldw, oldh);
}
/**
* 描画されてから {@link WaveView#mMaxDropHeight} を更新する
* {@inheritDoc}
*/
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
if (mDropHeightUpdated) {
updateMaxDropHeight(mUpdateMaxDropHeight);
}
return false;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//引っ張ってる最中の波と終わったあとの波
// canvas.drawPath(mWavePath, mShadowPaint);
canvas.drawPath(mWavePath, mPaint);
if (!isInEditMode()) {
mWavePath.rewind();
//円が落ちる部分の描画
mDropTangentPath.rewind();
mDropCirclePath.rewind();
}
float circleCenterY = (Float) mDropCircleAnimator.getAnimatedValue();
float circleCenterX = mWidth / 2.f;
mDropRect.setEmpty();
//円の座標をRectFに保存
float scale = (Float) mDisappearCircleAnimator.getAnimatedValue();
float vertical = (Float) mDropBounceVerticalAnimator.getAnimatedValue();
float horizontal = (Float) mDropBounceHorizontalAnimator.getAnimatedValue();
mDropRect.set(circleCenterX - mDropCircleRadius * (1 + vertical) * scale
+ mDropCircleRadius * horizontal / 2,
circleCenterY + mDropCircleRadius * (1 + horizontal) * scale
- mDropCircleRadius * vertical / 2,
circleCenterX + mDropCircleRadius * (1 + vertical) * scale
- mDropCircleRadius * horizontal / 2,
circleCenterY - mDropCircleRadius * (1 + horizontal) * scale
+ mDropCircleRadius * vertical / 2);
float vertex = (Float) mDropVertexAnimator.getAnimatedValue();
mDropTangentPath.moveTo(circleCenterX, vertex);
//円の接点(p1,q),(p2,q)
double q =
(Math.pow(mDropCircleRadius, 2) + circleCenterY * vertex - Math.pow(circleCenterY, 2)) / (
vertex - circleCenterY);
//2次方程式解くための解の公式
double b = -2.0 * mWidth / 2;
double c =
Math.pow(q - circleCenterY, 2) + Math.pow(circleCenterX, 2) - Math.pow(mDropCircleRadius,
2);
double p1 = (-b + Math.sqrt(b * b - 4 * c)) / 2;
double p2 = (-b - Math.sqrt(b * b - 4 * c)) / 2;
mDropTangentPath.lineTo((float) p1, (float) q);
mDropTangentPath.lineTo((float) p2, (float) q);
mDropTangentPath.close();
mShadowPath.set(mDropTangentPath);
mShadowPath.addOval(mDropRect, Path.Direction.CCW);
mDropCirclePath.addOval(mDropRect, Path.Direction.CCW);
if (mDropVertexAnimator.isRunning()) {
// canvas.drawPath(mShadowPath, mShadowPaint);
} else {
// canvas.drawPath(mDropCirclePath, mShadowPaint);
}
canvas.drawPath(mDropTangentPath, mPaint);
canvas.drawPath(mDropCirclePath, mPaint);
}
@Override
protected void onDetachedFromWindow() {
if (mDisappearCircleAnimator != null) {
mDisappearCircleAnimator.end();
mDisappearCircleAnimator.removeAllUpdateListeners();
}
if (mDropCircleAnimator != null) {
mDropCircleAnimator.end();
mDropCircleAnimator.removeAllUpdateListeners();
}
if (mDropVertexAnimator != null) {
mDropVertexAnimator.end();
mDropVertexAnimator.removeAllUpdateListeners();
}
if (mWaveReverseAnimator != null) {
mWaveReverseAnimator.end();
mWaveReverseAnimator.removeAllUpdateListeners();
}
if (mDropBounceHorizontalAnimator != null) {
mDropBounceHorizontalAnimator.end();
mDropBounceHorizontalAnimator.removeAllUpdateListeners();
}
if (mDropBounceVerticalAnimator != null) {
mDropBounceVerticalAnimator.end();
mDropBounceVerticalAnimator.removeAllUpdateListeners();
}
super.onDetachedFromWindow();
}
private void initView() {
setUpPaint();
setUpPath();
resetAnimator();
mDropRect = new RectF();
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
private void setUpPaint() {
float density = getResources().getDisplayMetrics().density;
mPaint = new Paint();
mPaint.setColor(0xff2196F3);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setShadowLayer((int) (0.5f + 2.0f * density), 0f, 0f, SHADOW_COLOR);
// float density = getResources().getDisplayMetrics().density;
// mShadowPaint = new Paint();
// mShadowPaint.setAntiAlias(true);
// mShadowPaint.setShadowLayer((int) (0.5f + 2.0f * density), 0f, 0f, SHADOW_COLOR);
}
private void setUpPath() {
mWavePath = new Path();
mDropTangentPath = new Path();
mDropCirclePath = new Path();
mShadowPath = new Path();
}
private void resetAnimator() {
mDropVertexAnimator = ValueAnimator.ofFloat(0.f, 0.f);
mDropBounceVerticalAnimator = ValueAnimator.ofFloat(0.f, 0.f);
mDropBounceHorizontalAnimator = ValueAnimator.ofFloat(0.f, 0.f);
mDropCircleAnimator = ValueAnimator.ofFloat(-1000.f, -1000.f);
mDropCircleAnimator.start();
mDisappearCircleAnimator = ValueAnimator.ofFloat(1.f, 1.f);
mDisappearCircleAnimator.setDuration(1); // immediately finish animation cycle
mDisappearCircleAnimator.start();
}
private void onPreDragWave() {
if (mWaveReverseAnimator != null) {
if (mWaveReverseAnimator.isRunning()) {
mWaveReverseAnimator.cancel();
}
}
}
public void manualRefresh() {
if (mIsManualRefreshing) {
return;
}
mIsManualRefreshing = true;
mDropCircleAnimator = ValueAnimator.ofFloat(mMaxDropHeight, mMaxDropHeight);
mDropCircleAnimator.start();
mDropVertexAnimator = ValueAnimator.ofFloat(mMaxDropHeight - mDropCircleRadius,
mMaxDropHeight - mDropCircleRadius);
mDropVertexAnimator.start();
mCurrentCircleCenterY = mMaxDropHeight;
postInvalidate();
}
public void beginPhase(float move1) {
onPreDragWave();
//円を描画し始める前の引っ張ったら膨れる波の部分の描画
mWavePath.moveTo(0, 0);
//左半分の描画
mWavePath.cubicTo(mWidth * BEGIN_PHASE_POINTS[0][0], BEGIN_PHASE_POINTS[0][1],
mWidth * BEGIN_PHASE_POINTS[1][0], mWidth * (BEGIN_PHASE_POINTS[1][1] + move1),
mWidth * BEGIN_PHASE_POINTS[2][0], mWidth * (BEGIN_PHASE_POINTS[2][1] + move1));
mWavePath.cubicTo(mWidth * BEGIN_PHASE_POINTS[3][0],
mWidth * (BEGIN_PHASE_POINTS[3][1] + move1), mWidth * BEGIN_PHASE_POINTS[4][0],
mWidth * (BEGIN_PHASE_POINTS[4][1] + move1), mWidth * BEGIN_PHASE_POINTS[5][0],
mWidth * (BEGIN_PHASE_POINTS[5][1] + move1));
//右半分の描画
mWavePath.cubicTo(mWidth - mWidth * BEGIN_PHASE_POINTS[4][0],
mWidth * (BEGIN_PHASE_POINTS[4][1] + move1), mWidth - mWidth * BEGIN_PHASE_POINTS[3][0],
mWidth * (BEGIN_PHASE_POINTS[3][1] + move1), mWidth - mWidth * BEGIN_PHASE_POINTS[2][0],
mWidth * (BEGIN_PHASE_POINTS[2][1] + move1));
mWavePath.cubicTo(mWidth - mWidth * BEGIN_PHASE_POINTS[1][0],
mWidth * (BEGIN_PHASE_POINTS[1][1] + move1), mWidth - mWidth * BEGIN_PHASE_POINTS[0][0],
BEGIN_PHASE_POINTS[0][1], mWidth, 0);
postInvalidateOnAnimation();
}
public void postInvalidateOnAnimation() {
if (Build.VERSION.SDK_INT >= 16) {
super.postInvalidateOnAnimation();
} else {
super.invalidate();
}
}
public void appearPhase(float move1, float move2) {
onPreDragWave();
mWavePath.moveTo(0, 0);
//左半分の描画
mWavePath.cubicTo(mWidth * APPEAR_PHASE_POINTS[0][0], mWidth * APPEAR_PHASE_POINTS[0][1],
mWidth * Math.min(BEGIN_PHASE_POINTS[1][0] + move2, APPEAR_PHASE_POINTS[1][0]),
mWidth * Math.max(BEGIN_PHASE_POINTS[1][1] + move1 - move2, APPEAR_PHASE_POINTS[1][1]),
mWidth * Math.max(BEGIN_PHASE_POINTS[2][0] - move2, APPEAR_PHASE_POINTS[2][0]),
mWidth * Math.max(BEGIN_PHASE_POINTS[2][1] + move1 - move2, APPEAR_PHASE_POINTS[2][1]));
mWavePath.cubicTo(
mWidth * Math.max(BEGIN_PHASE_POINTS[3][0] - move2, APPEAR_PHASE_POINTS[3][0]),
mWidth * Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]),
mWidth * Math.max(BEGIN_PHASE_POINTS[4][0] - move2, APPEAR_PHASE_POINTS[4][0]),
mWidth * Math.min(BEGIN_PHASE_POINTS[4][1] + move1 + move2, APPEAR_PHASE_POINTS[4][1]),
mWidth * APPEAR_PHASE_POINTS[5][0],
mWidth * Math.min(BEGIN_PHASE_POINTS[0][1] + move1 + move2, APPEAR_PHASE_POINTS[5][1]));
//右半分の描画
mWavePath.cubicTo(
mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[4][0] - move2, APPEAR_PHASE_POINTS[4][0]),
mWidth * Math.min(BEGIN_PHASE_POINTS[4][1] + move1 + move2, APPEAR_PHASE_POINTS[4][1]),
mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[3][0] - move2, APPEAR_PHASE_POINTS[3][0]),
mWidth * Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]),
mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[2][0] - move2, APPEAR_PHASE_POINTS[2][0]),
mWidth * Math.max(BEGIN_PHASE_POINTS[2][1] + move1 - move2, APPEAR_PHASE_POINTS[2][1]));
mWavePath.cubicTo(
mWidth - mWidth * Math.min(BEGIN_PHASE_POINTS[1][0] + move2, APPEAR_PHASE_POINTS[1][0]),
mWidth * Math.max(BEGIN_PHASE_POINTS[1][1] + move1 - move2, APPEAR_PHASE_POINTS[1][1]),
mWidth - mWidth * APPEAR_PHASE_POINTS[0][0], mWidth * APPEAR_PHASE_POINTS[0][1], mWidth, 0);
mCurrentCircleCenterY =
mWidth * Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1])
+ mDropCircleRadius;
postInvalidateOnAnimation();
}
public void expandPhase(float move1, float move2, float move3) {
onPreDragWave();
mWavePath.moveTo(0, 0);
//左半分の描画
mWavePath.cubicTo(mWidth * EXPAND_PHASE_POINTS[0][0], mWidth * EXPAND_PHASE_POINTS[0][1],
mWidth * Math.min(
Math.min(BEGIN_PHASE_POINTS[1][0] + move2, APPEAR_PHASE_POINTS[1][0]) + move3,
EXPAND_PHASE_POINTS[1][0]), mWidth * Math.max(
Math.max(BEGIN_PHASE_POINTS[1][1] + move1 - move2, APPEAR_PHASE_POINTS[1][1]) - move3,
EXPAND_PHASE_POINTS[1][1]),
mWidth * Math.max(BEGIN_PHASE_POINTS[2][0] - move2, EXPAND_PHASE_POINTS[2][0]),
mWidth * Math.min(
Math.max(BEGIN_PHASE_POINTS[2][1] + move1 - move2, APPEAR_PHASE_POINTS[2][1]) + move3,
EXPAND_PHASE_POINTS[2][1]));
mWavePath.cubicTo(mWidth * Math.min(
Math.max(BEGIN_PHASE_POINTS[3][0] - move2, APPEAR_PHASE_POINTS[3][0]) + move3,
EXPAND_PHASE_POINTS[3][0]), mWidth * Math.min(
Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]) + move3,
EXPAND_PHASE_POINTS[3][1]),
mWidth * Math.max(BEGIN_PHASE_POINTS[4][0] - move2, EXPAND_PHASE_POINTS[4][0]),
mWidth * Math.min(
Math.min(BEGIN_PHASE_POINTS[4][1] + move1 + move2, APPEAR_PHASE_POINTS[4][1]) + move3,
EXPAND_PHASE_POINTS[4][1]), mWidth * EXPAND_PHASE_POINTS[5][0], mWidth * Math.min(
Math.min(BEGIN_PHASE_POINTS[0][1] + move1 + move2, APPEAR_PHASE_POINTS[5][1]) + move3,
EXPAND_PHASE_POINTS[5][1]));
//右半分の描画
mWavePath.cubicTo(
mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[4][0] - move2, EXPAND_PHASE_POINTS[4][0]),
mWidth * Math.min(
Math.min(BEGIN_PHASE_POINTS[4][1] + move1 + move2, APPEAR_PHASE_POINTS[4][1]) + move3,
EXPAND_PHASE_POINTS[4][1]), mWidth - mWidth * Math.min(
Math.max(BEGIN_PHASE_POINTS[3][0] - move2, APPEAR_PHASE_POINTS[3][0]) + move3,
EXPAND_PHASE_POINTS[3][0]), mWidth * Math.min(
Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]) + move3,
EXPAND_PHASE_POINTS[3][1]),
mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[2][0] - move2, EXPAND_PHASE_POINTS[2][0]),
mWidth * Math.min(
Math.max(BEGIN_PHASE_POINTS[2][1] + move1 - move2, APPEAR_PHASE_POINTS[2][1]) + move3,
EXPAND_PHASE_POINTS[2][1]));
mWavePath.cubicTo(mWidth - mWidth * Math.min(
Math.min(BEGIN_PHASE_POINTS[1][0] + move2, APPEAR_PHASE_POINTS[1][0]) + move3,
EXPAND_PHASE_POINTS[1][0]), mWidth * Math.max(
Math.max(BEGIN_PHASE_POINTS[1][1] + move1 - move2, APPEAR_PHASE_POINTS[1][1]) - move3,
EXPAND_PHASE_POINTS[1][1]), mWidth - mWidth * EXPAND_PHASE_POINTS[0][0],
mWidth * EXPAND_PHASE_POINTS[0][1], mWidth, 0);
mCurrentCircleCenterY = mWidth * Math.min(
Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]) + move3,
EXPAND_PHASE_POINTS[3][1]) + mDropCircleRadius;
postInvalidateOnAnimation();
}
/**
* @param height 高さ
*/
private void updateMaxDropHeight(int height) {
if (500 * (mWidth / 1440.f) > height) {
Log.w("WaveView", "DropHeight is more than " + 500 * (mWidth / 1440.f));
return;
}
mMaxDropHeight = (int) Math.min(height, getHeight() - mDropCircleRadius);
if (mIsManualRefreshing) {
mIsManualRefreshing = false;
manualRefresh();
}
}
public void startDropAnimation() {
// show dropBubble again
mDisappearCircleAnimator = ValueAnimator.ofFloat(1.f, 1.f);
mDisappearCircleAnimator.setDuration(1);
mDisappearCircleAnimator.start();
mDropCircleAnimator = ValueAnimator.ofFloat(500 * (mWidth / 1440.f), mMaxDropHeight);
mDropCircleAnimator.setDuration(DROP_CIRCLE_ANIMATOR_DURATION);
mDropCircleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentCircleCenterY = (float) animation.getAnimatedValue();
postInvalidateOnAnimation();
}
});
mDropCircleAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mDropCircleAnimator.start();
mDropVertexAnimator = ValueAnimator.ofFloat(0.f, mMaxDropHeight - mDropCircleRadius);
mDropVertexAnimator.setDuration(DROP_VERTEX_ANIMATION_DURATION);
mDropVertexAnimator.addUpdateListener(mAnimatorUpdateListener);
mDropVertexAnimator.start();
mDropBounceVerticalAnimator = ValueAnimator.ofFloat(0.f, 1.f);
mDropBounceVerticalAnimator.setDuration(DROP_BOUNCE_ANIMATOR_DURATION);
mDropBounceVerticalAnimator.addUpdateListener(mAnimatorUpdateListener);
mDropBounceVerticalAnimator.setInterpolator(new DropBounceInterpolator());
mDropBounceVerticalAnimator.setStartDelay(DROP_VERTEX_ANIMATION_DURATION);
mDropBounceVerticalAnimator.start();
mDropBounceHorizontalAnimator = ValueAnimator.ofFloat(0.f, 1.f);
mDropBounceHorizontalAnimator.setDuration(DROP_BOUNCE_ANIMATOR_DURATION);
mDropBounceHorizontalAnimator.addUpdateListener(mAnimatorUpdateListener);
mDropBounceHorizontalAnimator.setInterpolator(new DropBounceInterpolator());
mDropBounceHorizontalAnimator.setStartDelay(
(long) (DROP_VERTEX_ANIMATION_DURATION + DROP_BOUNCE_ANIMATOR_DURATION * 0.25));
mDropBounceHorizontalAnimator.start();
}
public void startDisappearCircleAnimation() {
mDisappearCircleAnimator = ValueAnimator.ofFloat(1.f, 0.f);
mDisappearCircleAnimator.addUpdateListener(mAnimatorUpdateListener);
mDisappearCircleAnimator.setDuration(DROP_REMOVE_ANIMATOR_DURATION);
mDisappearCircleAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
//アニメーション修旅時にAnimatorをリセットすることにより落ちてくる円の初期位置を-100.fにする
resetAnimator();
mIsManualRefreshing = false;
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
mDisappearCircleAnimator.start();
}
/**
* @param h 波が始まる高さ
*/
public void startWaveAnimation(float h) {
h = Math.min(h, MAX_WAVE_HEIGHT) * mWidth;
mWaveReverseAnimator = ValueAnimator.ofFloat(h, 0.f);
mWaveReverseAnimator.setDuration(WAVE_ANIMATOR_DURATION);
mWaveReverseAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float h = (Float) valueAnimator.getAnimatedValue();
mWavePath.moveTo(0, 0);
mWavePath.quadTo(0.25f * mWidth, 0, 0.333f * mWidth, h * 0.5f);
mWavePath.quadTo(mWidth * 0.5f, h * 1.4f, 0.666f * mWidth, h * 0.5f);
mWavePath.quadTo(0.75f * mWidth, 0, mWidth, 0);
postInvalidate();
}
});
mWaveReverseAnimator.setInterpolator(new BounceInterpolator());
mWaveReverseAnimator.start();
}
public void animationDropCircle() {
if (mDisappearCircleAnimator.isRunning()) {
return;
}
startDropAnimation();
startWaveAnimation(0.1f);
}
public float getCurrentCircleCenterY() {
return mCurrentCircleCenterY;
}
/**
* @param maxDropHeight ある程度の高さ
*/
public void setMaxDropHeight(int maxDropHeight) {
if (mDropHeightUpdated) {
updateMaxDropHeight(maxDropHeight);
} else {
mUpdateMaxDropHeight = maxDropHeight;
mDropHeightUpdated = true;
if (getViewTreeObserver().isAlive()) {
getViewTreeObserver().removeOnPreDrawListener(this);
getViewTreeObserver().addOnPreDrawListener(this);
}
}
}
public boolean isDisappearCircleAnimatorRunning() {
return mDisappearCircleAnimator.isRunning();
}
/**
* @param radius 影の深さ
*/
public void setShadowRadius(int radius) {
// mShadowPaint.setShadowLayer(radius, 0.0f, 2.0f, SHADOW_COLOR);
mPaint.setShadowLayer(radius, 0f, 0f, SHADOW_COLOR);
}
/**
* @param radius 影の深さ
*/
public void setShadow(int radius, int color) {
mPaint.setShadowLayer(radius, 0f, 0f, color);
}
/**
* WaveView is colored by given color (including alpha)
*
* @param color ARGB color. WaveView will be colored by Black if rgb color is provided.
* @see Paint#setColor(int)
*/
public void setWaveColor(int color) {
mPaint.setColor(color);
invalidate();
}
public void setWaveARGBColor(int a, int r, int g, int b) {
mPaint.setARGB(a, r, g, b);
invalidate();
}
}
================================================
FILE: refresh-header/src/main/res/values/attrs.xml
================================================
================================================
FILE: refresh-header/src/main/res/values/strings.xml
================================================
SmartRefreshHeader
================================================
FILE: refresh-header/src/test/java/com/scwang/smartrefresh/header/ExampleUnitTest.java
================================================
package com.scwang.smartrefresh.header;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see Testing documentation
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
================================================
FILE: refresh-layout/.gitignore
================================================
/build
================================================
FILE: refresh-layout/build.gradle
================================================
apply plugin: 'com.android.library'
apply plugin: 'com.novoda.bintray-release'
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
defaultConfig {
minSdkVersion 12
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testCompile 'junit:junit:4.12'
provided 'com.android.support:design:26.0.0-alpha1'
}
publish {
userOrg = 'scwang90'
groupId = 'com.scwang.smartrefresh'
artifactId = 'SmartRefreshLayout'
version = '1.0.3'
description = 'An intelligent refresh layout'
website = "https://github.com/scwang90/${rootProject.name}"
}
================================================
FILE: refresh-layout/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in E:\Android\android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: refresh-layout/src/androidTest/java/com/scwang/smartrefresh/layout/ExampleInstrumentedTest.java
================================================
package com.scwang.smartrefresh.layout;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumentation test, which will execute on an Android device.
*
* @see Testing documentation
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.scwang.smartrefresh.layout.test", appContext.getPackageName());
}
}
================================================
FILE: refresh-layout/src/main/AndroidManifest.xml
================================================
================================================
FILE: refresh-layout/src/main/java/android/support/v4/view/PagerAdapterWrapper.java
================================================
package android.support.v4.view;
import android.database.DataSetObserver;
import android.os.Parcelable;
import android.view.View;
import android.view.ViewGroup;
import java.lang.reflect.Field;
@SuppressWarnings("deprecation")
public class PagerAdapterWrapper extends PagerAdapter {
protected PagerAdapter wrapped = null;
public PagerAdapterWrapper(PagerAdapter wrapped) {
this.wrapped = wrapped;
}
public void attachViewPager(ViewPager viewPager) {
//viewPager.mAdapter = this;
try {
Field[] fields = ViewPager.class.getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
if (PagerAdapter.class.equals(field.getType())) {
field.setAccessible(true);
field.set(viewPager, this);
break;
}
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
protected void setViewPagerObserver(DataSetObserver observer) {
super.setViewPagerObserver(observer);
}
@Override
public int getCount() {
return wrapped.getCount();
}
@Override
public void startUpdate(ViewGroup container) {
wrapped.startUpdate(container);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
return wrapped.instantiateItem(container, position);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
wrapped.destroyItem(container, position, object);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
wrapped.setPrimaryItem(container, position, object);
}
@Override
public void finishUpdate(ViewGroup container) {
wrapped.finishUpdate(container);
}
@Override
@Deprecated
public void startUpdate(View container) {
wrapped.startUpdate(container);
}
@Override
@Deprecated
public Object instantiateItem(View container, int position) {
return wrapped.instantiateItem(container, position);
}
@Override
@Deprecated
public void destroyItem(View container, int position, Object object) {
wrapped.destroyItem(container, position, object);
}
@Override
@Deprecated
public void setPrimaryItem(View container, int position, Object object) {
wrapped.setPrimaryItem(container, position, object);
}
@Override
@Deprecated
public void finishUpdate(View container) {
wrapped.finishUpdate(container);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return wrapped.isViewFromObject(view, object);
}
@Override
public Parcelable saveState() {
return wrapped.saveState();
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
wrapped.restoreState(state, loader);
}
@Override
public int getItemPosition(Object object) {
return wrapped.getItemPosition(object);
}
@Override
public void notifyDataSetChanged() {
wrapped.notifyDataSetChanged();
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
wrapped.registerDataSetObserver(observer);
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
wrapped.unregisterDataSetObserver(observer);
}
@Override
public CharSequence getPageTitle(int position) {
return wrapped.getPageTitle(position);
}
@Override
public float getPageWidth(int position) {
return wrapped.getPageWidth(position);
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/SmartRefreshLayout.java
================================================
package com.scwang.smartrefresh.layout;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ScrollingView;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.webkit.WebView;
import android.widget.AbsListView;
import android.widget.ScrollView;
import com.scwang.smartrefresh.layout.api.DefaultRefreshFooterCreater;
import com.scwang.smartrefresh.layout.api.DefaultRefreshHeaderCreater;
import com.scwang.smartrefresh.layout.api.RefreshContent;
import com.scwang.smartrefresh.layout.api.RefreshFooter;
import com.scwang.smartrefresh.layout.api.RefreshHeader;
import com.scwang.smartrefresh.layout.api.RefreshKernel;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.api.ScrollBoundaryDecider;
import com.scwang.smartrefresh.layout.constant.DimensionStatus;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.scwang.smartrefresh.layout.constant.SpinnerStyle;
import com.scwang.smartrefresh.layout.footer.BallPulseFooter;
import com.scwang.smartrefresh.layout.header.BezierRadarHeader;
import com.scwang.smartrefresh.layout.header.FalsifyHeader;
import com.scwang.smartrefresh.layout.impl.RefreshContentWrapper;
import com.scwang.smartrefresh.layout.impl.RefreshFooterWrapper;
import com.scwang.smartrefresh.layout.impl.RefreshHeaderWrapper;
import com.scwang.smartrefresh.layout.listener.OnLoadmoreListener;
import com.scwang.smartrefresh.layout.listener.OnMultiPurposeListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadmoreListener;
import com.scwang.smartrefresh.layout.util.DelayedRunable;
import com.scwang.smartrefresh.layout.util.DensityUtil;
import com.scwang.smartrefresh.layout.util.ViscousFluidInterpolator;
import java.util.ArrayList;
import java.util.List;
import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.getSize;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.scwang.smartrefresh.layout.util.DensityUtil.dp2px;
import static java.lang.System.currentTimeMillis;
/**
* 智能刷新布局
* Intelligent Refreshlayout
* Created by SCWANG on 2017/5/26.
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public class SmartRefreshLayout extends ViewGroup implements RefreshLayout, NestedScrollingParent, NestedScrollingChild {
//
//
protected int mTouchSlop;
protected int mSpinner;//当前的 Spinner
protected int mLastSpinner;//最后的,的Spinner
protected int mTouchSpinner;//触摸时候,的Spinner
protected int mReboundDuration = 250;
protected int mScreenHeightPixels;
protected float mTouchX;
protected float mTouchY;
protected float mLastTouchX;//用于实现Header的左右拖动效果
protected float mLastTouchY;//用于实现多点触摸
protected float mDragRate = .5f;
protected boolean mIsBeingDragged;
protected Interpolator mReboundInterpolator;
protected int mFixedHeaderViewId;//固定在头部的视图Id
protected int mFixedFooterViewId;//固定在 头部的视图Id
//
//
protected int[] mPrimaryColors;
protected boolean mEnableRefresh = true;
protected boolean mEnableLoadmore = false;
protected boolean mEnableHeaderTranslationContent = true;//是否启用内容视图拖动效果
protected boolean mEnableFooterTranslationContent = true;//是否启用内容视图拖动效果
protected boolean mEnablePreviewInEditMode = true;//是否在编辑模式下开启预览功能
protected boolean mEnableOverScrollBounce = true;//是否启用越界回弹
protected boolean mEnableAutoLoadmore = true;//是否在列表滚动到底部时自动加载更多
protected boolean mEnablePureScrollMode = false;//是否开启纯滚动模式
protected boolean mEnableScrollContentWhenLoaded = true;//是否在加载更多完成之后滚动内容显示新数据
protected boolean mEnableLoadmoreWhenContentNotFull = false;//在内容不满一页的时候,是否可以上拉加载更多
protected boolean mDisableContentWhenRefresh = false;//是否开启在刷新时候禁止操作内容视图
protected boolean mDisableContentWhenLoading = false;//是否开启在刷新时候禁止操作内容视图
protected boolean mLoadmoreFinished = false;//数据是否全部加载完成,如果完成就不能在触发加载事件
protected boolean mManualLoadmore = false;//是否手动设置过Loadmore,用于智能开启
protected boolean mManualNestedScrolling = false;//是否手动设置过 NestedScrolling,用于智能开启
//
//
protected OnRefreshListener mRefreshListener;
protected OnLoadmoreListener mLoadmoreListener;
protected OnMultiPurposeListener mOnMultiPurposeListener;
protected ScrollBoundaryDecider mScrollBoundaryDecider;
//
//
protected int[] mParentScrollConsumed = new int[2];
protected int[] mParentOffsetInWindow = new int[2];
protected int mTotalUnconsumed;
protected boolean mNestedScrollInProgress;
protected NestedScrollingChildHelper mNestedScrollingChildHelper;
protected NestedScrollingParentHelper mNestedScrollingParentHelper;
//
//
/**
* 头部高度
*/
protected int mHeaderHeight;
protected DimensionStatus mHeaderHeightStatus = DimensionStatus.DefaultUnNotify;
/**
* 底部高度
*/
protected int mFooterHeight;
protected DimensionStatus mFooterHeightStatus = DimensionStatus.DefaultUnNotify;
/**
* 扩展高度
*/
protected int mHeaderExtendHeight;
/**
* 扩展高度
*/
protected int mFooterExtendHeight;
/**
* 最大拖动比率(最大高度/Header高度)
*/
protected float mHeaderMaxDragRate = 2.0f;
/**
* 最大拖动比率(最大高度/Footer高度)
*/
protected float mFooterMaxDragRate = 2.0f;
/**
* 下拉头部视图
*/
protected RefreshHeader mRefreshHeader;
/**
* 显示内容视图
*/
protected RefreshContent mRefreshContent;
/**
* 上拉底部视图
*/
protected RefreshFooter mRefreshFooter;
//
protected Paint mPaint;
protected Handler handler;
protected RefreshKernel mKernel;
protected List mDelayedRunables;
protected RefreshState mState = RefreshState.None; //主状态
protected RefreshState mViceState = RefreshState.None; //副状态(主状态刷新时候的滚动状态)
protected long mLastLoadingTime = 0;
protected long mLastRefreshingTime = 0;
protected int mHeaderBackgroundColor = 0; //为Header绘制纯色背景
protected int mFooterBackgroundColor = 0;
protected boolean mHeaderNeedTouchEventWhenRefreshing; //为游戏Header提供独立事件
protected boolean mFooterNeedTouchEventWhenRefreshing;
protected static boolean sManualFooterCreater = false;
protected static DefaultRefreshFooterCreater sFooterCreater = new DefaultRefreshFooterCreater() {
@NonNull
@Override
public RefreshFooter createRefreshFooter(Context context, RefreshLayout layout) {
return new BallPulseFooter(context);
}
};
protected static DefaultRefreshHeaderCreater sHeaderCreater = new DefaultRefreshHeaderCreater() {
@NonNull
@Override
public RefreshHeader createRefreshHeader(Context context, RefreshLayout layout) {
return new BezierRadarHeader(context);
}
};
//
//
public SmartRefreshLayout(Context context) {
super(context);
this.initView(context, null);
}
public SmartRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.initView(context, attrs);
}
public SmartRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.initView(context, attrs);
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public SmartRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.initView(context, attrs);
}
private void initView(Context context, AttributeSet attrs) {
setClipToPadding(false);
mScreenHeightPixels = context.getResources().getDisplayMetrics().heightPixels;
mReboundInterpolator = new ViscousFluidInterpolator();
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
DensityUtil density = new DensityUtil();
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SmartRefreshLayout);
ViewCompat.setNestedScrollingEnabled(this, ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableNestedScrolling, false));
mDragRate = ta.getFloat(R.styleable.SmartRefreshLayout_srlDragRate, mDragRate);
mHeaderMaxDragRate = ta.getFloat(R.styleable.SmartRefreshLayout_srlHeaderMaxDragRate, mHeaderMaxDragRate);
mFooterMaxDragRate = ta.getFloat(R.styleable.SmartRefreshLayout_srlFooterMaxDragRate, mFooterMaxDragRate);
mEnableRefresh = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableRefresh, mEnableRefresh);
mReboundDuration = ta.getInt(R.styleable.SmartRefreshLayout_srlReboundDuration, mReboundDuration);
mEnableLoadmore = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableLoadmore, mEnableLoadmore);
mHeaderHeight = ta.getDimensionPixelOffset(R.styleable.SmartRefreshLayout_srlHeaderHeight, density.dip2px(100));
mFooterHeight = ta.getDimensionPixelOffset(R.styleable.SmartRefreshLayout_srlFooterHeight, density.dip2px(60));
mDisableContentWhenRefresh = ta.getBoolean(R.styleable.SmartRefreshLayout_srlDisableContentWhenRefresh, mDisableContentWhenRefresh);
mDisableContentWhenLoading = ta.getBoolean(R.styleable.SmartRefreshLayout_srlDisableContentWhenLoading, mDisableContentWhenLoading);
mEnableHeaderTranslationContent = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableHeaderTranslationContent, mEnableHeaderTranslationContent);
mEnableFooterTranslationContent = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableFooterTranslationContent, mEnableFooterTranslationContent);
mEnablePreviewInEditMode = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnablePreviewInEditMode, mEnablePreviewInEditMode);
mEnableAutoLoadmore = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableAutoLoadmore, mEnableAutoLoadmore);
mEnableOverScrollBounce = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableOverScrollBounce, mEnableOverScrollBounce);
mEnablePureScrollMode = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnablePureScrollMode, mEnablePureScrollMode);
mEnableScrollContentWhenLoaded = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableScrollContentWhenLoaded, mEnableScrollContentWhenLoaded);
mEnableLoadmoreWhenContentNotFull = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableLoadmoreWhenContentNotFull, mEnableLoadmoreWhenContentNotFull);
mFixedHeaderViewId = ta.getResourceId(R.styleable.SmartRefreshLayout_srlFixedHeaderViewId, View.NO_ID);
mFixedFooterViewId = ta.getResourceId(R.styleable.SmartRefreshLayout_srlFixedFooterViewId, View.NO_ID);
mManualLoadmore = ta.hasValue(R.styleable.SmartRefreshLayout_srlEnableLoadmore);
mManualNestedScrolling = ta.hasValue(R.styleable.SmartRefreshLayout_srlEnableNestedScrolling);
mHeaderHeightStatus = ta.hasValue(R.styleable.SmartRefreshLayout_srlHeaderHeight) ? DimensionStatus.XmlLayoutUnNotify : mHeaderHeightStatus;
mFooterHeightStatus = ta.hasValue(R.styleable.SmartRefreshLayout_srlFooterHeight) ? DimensionStatus.XmlLayoutUnNotify : mFooterHeightStatus;
mHeaderExtendHeight = (int) Math.max((mHeaderHeight * (mHeaderMaxDragRate - 1)), 0);
mFooterExtendHeight = (int) Math.max((mFooterHeight * (mFooterMaxDragRate - 1)), 0);
int accentColor = ta.getColor(R.styleable.SmartRefreshLayout_srlAccentColor, 0);
int primaryColor = ta.getColor(R.styleable.SmartRefreshLayout_srlPrimaryColor, 0);
if (primaryColor != 0) {
if (accentColor != 0) {
mPrimaryColors = new int[]{primaryColor, accentColor};
} else {
mPrimaryColors = new int[]{primaryColor};
}
}
ta.recycle();
}
//
//
@Override
protected void onFinishInflate() {
super.onFinishInflate();
final int count = getChildCount();
if (count > 3) {
throw new RuntimeException("最多只支持3个子View,Most only support three sub view");
} else if (mEnablePureScrollMode && count > 1) {
throw new RuntimeException("PureScrollMode模式只支持一个子View,Most only support one sub view in PureScrollMode");
}
//定义为确认的子View索引
boolean[] uncertains = new boolean[count];
//第一次查找确认的 子View
for (int i = 0; i < count; i++) {
View view = getChildAt(i);
if (view instanceof RefreshHeader && mRefreshHeader == null) {
mRefreshHeader = ((RefreshHeader) view);
} else if (view instanceof RefreshFooter && mRefreshFooter == null) {
mEnableLoadmore = mEnableLoadmore || !mManualLoadmore;
mRefreshFooter = ((RefreshFooter) view);
} else if (mRefreshContent == null && (view instanceof AbsListView
|| view instanceof WebView
|| view instanceof ScrollView
|| view instanceof ScrollingView
|| view instanceof NestedScrollingChild
|| view instanceof NestedScrollingParent
|| view instanceof ViewPager)) {
mRefreshContent = new RefreshContentWrapper(view);
} else if (RefreshHeaderWrapper.isTagedHeader(view) && mRefreshHeader == null) {
mRefreshHeader = new RefreshHeaderWrapper(view);
} else if (RefreshFooterWrapper.isTagedFooter(view) && mRefreshFooter == null) {
mRefreshFooter = new RefreshFooterWrapper(view);
} else if (RefreshContentWrapper.isTagedContent(view) && mRefreshContent == null) {
mRefreshContent = new RefreshContentWrapper(view);
} else {
uncertains[i] = true;//标记未确认
}
}
//如果有 未确认(uncertains)的子View 通过智能算法计算
for (int i = 0; i < count; i++) {
if (uncertains[i]) {
View view = getChildAt(i);
if (count == 1 && mRefreshContent == null) {
mRefreshContent = new RefreshContentWrapper(view);
} else if (i == 0 && mRefreshHeader == null) {
mRefreshHeader = new RefreshHeaderWrapper(view);
} else if (count == 2 && mRefreshContent == null) {
mRefreshContent = new RefreshContentWrapper(view);
} else if (i == 2 && mRefreshFooter == null) {
mEnableLoadmore = mEnableLoadmore || !mManualLoadmore;
mRefreshFooter = new RefreshFooterWrapper(view);
} else if (mRefreshContent == null) {
mRefreshContent = new RefreshContentWrapper(view);
}
}
}
if (isInEditMode()) {
if (mPrimaryColors != null) {
if (mRefreshHeader != null) {
mRefreshHeader.setPrimaryColors(mPrimaryColors);
}
if (mRefreshFooter != null) {
mRefreshFooter.setPrimaryColors(mPrimaryColors);
}
}
//重新排序
if (mRefreshContent != null) {
bringChildToFront(mRefreshContent.getView());
}
if (mRefreshHeader != null && mRefreshHeader.getSpinnerStyle() != SpinnerStyle.FixedBehind) {
bringChildToFront(mRefreshHeader.getView());
}
if (mRefreshFooter != null && mRefreshFooter.getSpinnerStyle() != SpinnerStyle.FixedBehind) {
bringChildToFront(mRefreshFooter.getView());
}
if (mKernel == null) {
mKernel = new RefreshKernelImpl();
}
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (isInEditMode()) return;
if (mKernel == null) {
mKernel = new RefreshKernelImpl();
}
if (handler == null) {
handler = new Handler();
}
if (mDelayedRunables != null) {
for (DelayedRunable runable : mDelayedRunables) {
handler.postDelayed(runable, runable.delayMillis);
}
mDelayedRunables.clear();
mDelayedRunables = null;
}
if (mRefreshContent == null
&& mRefreshHeader == null
&& mRefreshFooter == null) {
onFinishInflate();
}
if (mRefreshHeader == null) {
if (mEnablePureScrollMode) {
mRefreshHeader = new FalsifyHeader(getContext());
} else {
mRefreshHeader = sHeaderCreater.createRefreshHeader(getContext(), this);
}
if (!(mRefreshHeader.getView().getLayoutParams() instanceof MarginLayoutParams)) {
if (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Scale) {
addView(mRefreshHeader.getView(), MATCH_PARENT, MATCH_PARENT);
} else {
addView(mRefreshHeader.getView(), MATCH_PARENT, WRAP_CONTENT);
}
}
}
if (mRefreshFooter == null) {
if (mEnablePureScrollMode) {
mRefreshFooter = new RefreshFooterWrapper(new FalsifyHeader(getContext()));
mEnableLoadmore = mEnableLoadmore || !mManualLoadmore;
} else {
mRefreshFooter = sFooterCreater.createRefreshFooter(getContext(), this);
mEnableLoadmore = mEnableLoadmore || (!mManualLoadmore && sManualFooterCreater);
}
if (!(mRefreshFooter.getView().getLayoutParams() instanceof MarginLayoutParams)) {
if (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.Scale) {
addView(mRefreshFooter.getView(), MATCH_PARENT, MATCH_PARENT);
} else {
addView(mRefreshFooter.getView(), MATCH_PARENT, WRAP_CONTENT);
}
}
}
if (mRefreshContent == null) {
for (int i = 0, len = getChildCount(); i < len; i++) {
View view = getChildAt(i);
if ((mRefreshHeader == null || view != mRefreshHeader.getView()) &&
(mRefreshFooter == null || view != mRefreshFooter.getView())) {
mRefreshContent = new RefreshContentWrapper(view);
}
}
if (mRefreshContent == null) {
mRefreshContent = new RefreshContentWrapper(getContext());
mRefreshContent.getView().setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
}
View fixedHeaderView = mFixedHeaderViewId > 0 ? findViewById(mFixedHeaderViewId) : null;
View fixedFooterView = mFixedFooterViewId > 0 ? findViewById(mFixedFooterViewId) : null;
mRefreshContent.setScrollBoundaryDecider(mScrollBoundaryDecider);
mRefreshContent.setEnableLoadmoreWhenContentNotFull(mEnableLoadmoreWhenContentNotFull || mEnablePureScrollMode);
mRefreshContent.setupComponent(mKernel, fixedHeaderView, fixedFooterView);
if (mSpinner != 0) {
notifyStateChanged(RefreshState.None);
mRefreshContent.moveSpinner(mSpinner = 0);
}
//重新排序
bringChildToFront(mRefreshContent.getView());
if (mRefreshHeader.getSpinnerStyle() != SpinnerStyle.FixedBehind) {
bringChildToFront(mRefreshHeader.getView());
}
if (mRefreshFooter.getSpinnerStyle() != SpinnerStyle.FixedBehind) {
bringChildToFront(mRefreshFooter.getView());
}
if (mRefreshListener == null) {
mRefreshListener = new OnRefreshListener() {
@Override
public void onRefresh(RefreshLayout refreshlayout) {
refreshlayout.finishRefresh(3000);
}
};
}
if (mLoadmoreListener == null) {
mLoadmoreListener = new OnLoadmoreListener() {
@Override
public void onLoadmore(RefreshLayout refreshlayout) {
refreshlayout.finishLoadmore(2000);
}
};
}
if (mPrimaryColors != null) {
mRefreshHeader.setPrimaryColors(mPrimaryColors);
mRefreshFooter.setPrimaryColors(mPrimaryColors);
}
try {
if (!mManualNestedScrolling && !isNestedScrollingEnabled()) {
for (ViewParent parent = this ; parent != null ; parent = parent.getParent()) {
if (parent instanceof CoordinatorLayout) {
setNestedScrollingEnabled(true);
mManualNestedScrolling = false;
break;
}
}
}
} catch (Throwable e) {//try 不能删除,否则会出现兼容性问题
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int minimumHeight = 0;
final boolean isInEditMode = isInEditMode() && mEnablePreviewInEditMode;
if (mRefreshHeader != null) {
final View headerView = mRefreshHeader.getView();
final LayoutParams lp = (LayoutParams) headerView.getLayoutParams();
final int widthSpec = getChildMeasureSpec(widthMeasureSpec, lp.leftMargin + lp.rightMargin, lp.width);
int heightSpec = heightMeasureSpec;
if (mHeaderHeightStatus.gteReplaceWith(DimensionStatus.XmlLayoutUnNotify)) {
heightSpec = makeMeasureSpec(Math.max(mHeaderHeight - lp.bottomMargin, 0), EXACTLY);
headerView.measure(widthSpec, heightSpec);
} else if (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.MatchLayout) {
headerView.measure(widthSpec, heightSpec);
} else if (lp.height > 0) {
if (mHeaderHeightStatus.canReplaceWith(DimensionStatus.XmlExact)) {
mHeaderHeightStatus = DimensionStatus.XmlExact;
mHeaderHeight = lp.height + lp.bottomMargin;
mHeaderExtendHeight = (int) Math.max((mHeaderHeight * (mHeaderMaxDragRate - 1)), 0);
mRefreshHeader.onInitialized(mKernel, mHeaderHeight, mHeaderExtendHeight);
}
heightSpec = makeMeasureSpec(lp.height, EXACTLY);
headerView.measure(widthSpec, heightSpec);
} else if (lp.height == WRAP_CONTENT) {
heightSpec = makeMeasureSpec(Math.max(getSize(heightMeasureSpec) - lp.bottomMargin, 0), AT_MOST);
headerView.measure(widthSpec, heightSpec);
int measuredHeight = headerView.getMeasuredHeight();
if (measuredHeight > 0 && mHeaderHeightStatus.canReplaceWith(DimensionStatus.XmlWrap)) {
mHeaderHeightStatus = DimensionStatus.XmlWrap;
mHeaderHeight = headerView.getMeasuredHeight() + lp.bottomMargin;
mHeaderExtendHeight = (int) Math.max((mHeaderHeight * (mHeaderMaxDragRate - 1)), 0);
mRefreshHeader.onInitialized(mKernel, mHeaderHeight, mHeaderExtendHeight);
} else if (measuredHeight <= 0) {
heightSpec = makeMeasureSpec(Math.max(mHeaderHeight - lp.bottomMargin, 0), EXACTLY);
headerView.measure(widthSpec, heightSpec);
}
} else if (lp.height == MATCH_PARENT) {
heightSpec = makeMeasureSpec(Math.max(mHeaderHeight - lp.bottomMargin, 0), EXACTLY);
headerView.measure(widthSpec, heightSpec);
} else {
headerView.measure(widthSpec, heightSpec);
}
if (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Scale && !isInEditMode) {
final int height = Math.max(0, mSpinner);
heightSpec = makeMeasureSpec(Math.max(height - lp.bottomMargin, 0), EXACTLY);
headerView.measure(widthSpec, heightSpec);
}
if (!mHeaderHeightStatus.notifyed) {
mHeaderHeightStatus = mHeaderHeightStatus.notifyed();
mRefreshHeader.onInitialized(mKernel, mHeaderHeight, mHeaderExtendHeight);
}
if (isInEditMode) {
minimumHeight += headerView.getMeasuredHeight();
}
}
if (mRefreshFooter != null) {
final View footerView = mRefreshFooter.getView();
final LayoutParams lp = (LayoutParams) footerView.getLayoutParams();
final int widthSpec = getChildMeasureSpec(widthMeasureSpec, lp.leftMargin + lp.rightMargin, lp.width);
int heightSpec = heightMeasureSpec;
if (mFooterHeightStatus.gteReplaceWith(DimensionStatus.XmlLayoutUnNotify)) {
heightSpec = makeMeasureSpec(Math.max(mFooterHeight - lp.topMargin, 0), EXACTLY);
footerView.measure(widthSpec, heightSpec);
} else if (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.MatchLayout) {
footerView.measure(widthSpec, heightSpec);
} else if (lp.height > 0) {
if (mFooterHeightStatus.canReplaceWith(DimensionStatus.XmlExact)) {
mFooterHeightStatus = DimensionStatus.XmlExact;
mFooterHeight = lp.height + lp.topMargin;
mFooterExtendHeight = (int) Math.max((mFooterHeight * (mFooterMaxDragRate - 1)), 0);
mRefreshFooter.onInitialized(mKernel, mFooterHeight, mFooterExtendHeight);
}
heightSpec = makeMeasureSpec(lp.height - lp.topMargin, EXACTLY);
footerView.measure(widthSpec, heightSpec);
} else if (lp.height == WRAP_CONTENT) {
heightSpec = makeMeasureSpec(Math.max(getSize(heightMeasureSpec) - lp.topMargin, 0), AT_MOST);
footerView.measure(widthSpec, heightSpec);
int measuredHeight = footerView.getMeasuredHeight();
if (measuredHeight > 0 && mFooterHeightStatus.canReplaceWith(DimensionStatus.XmlWrap)) {
mFooterHeightStatus = DimensionStatus.XmlWrap;
mFooterHeight = footerView.getMeasuredHeight() + lp.topMargin;
mFooterExtendHeight = (int) Math.max((mFooterHeight * (mFooterMaxDragRate - 1)), 0);
mRefreshFooter.onInitialized(mKernel, mFooterHeight, mFooterExtendHeight);
} else if (measuredHeight <= 0) {
heightSpec = makeMeasureSpec(Math.max(mFooterHeight - lp.topMargin, 0), EXACTLY);
footerView.measure(widthSpec, heightSpec);
}
} else if (lp.height == MATCH_PARENT) {
heightSpec = makeMeasureSpec(Math.max(mFooterHeight - lp.topMargin, 0), EXACTLY);
footerView.measure(widthSpec, heightSpec);
} else {
footerView.measure(widthSpec, heightSpec);
}
if (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.Scale && !isInEditMode) {
final int height = Math.max(0, -mSpinner);
heightSpec = makeMeasureSpec(Math.max(height - lp.topMargin, 0), EXACTLY);
footerView.measure(widthSpec, heightSpec);
}
if (!mFooterHeightStatus.notifyed) {
mFooterHeightStatus = mFooterHeightStatus.notifyed();
mRefreshFooter.onInitialized(mKernel, mFooterHeight, mFooterExtendHeight);
}
if (isInEditMode) {
minimumHeight += footerView.getMeasuredHeight();
}
}
if (mRefreshContent != null) {
final LayoutParams lp = (LayoutParams) mRefreshContent.getLayoutParams();
final int widthSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeft() + getPaddingRight() +
lp.leftMargin + lp.rightMargin, lp.width);
final int heightSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTop() + getPaddingBottom() +
lp.topMargin + lp.bottomMargin +
((isInEditMode && mRefreshHeader != null && (mEnableHeaderTranslationContent || mRefreshHeader.getSpinnerStyle() == SpinnerStyle.FixedBehind)) ? mHeaderHeight : 0) +
((isInEditMode && mRefreshFooter != null && (mEnableFooterTranslationContent || mRefreshFooter.getSpinnerStyle() == SpinnerStyle.FixedBehind)) ? mFooterHeight : 0), lp.height);
mRefreshContent.measure(widthSpec, heightSpec);
mRefreshContent.onInitialHeaderAndFooter(mHeaderHeight, mFooterHeight);
minimumHeight += mRefreshContent.getMeasuredHeight();
}
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), resolveSize(minimumHeight, heightMeasureSpec));
mLastTouchX = getMeasuredWidth() / 2;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int paddingLeft = getPaddingLeft();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
final boolean isInEditMode = isInEditMode() && mEnablePreviewInEditMode;
if (mRefreshContent != null) {
final LayoutParams lp = (LayoutParams) mRefreshContent.getLayoutParams();
int left = paddingLeft + lp.leftMargin;
int top = paddingTop + lp.topMargin;
int right = left + mRefreshContent.getMeasuredWidth();
int bottom = top + mRefreshContent.getMeasuredHeight();
if (isInEditMode && mRefreshHeader != null && (mEnableHeaderTranslationContent || mRefreshHeader.getSpinnerStyle() == SpinnerStyle.FixedBehind)) {
top = top + mHeaderHeight;
bottom = bottom + mHeaderHeight;
}
mRefreshContent.layout(left, top, right, bottom);
}
if (mRefreshHeader != null) {
final View headerView = mRefreshHeader.getView();
final LayoutParams lp = (LayoutParams) headerView.getLayoutParams();
int left = lp.leftMargin;
int top = lp.topMargin;
int right = left + headerView.getMeasuredWidth();
int bottom = top + headerView.getMeasuredHeight();
if (!isInEditMode) {
if (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Translate) {
top = top - mHeaderHeight + Math.max(0, mSpinner);
bottom = top + headerView.getMeasuredHeight();
} else if (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Scale) {
bottom = top + Math.max(Math.max(0, mSpinner) - lp.bottomMargin, 0);
}
}
headerView.layout(left, top, right, bottom);
}
if (mRefreshFooter != null) {
final View footerView = mRefreshFooter.getView();
final LayoutParams lp = (LayoutParams) footerView.getLayoutParams();
final SpinnerStyle style = mRefreshFooter.getSpinnerStyle();
int left = lp.leftMargin;
int top = lp.topMargin + getMeasuredHeight();
if (isInEditMode
|| style == SpinnerStyle.FixedFront
|| style == SpinnerStyle.FixedBehind) {
top = top - mFooterHeight;
} else if (style == SpinnerStyle.Scale || style == SpinnerStyle.Translate) {
top = top - Math.max(Math.max(-mSpinner, 0) - lp.topMargin, 0);
}
int right = left + footerView.getMeasuredWidth();
int bottom = top + footerView.getMeasuredHeight();
footerView.layout(left, top, right, bottom);
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
boolean isInEditMode = mEnablePreviewInEditMode && isInEditMode();
if (mHeaderBackgroundColor != 0 && (mSpinner > 0 || isInEditMode)) {
mPaint.setColor(mHeaderBackgroundColor);
canvas.drawRect(0, 0, getWidth(), (isInEditMode) ? mHeaderHeight : mSpinner, mPaint);
} else if (mFooterBackgroundColor != 0 && (mSpinner < 0 || isInEditMode)) {
final int height = getHeight();
mPaint.setColor(mFooterBackgroundColor);
canvas.drawRect(0, height - (isInEditMode ? (mFooterHeight) : -mSpinner), getWidth(), height, mPaint);
}
super.dispatchDraw(canvas);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mSpinner = 0;
mRefreshContent.moveSpinner(0);
notifyStateChanged(RefreshState.None);
handler.removeCallbacksAndMessages(null);
handler = null;
mKernel = null;
// mRefreshHeader = null;
// mRefreshFooter = null;
// mRefreshContent = null;
mManualLoadmore = true;
mManualNestedScrolling = true;
}
//
//
MotionEvent mFalsifyEvent = null;
@Override
public boolean dispatchTouchEvent(MotionEvent e) {
//
//---------------------------------------------------------------------------
//多点触摸计算代码
//---------------------------------------------------------------------------
final int action = MotionEventCompat.getActionMasked(e);
final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? e.getActionIndex() : -1;
// Determine focal point
float sumX = 0, sumY = 0;
final int count = e.getPointerCount();
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
sumX += e.getX(i);
sumY += e.getY(i);
}
final int div = pointerUp ? count - 1 : count;
final float touchX = sumX / div;
final float touchY = sumY / div;
if ((action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN)
&& mIsBeingDragged) {
mTouchY += touchY - mLastTouchY;
}
mLastTouchX = touchX;
mLastTouchY = touchY;
//---------------------------------------------------------------------------
//
if (mRefreshContent != null) {
//为 RefreshContent 传递当前触摸事件的坐标,用于智能判断对应坐标位置View的滚动边界和相关信息
switch (action) {
case MotionEvent.ACTION_DOWN:
mRefreshContent.onActionDown(e);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mRefreshContent.onActionUpOrCancel();
}
}
if ((reboundAnimator != null && !interceptAnimator(action))
|| (mState == RefreshState.Loading && mDisableContentWhenLoading)
|| (mState == RefreshState.Refreshing && mDisableContentWhenRefresh)) {
return false;
}
if (mNestedScrollInProgress) {//嵌套滚动时,补充竖直方向不滚动,但是水平方向滚动,需要通知 onHorizontalDrag
int totalUnconsumed = this.mTotalUnconsumed;
boolean ret = super.dispatchTouchEvent(e);
if (action == MotionEvent.ACTION_MOVE && totalUnconsumed == mTotalUnconsumed) {
final int offsetX = (int) mLastTouchX;
final int offsetMax = getWidth();
final float percentX = mLastTouchX / offsetMax;
if (mSpinner > 0 && mRefreshHeader != null && mRefreshHeader.isSupportHorizontalDrag()) {
mRefreshHeader.onHorizontalDrag(percentX, offsetX, offsetMax);
} else if (mSpinner < 0 && mRefreshFooter != null && mRefreshFooter.isSupportHorizontalDrag()) {
mRefreshFooter.onHorizontalDrag(percentX, offsetX, offsetMax);
}
}
return ret;
} else if (!isEnabled()
|| (!mEnableRefresh && !mEnableLoadmore)
|| (mHeaderNeedTouchEventWhenRefreshing && (mState == RefreshState.Refreshing || mState == RefreshState.RefreshFinish))
|| (mFooterNeedTouchEventWhenRefreshing && (mState == RefreshState.Loading || mState == RefreshState.LoadFinish))
) {
return super.dispatchTouchEvent(e);
}
switch (action) {
case MotionEvent.ACTION_DOWN:
mTouchX = touchX;
mTouchY = touchY;
mLastTouchY = touchY;
mLastSpinner = 0;
mTouchSpinner = mSpinner;
mIsBeingDragged = false;
super.dispatchTouchEvent(e);
return true;
case MotionEvent.ACTION_MOVE:
float dx = touchX - mTouchX;
float dy = touchY - mTouchY;
mLastTouchY = touchY;
if (!mIsBeingDragged) {
if (Math.abs(dy) >= mTouchSlop && Math.abs(dx) < Math.abs(dy)) {//滑动允许最大角度为45度
if (dy > 0 && (mSpinner < 0 || (mEnableRefresh && mRefreshContent.canRefresh()))) {
if (mSpinner < 0) {
setStatePullUpToLoad();
} else {
setStatePullDownToRefresh();
}
mIsBeingDragged = true;
mTouchY = touchY - mTouchSlop;
dy = touchY - mTouchY;
e.setAction(MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(e);
} else if (dy < 0 && (mSpinner > 0 || (mEnableLoadmore && mRefreshContent.canLoadmore()))) {
if (mSpinner > 0) {
setStatePullDownToRefresh();
} else {
setStatePullUpToLoad();
}
mIsBeingDragged = true;
mTouchY = touchY + mTouchSlop;
dy = touchY - mTouchY;
e.setAction(MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(e);
} else {
return super.dispatchTouchEvent(e);
}
} else {
return super.dispatchTouchEvent(e);
}
}
if (mIsBeingDragged) {
final float spinner = dy + mTouchSpinner;
if ((mRefreshContent != null)
&& (getViceState().isHeader() && (spinner < 0 || mLastSpinner < 0))
|| (getViceState().isFooter() && (spinner > 0 || mLastSpinner > 0))) {
long time = e.getEventTime();
if (mFalsifyEvent == null) {
mFalsifyEvent = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, mTouchX + dx, mTouchY, 0);
super.dispatchTouchEvent(mFalsifyEvent);
}
MotionEvent em = MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, mTouchX + dx, mTouchY + spinner, 0);
super.dispatchTouchEvent(em);
if ((getViceState().isHeader() && spinner < 0) || (getViceState().isFooter() && spinner > 0)) {
mLastSpinner = (int) spinner;
if (mSpinner != 0) {
moveSpinnerInfinitely(0);
}
return true;
}
mLastSpinner = (int) spinner;
mFalsifyEvent = null;
MotionEvent ec = MotionEvent.obtain(time, time, MotionEvent.ACTION_CANCEL, mTouchX, mTouchY + spinner, 0);
super.dispatchTouchEvent(ec);
}
if (getViceState().isDraging()) {
moveSpinnerInfinitely(spinner);
return true;
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsBeingDragged = false;
if (mFalsifyEvent != null) {
mFalsifyEvent = null;
long time = e.getEventTime();
MotionEvent ec = MotionEvent.obtain(time, time, mSpinner == 0 ? MotionEvent.ACTION_UP : MotionEvent.ACTION_CANCEL, mTouchX, touchY, 0);
super.dispatchTouchEvent(ec);
}
if (overSpinner()) {
return true;
}
break;
}
return super.dispatchTouchEvent(e);
}
/**
* 在动画执行时,触摸屏幕,打断动画,转为拖动状态
*/
protected boolean interceptAnimator(int action) {
if (reboundAnimator != null && action == MotionEvent.ACTION_DOWN) {
if (mState == RefreshState.LoadFinish || mState == RefreshState.RefreshFinish) {
return false;
}
if (mState == RefreshState.PullDownCanceled) {
setStatePullDownToRefresh();
} else if (mState == RefreshState.PullUpCanceled) {
setStatePullUpToLoad();
}
reboundAnimator.cancel();
reboundAnimator = null;
return true;
}
return false;
}
@Override
public void requestDisallowInterceptTouchEvent(boolean b) {
// if this is a List < L or another view that doesn't support nested
// scrolling, ignore this request so that the vertical scroll event
// isn't stolen
View target = mRefreshContent.getScrollableView();
if ((android.os.Build.VERSION.SDK_INT >= 21 || !(target instanceof AbsListView))
&& (target == null || ViewCompat.isNestedScrollingEnabled(target))) {
super.requestDisallowInterceptTouchEvent(b);
//} else {
// Nope.
}
}
//
//
protected void notifyStateChanged(RefreshState state) {
final RefreshState oldState = mState;
if (oldState != state) {
mState = state;
mViceState = state;
if (mRefreshFooter != null) {
mRefreshFooter.onStateChanged(this, oldState, state);
}
if (mRefreshHeader != null) {
mRefreshHeader.onStateChanged(this, oldState, state);
}
if (mOnMultiPurposeListener != null) {
mOnMultiPurposeListener.onStateChanged(this, oldState, state);
}
}
}
protected void setStatePullUpToLoad() {
if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) {
notifyStateChanged(RefreshState.PullToUpLoad);
} else {
setViceState(RefreshState.PullToUpLoad);
}
}
protected void setStateReleaseToLoad() {
if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) {
notifyStateChanged(RefreshState.ReleaseToLoad);
} else {
setViceState(RefreshState.ReleaseToLoad);
}
}
protected void setStateReleaseToRefresh() {
if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) {
notifyStateChanged(RefreshState.ReleaseToRefresh);
} else {
setViceState(RefreshState.ReleaseToRefresh);
}
}
protected void setStatePullDownToRefresh() {
if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) {
notifyStateChanged(RefreshState.PullDownToRefresh);
} else {
setViceState(RefreshState.PullDownToRefresh);
}
}
protected void setStatePullDownCanceled() {
if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) {
notifyStateChanged(RefreshState.PullDownCanceled);
resetStatus();
} else {
setViceState(RefreshState.PullDownCanceled);
}
}
protected void setStatePullUpCanceled() {
if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) {
notifyStateChanged(RefreshState.PullUpCanceled);
resetStatus();
} else {
setViceState(RefreshState.PullUpCanceled);
}
}
protected void setStateLodingFinish() {
notifyStateChanged(RefreshState.LoadFinish);
}
protected void setStateRefresingFinish() {
notifyStateChanged(RefreshState.RefreshFinish);
}
protected void setStateLoding() {
mLastLoadingTime = currentTimeMillis();
notifyStateChanged(RefreshState.Loading);
animSpinner(-mFooterHeight);
if (mLoadmoreListener != null) {
mLoadmoreListener.onLoadmore(this);
}
if (mRefreshFooter != null) {
mRefreshFooter.onStartAnimator(this, mFooterHeight, mFooterExtendHeight);
}
if (mOnMultiPurposeListener != null) {
mOnMultiPurposeListener.onLoadmore(this);
mOnMultiPurposeListener.onFooterStartAnimator(mRefreshFooter, mFooterHeight, mFooterExtendHeight);
}
}
protected void setStateRefresing() {
mLastRefreshingTime = currentTimeMillis();
notifyStateChanged(RefreshState.Refreshing);
animSpinner(mHeaderHeight);
if (mRefreshListener != null) {
mRefreshListener.onRefresh(this);
}
if (mRefreshHeader != null) {
mRefreshHeader.onStartAnimator(this, mHeaderHeight, mHeaderExtendHeight);
}
if (mOnMultiPurposeListener != null) {
mOnMultiPurposeListener.onRefresh(this);
mOnMultiPurposeListener.onHeaderStartAnimator(mRefreshHeader, mHeaderHeight, mHeaderExtendHeight);
}
}
/**
* 重置状态
*/
protected void resetStatus() {
if (mState != RefreshState.None) {
if (mSpinner == 0) {
notifyStateChanged(RefreshState.None);
}
}
if (mSpinner != 0) {
animSpinner(0);
}
}
protected RefreshState getViceState() {
return (mState == RefreshState.Refreshing || mState == RefreshState.Loading) ? mViceState : mState;
}
protected void setViceState(RefreshState state) {
if (mState == RefreshState.Refreshing || mState == RefreshState.Loading) {
if (mViceState != state) {
mViceState = state;
}
}
}
//
//
//
protected ValueAnimator reboundAnimator;
protected AnimatorListener reboundAnimatorEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
reboundAnimator = null;
if ((int) ((ValueAnimator) animation).getAnimatedValue() == 0) {
if (mState != RefreshState.None && mState != RefreshState.Refreshing && mState != RefreshState.Loading) {
notifyStateChanged(RefreshState.None);
}
}
}
};
protected AnimatorUpdateListener reboundUpdateListener = new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
moveSpinner((int) animation.getAnimatedValue(), true);
}
};
//
protected ValueAnimator animSpinner(int endSpinner) {
return animSpinner(endSpinner, 0);
}
protected ValueAnimator animSpinner(int endSpinner, int startDelay) {
return animSpinner(endSpinner, startDelay, mReboundInterpolator);
}
/**
* 执行回弹动画
*/
protected ValueAnimator animSpinner(int endSpinner, int startDelay, Interpolator interpolator) {
if (mSpinner != endSpinner) {
if (reboundAnimator != null) {
reboundAnimator.cancel();
}
reboundAnimator = ValueAnimator.ofInt(mSpinner, endSpinner);
reboundAnimator.setDuration(mReboundDuration);
reboundAnimator.setInterpolator(interpolator);
reboundAnimator.addUpdateListener(reboundUpdateListener);
reboundAnimator.addListener(reboundAnimatorEndListener);
reboundAnimator.setStartDelay(startDelay);
reboundAnimator.start();
}
return reboundAnimator;
}
/**
* 越界回弹动画
*/
protected ValueAnimator animSpinnerBounce(int bounceSpinner) {
if (reboundAnimator == null) {
mLastTouchX = getMeasuredWidth() / 2;
if (mState == RefreshState.Refreshing && bounceSpinner > 0) {
reboundAnimator = ValueAnimator.ofInt(mSpinner, Math.min(2 * bounceSpinner, mHeaderHeight));
reboundAnimator.addListener(reboundAnimatorEndListener);
} else if (mState == RefreshState.Loading && bounceSpinner < 0) {
reboundAnimator = ValueAnimator.ofInt(mSpinner, Math.max(2 * bounceSpinner, -mFooterHeight));
reboundAnimator.addListener(reboundAnimatorEndListener);
} else if (mSpinner == 0 && mEnableOverScrollBounce) {
if (bounceSpinner > 0) {
if (mState != RefreshState.Loading) {
setStatePullDownToRefresh();
}
reboundAnimator = ValueAnimator.ofInt(0, Math.min(bounceSpinner, mHeaderHeight + mHeaderExtendHeight));
} else {
if (mState != RefreshState.Refreshing) {
setStatePullUpToLoad();
}
reboundAnimator = ValueAnimator.ofInt(0, Math.max(bounceSpinner, -mFooterHeight - mFooterExtendHeight));
}
reboundAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
reboundAnimator = ValueAnimator.ofInt(mSpinner, 0);
reboundAnimator.setDuration(mReboundDuration * 2 / 3);
reboundAnimator.setInterpolator(new DecelerateInterpolator());
reboundAnimator.addUpdateListener(reboundUpdateListener);
reboundAnimator.addListener(reboundAnimatorEndListener);
reboundAnimator.start();
}
});
}
if (reboundAnimator != null) {
reboundAnimator.setDuration(mReboundDuration * 2 / 3);
reboundAnimator.setInterpolator(new DecelerateInterpolator());
reboundAnimator.addUpdateListener(reboundUpdateListener);
reboundAnimator.start();
}
}
return reboundAnimator;
}
/**
* 手势拖动结束
* 开始执行回弹动画
*/
protected boolean overSpinner() {
if (mState == RefreshState.Loading) {
if (mSpinner < -mFooterHeight) {
mTotalUnconsumed = -mFooterHeight;
animSpinner(-mFooterHeight);
} else if (mSpinner > 0) {
mTotalUnconsumed = 0;
animSpinner(0);
} else {
return false;
}
} else if (mState == RefreshState.Refreshing) {
if (mSpinner > mHeaderHeight) {
mTotalUnconsumed = mHeaderHeight;
animSpinner(mHeaderHeight);
} else if (mSpinner < 0) {
mTotalUnconsumed = 0;
animSpinner(0);
} else {
return false;
}
} else if (mState == RefreshState.PullDownToRefresh
|| (mEnablePureScrollMode && mState == RefreshState.ReleaseToRefresh)) {
setStatePullDownCanceled();
} else if (mState == RefreshState.PullToUpLoad
|| (mEnablePureScrollMode && mState == RefreshState.ReleaseToLoad)) {
setStatePullUpCanceled();
} else if (mState == RefreshState.ReleaseToRefresh) {
setStateRefresing();
} else if (mState == RefreshState.ReleaseToLoad) {
setStateLoding();
} else if (mSpinner != 0) {
animSpinner(0);
} else {
return false;
}
return true;
}
protected void moveSpinnerInfinitely(float dy) {
if (mState == RefreshState.Refreshing && dy >= 0) {
if (dy < mHeaderHeight) {
moveSpinner((int) dy, false);
} else {
final double M = mHeaderExtendHeight;
final double H = Math.max(mScreenHeightPixels * 4 / 3, getHeight()) - mHeaderHeight;
final double x = Math.max(0, (dy - mHeaderHeight) * mDragRate);
final double y = Math.min(M * (1 - Math.pow(100, -x / H)), x);// 公式 y = M(1-40^(-x/H))
moveSpinner((int) y + mHeaderHeight, false);
}
} else if (mState == RefreshState.Loading && dy < 0) {
if (dy > -mFooterHeight) {
moveSpinner((int) dy, false);
} else {
final double M = mFooterExtendHeight;
final double H = Math.max(mScreenHeightPixels * 4 / 3, getHeight()) - mFooterHeight;
final double x = -Math.min(0, (dy + mHeaderHeight) * mDragRate);
final double y = -Math.min(M * (1 - Math.pow(100, -x / H)), x);// 公式 y = M(1-40^(-x/H))
moveSpinner((int) y - mFooterHeight, false);
}
} else if (dy >= 0) {
final double M = mHeaderExtendHeight + mHeaderHeight;
final double H = Math.max(mScreenHeightPixels / 2, getHeight());
final double x = Math.max(0, dy * mDragRate);
final double y = Math.min(M * (1 - Math.pow(100, -x / H)), x);// 公式 y = M(1-40^(-x/H))
moveSpinner((int) y, false);
} else {
final double M = mFooterExtendHeight + mFooterHeight;
final double H = Math.max(mScreenHeightPixels / 2, getHeight());
final double x = -Math.min(0, dy * mDragRate);
final double y = -Math.min(M * (1 - Math.pow(100, -x / H)), x);// 公式 y = M(1-40^(-x/H))
moveSpinner((int) y, false);
}
}
/**
* 移动滚动 Scroll
* moveSpinner 的取名来自 谷歌官方的 @{@link android.support.v4.widget.SwipeRefreshLayout#moveSpinner(float)}
*/
protected void moveSpinner(int spinner, boolean isAnimator) {
if (mSpinner == spinner
&& (mRefreshHeader == null || !mRefreshHeader.isSupportHorizontalDrag())
&& (mRefreshFooter == null || !mRefreshFooter.isSupportHorizontalDrag())) {
return;
}
final int oldSpinner = mSpinner;
this.mSpinner = spinner;
if (!isAnimator && getViceState().isDraging()) {
if (mSpinner > mHeaderHeight) {
setStateReleaseToRefresh();
} else if (-mSpinner > mFooterHeight && !mLoadmoreFinished) {
setStateReleaseToLoad();
} else if (mSpinner < 0 && !mLoadmoreFinished) {
setStatePullUpToLoad();
} else if (mSpinner > 0) {
setStatePullDownToRefresh();
}
}
if (mRefreshContent != null) {
if (spinner > 0) {
if (mEnableHeaderTranslationContent || mRefreshHeader == null || mRefreshHeader.getSpinnerStyle() == SpinnerStyle.FixedBehind) {
mRefreshContent.moveSpinner(spinner);
if (mHeaderBackgroundColor != 0) {
invalidate();
}
}
} else {
if (mEnableFooterTranslationContent || mRefreshFooter == null || mRefreshFooter.getSpinnerStyle() == SpinnerStyle.FixedBehind) {
mRefreshContent.moveSpinner(spinner);
if (mHeaderBackgroundColor != 0) {
invalidate();
}
}
}
}
if ((spinner > 0 || oldSpinner > 0) && mRefreshHeader != null) {
spinner = Math.max(spinner, 0);
if (mEnableRefresh || (mState == RefreshState.RefreshFinish && isAnimator)) {
if (oldSpinner != mSpinner
&& (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Scale
|| mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Translate)) {
mRefreshHeader.getView().requestLayout();
}
}
final int offset = spinner;
final int headerHeight = mHeaderHeight;
final int extendHeight = mHeaderExtendHeight;
final float percent = 1f * spinner / mHeaderHeight;
if (isAnimator) {
mRefreshHeader.onReleasing(percent, offset, headerHeight, extendHeight);
if (mOnMultiPurposeListener != null) {
mOnMultiPurposeListener.onHeaderReleasing(mRefreshHeader, percent, offset, headerHeight, extendHeight);
}
} else {
if (mRefreshHeader.isSupportHorizontalDrag()) {
final int offsetX = (int) mLastTouchX;
final int offsetMax = getWidth();
final float percentX = mLastTouchX / offsetMax;
mRefreshHeader.onHorizontalDrag(percentX, offsetX, offsetMax);
}
mRefreshHeader.onPullingDown(percent, offset, headerHeight, extendHeight);
if (mOnMultiPurposeListener != null) {
mOnMultiPurposeListener.onHeaderPulling(mRefreshHeader, percent, offset, headerHeight, extendHeight);
}
}
}
if ((spinner < 0 || oldSpinner < 0) && mRefreshFooter != null) {
spinner = Math.min(spinner, 0);
if (mEnableLoadmore || (mState == RefreshState.LoadFinish && isAnimator)) {
if (oldSpinner != mSpinner
&& (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.Scale
|| mRefreshFooter.getSpinnerStyle() == SpinnerStyle.Translate)) {
mRefreshFooter.getView().requestLayout();
}
}
final int offset = -spinner;
final int footerHeight = mFooterHeight;
final int extendHeight = mFooterExtendHeight;
final float percent = -spinner * 1f / mFooterHeight;
if (isAnimator) {
mRefreshFooter.onPullReleasing(percent, offset, footerHeight, extendHeight);
if (mOnMultiPurposeListener != null) {
mOnMultiPurposeListener.onFooterReleasing(mRefreshFooter, percent, offset, footerHeight, extendHeight);
}
} else {
if (mRefreshFooter.isSupportHorizontalDrag()) {
final int offsetX = (int) mLastTouchX;
final int offsetMax = getWidth();
final float percentX = mLastTouchX / offsetMax;
mRefreshFooter.onHorizontalDrag(percentX, offsetX, offsetMax);
}
mRefreshFooter.onPullingUp(percent, offset, footerHeight, extendHeight);
if (mOnMultiPurposeListener != null) {
mOnMultiPurposeListener.onFooterPulling(mRefreshFooter, percent, offset, footerHeight, extendHeight);
}
}
}
}
//
//
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(MATCH_PARENT, MATCH_PARENT);
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SmartRefreshLayout_Layout);
backgroundColor = ta.getColor(R.styleable.SmartRefreshLayout_Layout_layout_srlBackgroundColor, backgroundColor);
if (ta.hasValue(R.styleable.SmartRefreshLayout_Layout_layout_srlSpinnerStyle)) {
spinnerStyle = SpinnerStyle.values()[ta.getInt(R.styleable.SmartRefreshLayout_Layout_layout_srlSpinnerStyle, SpinnerStyle.Translate.ordinal())];
}
ta.recycle();
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public int backgroundColor = 0;
public SpinnerStyle spinnerStyle = null;
}
//
//
//
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
boolean accepted = isEnabled() && isNestedScrollingEnabled() && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
accepted = accepted && (mEnableRefresh || mEnableLoadmore);
return accepted;
}
@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
// Reset the counter of how much leftover scroll needs to be consumed.
mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
// Dispatch up to the nested parent
startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
mTotalUnconsumed = 0;
mTouchSpinner = mSpinner;
mNestedScrollInProgress = true;
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
// If we are in the middle of consuming, a scroll, then we want to move the spinner back up
// before allowing the list to scroll
if (mState == RefreshState.Refreshing || mState == RefreshState.Loading) {
final int[] parentConsumed = mParentScrollConsumed;
if (dispatchNestedPreScroll(dx, dy, parentConsumed, null)) {
dy -= parentConsumed[1];
}
//判断 mTotalUnconsumed和dy 同为负数或者正数
if (mState == RefreshState.Refreshing && (dy * mTotalUnconsumed > 0 || mTouchSpinner > 0)) {
consumed[1] = 0;
if (Math.abs(dy) > Math.abs(mTotalUnconsumed)) {
consumed[1] += mTotalUnconsumed;
mTotalUnconsumed = 0;
dy -= mTotalUnconsumed;
if (mTouchSpinner <= 0) {
moveSpinnerInfinitely(0);
}
} else {
mTotalUnconsumed -= dy;
consumed[1] += dy;
dy = 0;
moveSpinnerInfinitely(mTotalUnconsumed + mTouchSpinner);
}
if (dy > 0 && mTouchSpinner > 0) {
if (dy > mTouchSpinner) {
consumed[1] += mTouchSpinner;
mTouchSpinner = 0;
} else {
mTouchSpinner -= dy;
consumed[1] += dy;
}
moveSpinnerInfinitely(mTouchSpinner);
}
} else {
if (mState == RefreshState.Loading && (dy * mTotalUnconsumed > 0 || mTouchSpinner < 0)) {
consumed[1] = 0;
if (Math.abs(dy) > Math.abs(mTotalUnconsumed)) {
consumed[1] += mTotalUnconsumed;
mTotalUnconsumed = 0;
dy -= mTotalUnconsumed;
if (mTouchSpinner >= 0) {
moveSpinnerInfinitely(0);
}
} else {
mTotalUnconsumed -= dy;
consumed[1] += dy;
dy = 0;
moveSpinnerInfinitely(mTotalUnconsumed + mTouchSpinner);
}
if (dy < 0 && mTouchSpinner < 0) {
if (dy < mTouchSpinner) {
consumed[1] += mTouchSpinner;
mTouchSpinner = 0;
} else {
mTouchSpinner -= dy;
consumed[1] += dy;
}
moveSpinnerInfinitely(mTouchSpinner);
}
}
}
} else {
if (mEnableRefresh && dy > 0 && mTotalUnconsumed > 0) {
if (dy > mTotalUnconsumed) {
consumed[1] = dy - mTotalUnconsumed;
mTotalUnconsumed = 0;
} else {
mTotalUnconsumed -= dy;
consumed[1] = dy;
}
moveSpinnerInfinitely(mTotalUnconsumed);
} else if (mEnableLoadmore && dy < 0 && mTotalUnconsumed < 0) {
if (dy < mTotalUnconsumed) {
consumed[1] = dy - mTotalUnconsumed;
mTotalUnconsumed = 0;
} else {
mTotalUnconsumed -= dy;
consumed[1] = dy;
}
moveSpinnerInfinitely(mTotalUnconsumed);
}
// If a client layout is using a custom start position for the circle
// view, they mean to hide it again before scrolling the child view
// If we get back to mTotalUnconsumed == 0 and there is more to go, hide
// the circle so it isn't exposed if its blocking content is moved
// if (mUsingCustomStart && dy > 0 && mTotalUnconsumed == 0
// && Math.abs(dy - consumed[1]) > 0) {
// mCircleView.setVisibility(View.GONE);
// }
// Now let our nested parent consume the leftovers
final int[] parentConsumed = mParentScrollConsumed;
if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) {
consumed[0] += parentConsumed[0];
consumed[1] += parentConsumed[1];
}
}
}
@Override
public int getNestedScrollAxes() {
return mNestedScrollingParentHelper.getNestedScrollAxes();
}
@Override
public void onStopNestedScroll(View target) {
mNestedScrollingParentHelper.onStopNestedScroll(target);
mNestedScrollInProgress = false;
// Finish the spinner for nested scrolling if we ever consumed any
// unconsumed nested scroll
// if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) {
// }
mTotalUnconsumed = 0;
overSpinner();
// Dispatch up our nested parent
stopNestedScroll();
}
@Override
public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed,
final int dxUnconsumed, final int dyUnconsumed) {
// Dispatch up to the nested parent first
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
mParentOffsetInWindow);
// This is a bit of a hack. Nested scrolling works from the bottom up, and as we are
// sometimes between two nested scrolling views, we need a way to be able to know when any
// nested scrolling parent has stopped handling events. We do that by using the
// 'offset in window 'functionality to see if we have been moved from the event.
// This is a decent indication of whether we should take over the event stream or not.
final int dy = dyUnconsumed + mParentOffsetInWindow[1];
if (mState == RefreshState.Refreshing || mState == RefreshState.Loading) {
if (mEnableRefresh && dy < 0 && (mRefreshContent == null || mRefreshContent.canRefresh())) {
mTotalUnconsumed += Math.abs(dy);
moveSpinnerInfinitely(mTotalUnconsumed + mTouchSpinner);
} else if (mEnableLoadmore && dy > 0 && (mRefreshContent == null || mRefreshContent.canLoadmore())) {
mTotalUnconsumed -= Math.abs(dy);
moveSpinnerInfinitely(mTotalUnconsumed + mTouchSpinner);
}
} else {
if (mEnableRefresh && dy < 0 && (mRefreshContent == null || mRefreshContent.canRefresh())) {
if (mState == RefreshState.None) {
setStatePullDownToRefresh();
}
mTotalUnconsumed += Math.abs(dy);
moveSpinnerInfinitely(mTotalUnconsumed);
} else if (mEnableLoadmore && dy > 0
&& (mRefreshContent == null || mRefreshContent.canLoadmore())) {
if (mState == RefreshState.None && !mLoadmoreFinished) {
setStatePullUpToLoad();
}
mTotalUnconsumed -= Math.abs(dy);
moveSpinnerInfinitely(mTotalUnconsumed);
}
}
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return reboundAnimator != null || mState == RefreshState.ReleaseToRefresh || mState == RefreshState.ReleaseToLoad || (mState == RefreshState.PullDownToRefresh && mSpinner > 0) || (mState == RefreshState.PullToUpLoad && mSpinner > 0) || (mState == RefreshState.Refreshing && mSpinner != 0) || (mState == RefreshState.Loading && mSpinner != 0) || dispatchNestedPreFling(velocityX, velocityY);
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return dispatchNestedFling(velocityX, velocityY, consumed);
}
//
//
@Override
public void setNestedScrollingEnabled(boolean enabled) {
mManualNestedScrolling = true;
mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return mNestedScrollingChildHelper.isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return mNestedScrollingChildHelper.startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
mNestedScrollingChildHelper.stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return mNestedScrollingChildHelper.hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow) {
return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mNestedScrollingChildHelper.dispatchNestedPreScroll(
dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
//
//
//
@Override
public SmartRefreshLayout setFooterHeight(float heightDp) {
return setFooterHeightPx(dp2px(heightDp));
}
@Override
public SmartRefreshLayout setFooterHeightPx(int heightPx) {
if (mFooterHeightStatus.canReplaceWith(DimensionStatus.CodeExact)) {
mFooterHeight = heightPx;
mFooterExtendHeight = (int) Math.max((heightPx * (mFooterMaxDragRate - 1)), 0);
mFooterHeightStatus = DimensionStatus.CodeExactUnNotify;
if (mRefreshFooter != null) {
mRefreshFooter.getView().requestLayout();
}
}
return this;
}
@Override
public SmartRefreshLayout setHeaderHeight(float heightDp) {
return setHeaderHeightPx(dp2px(heightDp));
}
@Override
public SmartRefreshLayout setHeaderHeightPx(int heightPx) {
if (mHeaderHeightStatus.canReplaceWith(DimensionStatus.CodeExact)) {
mHeaderHeight = heightPx;
mHeaderExtendHeight = (int) Math.max((heightPx * (mHeaderMaxDragRate - 1)), 0);
mHeaderHeightStatus = DimensionStatus.CodeExactUnNotify;
if (mRefreshHeader != null) {
mRefreshHeader.getView().requestLayout();
}
}
return this;
}
@Override
public SmartRefreshLayout setDragRate(float rate) {
this.mDragRate = rate;
return this;
}
/**
* 设置下拉最大高度和Header高度的比率(将会影响可以下拉的最大高度)
*/
@Override
public SmartRefreshLayout setHeaderMaxDragRate(float rate) {
this.mHeaderMaxDragRate = rate;
this.mHeaderExtendHeight = (int) Math.max((mHeaderHeight * (mHeaderMaxDragRate - 1)), 0);
if (mRefreshHeader != null && mKernel != null) {
mRefreshHeader.onInitialized(mKernel, mHeaderHeight, mHeaderExtendHeight);
} else {
mHeaderHeightStatus = mHeaderHeightStatus.unNotify();
}
return this;
}
/**
* 设置上啦最大高度和Footer高度的比率(将会影响可以上啦的最大高度)
*/
@Override
public SmartRefreshLayout setFooterMaxDragRate(float rate) {
this.mFooterMaxDragRate = rate;
this.mFooterExtendHeight = (int) Math.max((mFooterHeight * (mFooterMaxDragRate - 1)), 0);
if (mRefreshFooter != null && mKernel != null) {
mRefreshFooter.onInitialized(mKernel, mFooterHeight, mFooterExtendHeight);
} else {
mFooterHeightStatus = mFooterHeightStatus.unNotify();
}
return this;
}
/**
* 设置回弹显示插值器
*/
@Override
public SmartRefreshLayout setReboundInterpolator(Interpolator interpolator) {
this.mReboundInterpolator = interpolator;
return this;
}
/**
* 设置回弹动画时长
*/
@Override
public SmartRefreshLayout setReboundDuration(int duration) {
this.mReboundDuration = duration;
return this;
}
/**
* 设置是否启用上啦加载更多(默认启用)
*/
@Override
public SmartRefreshLayout setEnableLoadmore(boolean enable) {
this.mManualLoadmore = true;
this.mEnableLoadmore = enable;
return this;
}
/**
* 是否启用下拉刷新(默认启用)
*/
@Override
public SmartRefreshLayout setEnableRefresh(boolean enable) {
this.mEnableRefresh = enable;
return this;
}
/**
* 设置是否启用内容视图拖动效果
*/
@Override
public SmartRefreshLayout setEnableHeaderTranslationContent(boolean enable) {
this.mEnableHeaderTranslationContent = enable;
return this;
}
/**
* 设置是否启用内容视图拖动效果
*/
@Override
public SmartRefreshLayout setEnableFooterTranslationContent(boolean enable) {
this.mEnableFooterTranslationContent = enable;
return this;
}
/**
* 设置是否开启在刷新时候禁止操作内容视图
*/
@Override
public SmartRefreshLayout setDisableContentWhenRefresh(boolean disable) {
this.mDisableContentWhenRefresh = disable;
return this;
}
/**
* 设置是否开启在加载时候禁止操作内容视图
*/
@Override
public SmartRefreshLayout setDisableContentWhenLoading(boolean disable) {
this.mDisableContentWhenLoading = disable;
return this;
}
/**
* 设置是否监听列表在滚动到底部时触发加载事件
*/
@Override
public SmartRefreshLayout setEnableAutoLoadmore(boolean enable) {
this.mEnableAutoLoadmore = enable;
return this;
}
/**
* 设置是否启用越界回弹
*/
@Override
public SmartRefreshLayout setEnableOverScrollBounce(boolean enable) {
this.mEnableOverScrollBounce = enable;
return this;
}
/**
* 设置是否开启纯滚动模式
*/
@Override
public SmartRefreshLayout setEnablePureScrollMode(boolean enable) {
this.mEnablePureScrollMode = enable;
if (mRefreshContent != null) {
mRefreshContent.setEnableLoadmoreWhenContentNotFull(enable || mEnableLoadmoreWhenContentNotFull);
}
return this;
}
/**
* 设置是否在加载更多完成之后滚动内容显示新数据
*/
@Override
public SmartRefreshLayout setEnableScrollContentWhenLoaded(boolean enable) {
this.mEnableScrollContentWhenLoaded = enable;
return this;
}
/**
* 设置在内容不满一页的时候,是否可以上拉加载更多
*/
@Override
public SmartRefreshLayout setEnableLoadmoreWhenContentNotFull(boolean enable) {
this.mEnableLoadmoreWhenContentNotFull = enable;
if (mRefreshContent != null) {
mRefreshContent.setEnableLoadmoreWhenContentNotFull(enable || mEnablePureScrollMode);
}
return this;
}
/**
* 设置是会否启用嵌套滚动功能(默认关闭+智能开启)
*/
@Override
public RefreshLayout setEnableNestedScroll(boolean enabled) {
setNestedScrollingEnabled(enabled);
return this;
}
/**
* 设置指定的Header
*/
@Override
public SmartRefreshLayout setRefreshHeader(RefreshHeader header) {
if (header != null) {
if (mRefreshHeader != null) {
removeView(mRefreshHeader.getView());
}
this.mRefreshHeader = header;
this.mHeaderHeightStatus = mHeaderHeightStatus.unNotify();
if (header.getSpinnerStyle() == SpinnerStyle.FixedBehind) {
this.addView(mRefreshHeader.getView(), 0, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
} else {
this.addView(mRefreshHeader.getView(), MATCH_PARENT, WRAP_CONTENT);
}
}
return this;
}
/**
* 设置指定的Header
*/
@Override
public SmartRefreshLayout setRefreshHeader(RefreshHeader header, int width, int height) {
if (header != null) {
if (mRefreshHeader != null) {
removeView(mRefreshHeader.getView());
}
this.mRefreshHeader = header;
this.mHeaderHeightStatus = mHeaderHeightStatus.unNotify();
if (header.getSpinnerStyle() == SpinnerStyle.FixedBehind) {
this.addView(mRefreshHeader.getView(), 0, new LayoutParams(width, height));
} else {
this.addView(mRefreshHeader.getView(), width, height);
}
}
return this;
}
/**
* 设置指定的Footer
*/
@Override
public SmartRefreshLayout setRefreshFooter(RefreshFooter footer) {
if (footer != null) {
if (mRefreshFooter != null) {
removeView(mRefreshFooter.getView());
}
this.mRefreshFooter = footer;
this.mFooterHeightStatus = mFooterHeightStatus.unNotify();
this.mEnableLoadmore = !mManualLoadmore || mEnableLoadmore;
if (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.FixedBehind) {
this.addView(mRefreshFooter.getView(), 0, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
} else {
this.addView(mRefreshFooter.getView(), MATCH_PARENT, WRAP_CONTENT);
}
}
return this;
}
/**
* 设置指定的Footer
*/
@Override
public SmartRefreshLayout setRefreshFooter(RefreshFooter footer, int width, int height) {
if (footer != null) {
if (mRefreshFooter != null) {
removeView(mRefreshFooter.getView());
}
this.mRefreshFooter = footer;
this.mFooterHeightStatus = mFooterHeightStatus.unNotify();
this.mEnableLoadmore = !mManualLoadmore || mEnableLoadmore;
if (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.FixedBehind) {
this.addView(mRefreshFooter.getView(), 0, new LayoutParams(width, height));
} else {
this.addView(mRefreshFooter.getView(), width, height);
}
}
return this;
}
/**
* 获取底部上啦组件的实现
*/
@Nullable
@Override
public RefreshFooter getRefreshFooter() {
return mRefreshFooter;
}
/**
* 获取顶部下拉组件的实现
*/
@Nullable
@Override
public RefreshHeader getRefreshHeader() {
return mRefreshHeader;
}
/**
* 获取状态
*/
@Override
public RefreshState getState() {
return mState;
}
/**
* 获取实体布局视图
*/
@Override
public SmartRefreshLayout getLayout() {
return this;
}
/**
* 单独设置刷新监听器
*/
@Override
public SmartRefreshLayout setOnRefreshListener(OnRefreshListener listener) {
this.mRefreshListener = listener;
return this;
}
/**
* 单独设置加载监听器
*/
@Override
public SmartRefreshLayout setOnLoadmoreListener(OnLoadmoreListener listener) {
this.mLoadmoreListener = listener;
this.mEnableLoadmore = mEnableLoadmore || (!mManualLoadmore && listener != null);
return this;
}
/**
* 同时设置刷新和加载监听器
*/
@Override
public SmartRefreshLayout setOnRefreshLoadmoreListener(OnRefreshLoadmoreListener listener) {
this.mRefreshListener = listener;
this.mLoadmoreListener = listener;
this.mEnableLoadmore = mEnableLoadmore || (!mManualLoadmore && listener != null);
return this;
}
/**
* 设置多功能监听器
*/
@Override
public SmartRefreshLayout setOnMultiPurposeListener(OnMultiPurposeListener listener) {
this.mOnMultiPurposeListener = listener;
return this;
}
/**
* 设置主题颜色
*/
@Override
public SmartRefreshLayout setPrimaryColorsId(@ColorRes int... primaryColorId) {
int[] colors = new int[primaryColorId.length];
for (int i = 0; i < primaryColorId.length; i++) {
colors[i] = ContextCompat.getColor(getContext(), primaryColorId[i]);
}
setPrimaryColors(colors);
return this;
}
/**
* 设置主题颜色
*/
@Override
public SmartRefreshLayout setPrimaryColors(int... colors) {
if (mRefreshHeader != null) {
mRefreshHeader.setPrimaryColors(colors);
}
if (mRefreshFooter != null) {
mRefreshFooter.setPrimaryColors(colors);
}
mPrimaryColors = colors;
return this;
}
/**
* 设置滚动边界
*/
@Override
public RefreshLayout setScrollBoundaryDecider(ScrollBoundaryDecider boundary) {
mScrollBoundaryDecider = boundary;
if (mRefreshContent != null) {
mRefreshContent.setScrollBoundaryDecider(boundary);
}
return this;
}
/**
* 设置数据全部加载完成,将不能再次触发加载功能
*/
@Override
public SmartRefreshLayout setLoadmoreFinished(boolean finished) {
mLoadmoreFinished = finished;
if (mRefreshFooter != null) {
mRefreshFooter.setLoadmoreFinished(finished);
}
return this;
}
/**
* 完成刷新
*/
@Override
public SmartRefreshLayout finishRefresh() {
long passTime = System.currentTimeMillis() - mLastRefreshingTime;
return finishRefresh(Math.max(0, 1000 - (int) passTime));//保证刷新动画有1000毫秒的时间
}
/**
* 完成加载
*/
@Override
public SmartRefreshLayout finishLoadmore() {
long passTime = System.currentTimeMillis() - mLastLoadingTime;
return finishLoadmore(Math.max(0, 1000 - (int) passTime));//保证加载动画有1000毫秒的时间
}
/**
* 完成刷新
*/
@Override
public SmartRefreshLayout finishRefresh(int delayed) {
return finishRefresh(delayed, true);
}
/**
* 完成刷新
*/
@Override
public SmartRefreshLayout finishRefresh(boolean success) {
long passTime = System.currentTimeMillis() - mLastRefreshingTime;
return finishRefresh(Math.max(0, 1000 - (int) passTime), success);
}
/**
* 完成刷新
*/
@Override
public SmartRefreshLayout finishRefresh(int delayed, final boolean success) {
postDelayed(new Runnable() {
@Override
public void run() {
if (mState == RefreshState.Refreshing) {
if (mRefreshHeader != null) {
int startDelay = mRefreshHeader.onFinish(SmartRefreshLayout.this, success);
notifyStateChanged(RefreshState.RefreshFinish);
if (mOnMultiPurposeListener != null) {
mOnMultiPurposeListener.onHeaderFinish(mRefreshHeader, success);
}
if (startDelay < Integer.MAX_VALUE) {
if (mSpinner == 0) {
resetStatus();
} else {
animSpinner(0, startDelay);
}
}
} else {
resetStatus();
}
}
}
}, delayed);
return this;
}
/**
* 完成加载
*/
@Override
public SmartRefreshLayout finishLoadmore(int delayed) {
return finishLoadmore(delayed, true);
}
/**
* 完成加载
*/
@Override
public SmartRefreshLayout finishLoadmore(boolean success) {
long passTime = System.currentTimeMillis() - mLastLoadingTime;
return finishLoadmore(Math.max(0, 1000 - (int) passTime), success);
}
/**
* 完成加载
*/
@Override
public SmartRefreshLayout finishLoadmore(int delayed, final boolean success) {
postDelayed(new Runnable() {
@Override
public void run() {
if (mState == RefreshState.Loading) {
if (mRefreshFooter != null && mKernel != null && mRefreshContent != null) {
int startDelay = mRefreshFooter.onFinish(SmartRefreshLayout.this, success);
if (startDelay == Integer.MAX_VALUE) {
return;
}
notifyStateChanged(RefreshState.LoadFinish);
AnimatorUpdateListener updateListener = mRefreshContent.onLoadingFinish(mKernel, mFooterHeight, startDelay, mReboundDuration);
if (mOnMultiPurposeListener != null) {
mOnMultiPurposeListener.onFooterFinish(mRefreshFooter, success);
}
if (mSpinner == 0) {
resetStatus();
} else {
ValueAnimator valueAnimator = animSpinner(0, startDelay);
if (updateListener != null && valueAnimator != null) {
valueAnimator.addUpdateListener(updateListener);
}
}
} else {
resetStatus();
}
}
}
}, delayed);
return this;
}
/**
* 是否正在刷新
*/
@Override
public boolean isRefreshing() {
return mState == RefreshState.Refreshing;
}
/**
* 是否正在加载
*/
@Override
public boolean isLoading() {
return mState == RefreshState.Loading;
}
/**
* 自动刷新
*/
@Override
public boolean autoRefresh() {
return autoRefresh(400);
}
/**
* 自动刷新
*/
@Override
public boolean autoRefresh(int delayed) {
return autoRefresh(delayed, 1f * (mHeaderHeight + mHeaderExtendHeight / 2) / mHeaderHeight);
}
/**
* 自动刷新
*/
@Override
public boolean autoRefresh(int delayed, final float dragrate) {
if (mState == RefreshState.None && mEnableRefresh) {
if (reboundAnimator != null) {
reboundAnimator.cancel();
}
Runnable runnable = new Runnable() {
@Override
public void run() {
reboundAnimator = ValueAnimator.ofInt(mSpinner, (int) (mHeaderHeight * dragrate));
reboundAnimator.setDuration(mReboundDuration);
reboundAnimator.setInterpolator(new DecelerateInterpolator());
reboundAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
moveSpinner((int) animation.getAnimatedValue(), false);
}
});
reboundAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mLastTouchX = getMeasuredWidth() / 2;
setStatePullDownToRefresh();
}
@Override
public void onAnimationEnd(Animator animation) {
reboundAnimator = null;
if (mState != RefreshState.ReleaseToRefresh) {
setStateReleaseToRefresh();
}
overSpinner();
}
});
reboundAnimator.start();
}
};
if (delayed > 0) {
reboundAnimator = new ValueAnimator();
postDelayed(runnable, delayed);
} else {
runnable.run();
}
return true;
} else {
return false;
}
}
/**
* 自动加载
*/
@Override
public boolean autoLoadmore() {
return autoLoadmore(0);
}
/**
* 自动加载
*/
@Override
public boolean autoLoadmore(int delayed) {
return autoLoadmore(delayed, 1f * (mFooterHeight + mFooterExtendHeight / 2) / mFooterHeight);
}
/**
* 自动加载
*/
@Override
public boolean autoLoadmore(int delayed, final float dragrate) {
if (mState == RefreshState.None && (mEnableLoadmore && !mLoadmoreFinished)) {
if (reboundAnimator != null) {
reboundAnimator.cancel();
}
Runnable runnable = new Runnable() {
@Override
public void run() {
reboundAnimator = ValueAnimator.ofInt(mSpinner, -(int) (mFooterHeight * dragrate));
reboundAnimator.setDuration(mReboundDuration);
reboundAnimator.setInterpolator(new DecelerateInterpolator());
reboundAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
moveSpinner((int) animation.getAnimatedValue(), false);
}
});
reboundAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mLastTouchX = getMeasuredWidth() / 2;
setStatePullUpToLoad();
}
@Override
public void onAnimationEnd(Animator animation) {
reboundAnimator = null;
if (mState != RefreshState.ReleaseToLoad) {
setStateReleaseToLoad();
}
overSpinner();
}
});
reboundAnimator.start();
}
};
if (delayed > 0) {
reboundAnimator = new ValueAnimator();
postDelayed(runnable, delayed);
} else {
runnable.run();
}
return true;
} else {
return false;
}
}
@Override
public boolean isEnableLoadmore() {
return mEnableLoadmore;
}
@Override
public boolean isLoadmoreFinished() {
return mLoadmoreFinished;
}
@Override
public boolean isEnableAutoLoadmore() {
return mEnableAutoLoadmore;
}
@Override
public boolean isEnableRefresh() {
return mEnableRefresh;
}
@Override
public boolean isEnableOverScrollBounce() {
return mEnableOverScrollBounce;
}
@Override
public boolean isEnablePureScrollMode() {
return mEnablePureScrollMode;
}
@Override
public boolean isEnableScrollContentWhenLoaded() {
return mEnableScrollContentWhenLoaded;
}
/**
* 设置默认Header构建器
*/
public static void setDefaultRefreshHeaderCreater(@NonNull DefaultRefreshHeaderCreater creater) {
sHeaderCreater = creater;
}
/**
* 设置默认Footer构建器
*/
public static void setDefaultRefreshFooterCreater(@NonNull DefaultRefreshFooterCreater creater) {
sFooterCreater = creater;
sManualFooterCreater = true;
}
//
//
protected class RefreshKernelImpl implements RefreshKernel {
@NonNull
@Override
public RefreshLayout getRefreshLayout() {
return SmartRefreshLayout.this;
}
@NonNull
@Override
public RefreshContent getRefreshContent() {
return SmartRefreshLayout.this.mRefreshContent;
}
//
public RefreshKernel setStatePullUpToLoad() {
SmartRefreshLayout.this.setStatePullUpToLoad();
return this;
}
public RefreshKernel setStateReleaseToLoad() {
SmartRefreshLayout.this.setStateReleaseToLoad();
return this;
}
public RefreshKernel setStateReleaseToRefresh() {
SmartRefreshLayout.this.setStateReleaseToRefresh();
return this;
}
public RefreshKernel setStatePullDownToRefresh() {
SmartRefreshLayout.this.setStatePullDownToRefresh();
return this;
}
public RefreshKernel setStatePullDownCanceled() {
SmartRefreshLayout.this.setStatePullDownCanceled();
return this;
}
public RefreshKernel setStatePullUpCanceled() {
SmartRefreshLayout.this.setStatePullUpCanceled();
return this;
}
public RefreshKernel setStateLoding() {
SmartRefreshLayout.this.setStateLoding();
return this;
}
public RefreshKernel setStateRefresing() {
SmartRefreshLayout.this.setStateRefresing();
return this;
}
@Override
public RefreshKernel setStateLodingFinish() {
SmartRefreshLayout.this.setStateLodingFinish();
return this;
}
@Override
public RefreshKernel setStateRefresingFinish() {
SmartRefreshLayout.this.setStateRefresingFinish();
return this;
}
public RefreshKernel resetStatus() {
SmartRefreshLayout.this.resetStatus();
return this;
}
//
//
public RefreshKernel overSpinner() {
SmartRefreshLayout.this.overSpinner();
return this;
}
public RefreshKernel moveSpinnerInfinitely(float dy) {
SmartRefreshLayout.this.moveSpinnerInfinitely(dy);
return this;
}
public RefreshKernel moveSpinner(int spinner, boolean isAnimator) {
SmartRefreshLayout.this.moveSpinner(spinner, isAnimator);
return this;
}
public RefreshKernel animSpinner(int endSpinner) {
SmartRefreshLayout.this.animSpinner(endSpinner);
return this;
}
@Override
public RefreshKernel animSpinnerBounce(int bounceSpinner) {
SmartRefreshLayout.this.animSpinnerBounce(bounceSpinner);
return this;
}
@Override
public int getSpinner() {
return mSpinner;
}
//
//
@Override
public RefreshKernel requestDrawBackgoundForHeader(int backgroundColor) {
if (mPaint == null && backgroundColor != 0) {
mPaint = new Paint();
}
mHeaderBackgroundColor = backgroundColor;
return this;
}
@Override
public RefreshKernel requestDrawBackgoundForFooter(int backgroundColor) {
if (mPaint == null && backgroundColor != 0) {
mPaint = new Paint();
}
mFooterBackgroundColor = backgroundColor;
return this;
}
@Override
public RefreshKernel requestHeaderNeedTouchEventWhenRefreshing(boolean request) {
mHeaderNeedTouchEventWhenRefreshing = request;
return this;
}
@Override
public RefreshKernel requestFooterNeedTouchEventWhenLoading(boolean request) {
mFooterNeedTouchEventWhenRefreshing = request;
return this;
}
@Override
public RefreshKernel requestRemeasureHeightForHeader() {
if (mHeaderHeightStatus.notifyed) {
mHeaderHeightStatus = mHeaderHeightStatus.unNotify();
}
return this;
}
@Override
public RefreshKernel requestRemeasureHeightForFooter() {
if (mFooterHeightStatus.notifyed) {
mFooterHeightStatus = mFooterHeightStatus.unNotify();
}
return this;
}
//
}
//
//
@Override
public boolean post(Runnable action) {
if (handler == null) {
mDelayedRunables = mDelayedRunables == null ? new ArrayList() : mDelayedRunables;
mDelayedRunables.add(new DelayedRunable(action));
return false;
}
return handler.post(new DelayedRunable(action));
}
@Override
public boolean postDelayed(Runnable action, long delayMillis) {
if (handler == null) {
mDelayedRunables = mDelayedRunables == null ? new ArrayList() : mDelayedRunables;
mDelayedRunables.add(new DelayedRunable(action, delayMillis));
return false;
}
return handler.postDelayed(new DelayedRunable(action), delayMillis);
}
//
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/DefaultRefreshFooterCreater.java
================================================
package com.scwang.smartrefresh.layout.api;
import android.content.Context;
import android.support.annotation.NonNull;
/**
* 默认Footer创建器
* Created by SCWANG on 2017/5/26.
*/
public interface DefaultRefreshFooterCreater {
@NonNull
RefreshFooter createRefreshFooter(Context context, RefreshLayout layout);
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/DefaultRefreshHeaderCreater.java
================================================
package com.scwang.smartrefresh.layout.api;
import android.content.Context;
import android.support.annotation.NonNull;
/**
* 默认Header创建器
* Created by SCWANG on 2017/5/26.
*/
public interface DefaultRefreshHeaderCreater {
@NonNull
RefreshHeader createRefreshHeader(Context context, RefreshLayout layout);
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/RefreshContent.java
================================================
package com.scwang.smartrefresh.layout.api;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
/**
* 刷新内容组件
* Created by SCWANG on 2017/5/26.
*/
public interface RefreshContent {
void moveSpinner(int spinner);
boolean canRefresh();
boolean canLoadmore();
int getMeasuredWidth();
int getMeasuredHeight();
void measure(int widthSpec, int heightSpec);
void layout(int left, int top, int right, int bottom);
View getView();
View getScrollableView();
ViewGroup.LayoutParams getLayoutParams();
void onActionDown(MotionEvent e);
void onActionUpOrCancel();
void setupComponent(RefreshKernel kernel, View fixedHeader, View fixedFooter);
void onInitialHeaderAndFooter(int headerHeight, int footerHeight);
void setScrollBoundaryDecider(ScrollBoundaryDecider boundary);
void setEnableLoadmoreWhenContentNotFull(boolean enable);
AnimatorUpdateListener onLoadingFinish(RefreshKernel kernel, int footerHeight, int startDelay, int reboundDuration);
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/RefreshFooter.java
================================================
package com.scwang.smartrefresh.layout.api;
/**
* 刷新底部
* Created by SCWANG on 2017/5/26.
*/
public interface RefreshFooter extends RefreshInternal {
/**
* 手指拖动下拉(会连续多次调用)
* @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+extendHeight) / footerHeight )
* @param offset 下拉的像素偏移量 0 - offset - (footerHeight+extendHeight)
* @param footerHeight Footer的高度
* @param extendHeight Footer的扩展高度
*/
void onPullingUp(float percent, int offset, int footerHeight, int extendHeight);
/**
* 手指释放之后的持续动画(会连续多次调用)
* @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+extendHeight) / footerHeight )
* @param offset 下拉的像素偏移量 0 - offset - (footerHeight+extendHeight)
* @param footerHeight Footer的高度
* @param extendHeight Footer的扩展高度
*/
void onPullReleasing(float percent, int offset, int footerHeight, int extendHeight);
/**
* 设置数据全部加载完成,将不能再次触发加载功能
* @return true 支持全部加载完成的状态显示 false 不支持
*/
boolean setLoadmoreFinished(boolean finished);
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/RefreshHeader.java
================================================
package com.scwang.smartrefresh.layout.api;
/**
* 刷新头部
* Created by SCWANG on 2017/5/26.
*/
public interface RefreshHeader extends RefreshInternal {
/**
* 手指拖动下拉(会连续多次调用)
* @param percent 下拉的百分比 值 = offset/headerHeight (0 - percent - (headerHeight+extendHeight) / headerHeight )
* @param offset 下拉的像素偏移量 0 - offset - (headerHeight+extendHeight)
* @param headerHeight Header的高度
* @param extendHeight Header的扩展高度
*/
void onPullingDown(float percent, int offset, int headerHeight, int extendHeight);
/**
* 手指释放之后的持续动画
* @param percent 下拉的百分比 值 = offset/headerHeight (0 - percent - (headerHeight+extendHeight) / headerHeight )
* @param offset 下拉的像素偏移量 0 - offset - (headerHeight+extendHeight)
* @param headerHeight Header的高度
* @param extendHeight Header的扩展高度
*/
void onReleasing(float percent, int offset, int headerHeight, int extendHeight);
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/RefreshInternal.java
================================================
package com.scwang.smartrefresh.layout.api;
import android.support.annotation.NonNull;
import android.view.View;
import com.scwang.smartrefresh.layout.constant.SpinnerStyle;
import com.scwang.smartrefresh.layout.listener.OnStateChangedListener;
/**
* 刷新内部组件
* Created by SCWANG on 2017/5/26.
*/
public interface RefreshInternal extends OnStateChangedListener {
/**
* 获取实体视图
*/
@NonNull
View getView();
/**
* 获取变换方式 {@link SpinnerStyle}
*/
SpinnerStyle getSpinnerStyle();
/**
* 设置主题颜色
* @param colors 对应Xml中配置的 srlPrimaryColor srlAccentColor
*/
void setPrimaryColors(int... colors);
/**
* 尺寸定义完成 (如果高度不改变(代码修改:setHeader),只调用一次, 在RefreshLayout#onMeasure中调用)
* @param kernel RefreshKernel
* @param height HeaderHeight or FooterHeight
* @param extendHeight extendHeaderHeight or extendFooterHeight
*/
void onInitialized(RefreshKernel kernel, int height, int extendHeight);
/**
* 水平方向的拖动
* @param percentX 下拉时,手指水平坐标对屏幕的占比(0 - percentX - 1)
* @param offsetX 下拉时,手指水平坐标对屏幕的偏移(0 - offsetX - LayoutWidth)
*/
void onHorizontalDrag(float percentX, int offsetX, int offsetMax);
/**
* 开始动画
* @param layout RefreshLayout
* @param height HeaderHeight or FooterHeight
* @param extendHeight extendHeaderHeight or extendFooterHeight
*/
void onStartAnimator(RefreshLayout layout, int height, int extendHeight);
/**
* 动画结束
* @param layout RefreshLayout
* @param success 数据是否成功刷新或加载
* @return 完成动画所需时间 如果返回 Integer.MAX_VALUE 将取消本次完成事件,继续保持原有状态
*/
int onFinish(RefreshLayout layout, boolean success);
/**
* 是否支持水平方向的拖动(将会影响到onHorizontalDrag的调用)
* @return 水平拖动需要消耗更多的时间和资源,所以如果不支持请返回false
*/
boolean isSupportHorizontalDrag();
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/RefreshKernel.java
================================================
package com.scwang.smartrefresh.layout.api;
import android.support.annotation.NonNull;
/**
* 刷新布局核心功能接口
* 为功能复杂的 Header 或者 Footer 开放的接口
* Created by SCWANG on 2017/5/26.
*/
public interface RefreshKernel {
@NonNull
RefreshLayout getRefreshLayout();
@NonNull
RefreshContent getRefreshContent();
//
RefreshKernel setStatePullUpToLoad() ;
RefreshKernel setStateReleaseToLoad() ;
RefreshKernel setStateReleaseToRefresh() ;
RefreshKernel setStatePullDownToRefresh() ;
RefreshKernel setStatePullDownCanceled() ;
RefreshKernel setStatePullUpCanceled() ;
RefreshKernel setStateLoding() ;
RefreshKernel setStateRefresing() ;
RefreshKernel setStateLodingFinish() ;
RefreshKernel setStateRefresingFinish() ;
RefreshKernel resetStatus() ;
//
//
/**
* 结束视图位移(调用之后,如果没有在初始位移状态,会执行动画回到初始位置)
* moveSpinner 的取名来自 谷歌官方的 @{@link android.support.v4.widget.SwipeRefreshLayout#moveSpinner(float)}
*/
RefreshKernel overSpinner() ;
/**
* 移动视图到预设距离(dy 会被内部函数计算,将会出现无限接近最大值(height+extendHeader)的阻尼效果)
* moveSpinner 的取名来自 谷歌官方的 @{@link android.support.v4.widget.SwipeRefreshLayout#moveSpinner(float)}
* @param dy 距离 (px) 大于0表示下拉 小于0表示上啦
*/
RefreshKernel moveSpinnerInfinitely(float dy);
/**
* 移动视图到指定位置
* moveSpinner 的取名来自 谷歌官方的 @{@link android.support.v4.widget.SwipeRefreshLayout#moveSpinner(float)}
* @param spinner 位置 (px)
* @param isAnimator 标记是否是动画执行
*/
RefreshKernel moveSpinner(int spinner, boolean isAnimator);
/**
* 执行动画使视图位移到指定的 位置
* moveSpinner 的取名来自 谷歌官方的 @{@link android.support.v4.widget.SwipeRefreshLayout#moveSpinner(float)}
* @param endSpinner 指定的结束位置 (px)
*/
RefreshKernel animSpinner(int endSpinner);
/**
* 回弹动画
* @param bounceSpinner 回弹的最大位置 (px)
*/
RefreshKernel animSpinnerBounce(int bounceSpinner);
/**
* 获取 Spinner
*/
int getSpinner();
//
//
/**
* 指定在下拉时候为 Header 绘制背景
* @param backgroundColor 背景颜色
*/
RefreshKernel requestDrawBackgoundForHeader(int backgroundColor);
/**
* 指定在下拉时候为 Footer 绘制背景
* @param backgroundColor 背景颜色
*/
RefreshKernel requestDrawBackgoundForFooter(int backgroundColor);
/**
* 请求事件
*/
RefreshKernel requestHeaderNeedTouchEventWhenRefreshing(boolean request);
/**
* 请求事件
*/
RefreshKernel requestFooterNeedTouchEventWhenLoading(boolean request);
/**
* 请求重新测量
*/
RefreshKernel requestRemeasureHeightForHeader();
/**
* 请求重新测量
*/
RefreshKernel requestRemeasureHeightForFooter();
//
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/RefreshLayout.java
================================================
package com.scwang.smartrefresh.layout.api;
import android.support.annotation.ColorRes;
import android.support.annotation.Nullable;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.scwang.smartrefresh.layout.listener.OnLoadmoreListener;
import com.scwang.smartrefresh.layout.listener.OnMultiPurposeListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadmoreListener;
/**
* 刷新布局
* Created by SCWANG on 2017/5/26.
*/
public interface RefreshLayout {
RefreshLayout setFooterHeight(float dp);
RefreshLayout setFooterHeightPx(int px);
RefreshLayout setHeaderHeight(float dp);
RefreshLayout setHeaderHeightPx(int px);
/**
* 显示拖动高度/真实拖动高度(默认0.5,阻尼效果)
*/
RefreshLayout setDragRate(float rate);
/**
* 设置下拉最大高度和Header高度的比率(将会影响可以下拉的最大高度)
*/
RefreshLayout setHeaderMaxDragRate(float rate);
/**
* 设置上啦最大高度和Footer高度的比率(将会影响可以上啦的最大高度)
*/
RefreshLayout setFooterMaxDragRate(float rate);
/**
* 设置回弹显示插值器
*/
RefreshLayout setReboundInterpolator(Interpolator interpolator);
/**
* 设置回弹动画时长
*/
RefreshLayout setReboundDuration(int duration);
/**
* 设置是否启用上啦加载更多(默认启用)
*/
RefreshLayout setEnableLoadmore(boolean enable);
/**
* 是否启用下拉刷新(默认启用)
*/
RefreshLayout setEnableRefresh(boolean enable);
/**
* 设置是否启在下拉Header的同时下拉内容
*/
RefreshLayout setEnableHeaderTranslationContent(boolean enable);
/**
* 设置是否启在上拉Footer的同时上拉内容
*/
RefreshLayout setEnableFooterTranslationContent(boolean enable);
/**
* 设置是否开启在刷新时候禁止操作内容视图
*/
RefreshLayout setDisableContentWhenRefresh(boolean disable);
/**
* 设置是否开启在加载时候禁止操作内容视图
*/
RefreshLayout setDisableContentWhenLoading(boolean disable);
/**
* 设置是否监听列表在滚动到底部时触发加载事件(默认true)
*/
RefreshLayout setEnableAutoLoadmore(boolean enable);
/**
* 设置数据全部加载完成,将不能再次触发加载功能
*/
RefreshLayout setLoadmoreFinished(boolean finished);
/**
* 设置指定的Footer
*/
RefreshLayout setRefreshFooter(RefreshFooter footer);
/**
* 设置指定的Footer
*/
RefreshLayout setRefreshFooter(RefreshFooter footer, int width, int height);
/**
* 设置指定的Header
*/
RefreshLayout setRefreshHeader(RefreshHeader header);
/**
* 设置指定的Header
*/
RefreshLayout setRefreshHeader(RefreshHeader header, int width, int height);
/**
* 设置是否启用越界回弹
*/
RefreshLayout setEnableOverScrollBounce(boolean enable);
/**
* 设置是否开启纯滚动模式
*/
RefreshLayout setEnablePureScrollMode(boolean enable);
/**
* 设置是否在加载更多完成之后滚动内容显示新数据
*/
RefreshLayout setEnableScrollContentWhenLoaded(boolean enable);
/**
* 设置在内容不满一页的时候,是否可以上拉加载更多
*/
RefreshLayout setEnableLoadmoreWhenContentNotFull(boolean enable);
/**
* 设置是会否启用嵌套滚动功能(默认关闭+智能开启)
*/
RefreshLayout setEnableNestedScroll(boolean enabled);
/**
* 单独设置刷新监听器
*/
RefreshLayout setOnRefreshListener(OnRefreshListener listener);
/**
* 单独设置加载监听器
*/
RefreshLayout setOnLoadmoreListener(OnLoadmoreListener listener);
/**
* 同时设置刷新和加载监听器
*/
RefreshLayout setOnRefreshLoadmoreListener(OnRefreshLoadmoreListener listener);
/**
* 设置多功能监听器
*/
RefreshLayout setOnMultiPurposeListener(OnMultiPurposeListener listener);
/**
* 设置主题颜色
*/
RefreshLayout setPrimaryColorsId(@ColorRes int... primaryColorId);
/**
* 设置主题颜色
*/
RefreshLayout setPrimaryColors(int... colors);
/**
* 设置滚动边界判断器
*/
RefreshLayout setScrollBoundaryDecider(ScrollBoundaryDecider boundary);
/**
* 完成刷新
*/
RefreshLayout finishRefresh();
/**
* 完成加载
*/
RefreshLayout finishLoadmore();
/**
* 完成刷新
*/
RefreshLayout finishRefresh(int delayed);
/**
* 完成加载
* @param success 数据是否成功刷新 (会影响到上次更新时间的改变)
*/
RefreshLayout finishRefresh(boolean success);
/**
* 完成刷新
*/
RefreshLayout finishRefresh(int delayed, boolean success);
/**
* 完成加载
*/
RefreshLayout finishLoadmore(int delayed);
/**
* 完成加载
*/
RefreshLayout finishLoadmore(boolean success);
/**
* 完成加载
*/
RefreshLayout finishLoadmore(int delayed, boolean success);
/**
* 获取当前 Header
*/
@Nullable
RefreshHeader getRefreshHeader();
/**
* 获取当前 Footer
*/
@Nullable
RefreshFooter getRefreshFooter();
/**
* 获取当前状态
*/
RefreshState getState();
/**
* 获取实体布局视图
*/
ViewGroup getLayout();
/**
* 是否正在刷新
*/
boolean isRefreshing();
/**
* 是否正在加载
*/
boolean isLoading();
/**
* 自动刷新
*/
boolean autoRefresh();
/**
* 自动刷新
*/
boolean autoRefresh(int delayed);
/**
* 自动刷新
*/
boolean autoRefresh(int delayed, float dragrate);
/**
* 自动加载
*/
boolean autoLoadmore();
/**
* 自动加载
*/
boolean autoLoadmore(int delayed);
/**
* 自动加载
*/
boolean autoLoadmore(int delayed, float dragrate);
boolean isEnableRefresh();
boolean isEnableLoadmore();
boolean isLoadmoreFinished();
boolean isEnableAutoLoadmore();
boolean isEnableOverScrollBounce();
boolean isEnablePureScrollMode();
boolean isEnableScrollContentWhenLoaded();
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/ScrollBoundaryDecider.java
================================================
package com.scwang.smartrefresh.layout.api;
import android.view.View;
/**
* 滚动边界
* Created by SCWANG on 2017/7/8.
*/
public interface ScrollBoundaryDecider {
/**
* 根据内容视图状态判断是否可以开始下拉刷新
* @param content 内容视图
* @return true 将会触发下拉刷新
*/
boolean canRefresh(View content);
/**
* 根据内容视图状态判断是否可以开始上拉加载
* @param content 内容视图
* @return true 将会触发加载更多
*/
boolean canLoadmore(View content);
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/constant/DimensionStatus.java
================================================
package com.scwang.smartrefresh.layout.constant;
/**
* 尺寸值的定义状态,用于在值覆盖的时候决定优先级
* 越往下优先级越高
*/
public enum DimensionStatus {
DefaultUnNotify(false),//默认值,但是还没通知确认
Default(true),//默认值
XmlWrap(true),//Xml计算
XmlExact(true),//Xml 的view 指定
XmlLayoutUnNotify(false),//Xml 的layout 中指定,但是还没通知确认
XmlLayout(true),//Xml 的layout 中指定
CodeExactUnNotify(false),//代码指定,但是还没通知确认
CodeExact(true),//代码指定
DeadLockUnNotify(false),//锁死,但是还没通知确认
DeadLock(true);//锁死
public final boolean notifyed;
DimensionStatus(boolean notifyed) {
this.notifyed = notifyed;
}
/**
* 转换为未通知状态
*/
public DimensionStatus unNotify() {
if (notifyed) {
DimensionStatus prev = values()[ordinal() - 1];
if (!prev.notifyed) {
return prev;
}
return DefaultUnNotify;
}
return this;
}
/**
* 转换为通知状态
*/
public DimensionStatus notifyed() {
if (!notifyed) {
return values()[ordinal() + 1];
}
return this;
}
/**
* 小于等于
*/
public boolean canReplaceWith(DimensionStatus status) {
return ordinal() < status.ordinal() || ((!notifyed || CodeExact == this) && ordinal() == status.ordinal());
}
/**
* 大于等于
*/
public boolean gteReplaceWith(DimensionStatus status) {
return ordinal() >= status.ordinal();
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/constant/RefreshState.java
================================================
package com.scwang.smartrefresh.layout.constant;
public enum RefreshState {
None,
PullDownToRefresh, PullToUpLoad,
PullDownCanceled, PullUpCanceled,
ReleaseToRefresh, ReleaseToLoad,
Refreshing, Loading,
RefreshFinish, LoadFinish,;
public boolean isAnimating() {
return this == Refreshing ||
this == Loading;
}
public boolean isDraging() {
return ordinal() >= PullDownToRefresh.ordinal()
&& ordinal() <= ReleaseToLoad.ordinal()
&& this != PullDownCanceled
&& this != PullUpCanceled;
}
public boolean isDragingHeader() {
return this == PullDownToRefresh ||
this == ReleaseToRefresh;
}
public boolean isDragingFooter() {
return this == PullToUpLoad ||
this == ReleaseToLoad;
}
public boolean isHeader() {
return (ordinal() & 1) == 1;
}
public boolean isFooter() {
return (ordinal() & 1) == 0 && ordinal() > 0;
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/constant/SpinnerStyle.java
================================================
package com.scwang.smartrefresh.layout.constant;
/**
* 顶部和底部的组件在拖动时候的变换方式
* Created by SCWANG on 2017/5/26.
*/
public enum SpinnerStyle {
Translate,//平行移动 特点: HeaderView高度不会改变,
Scale,//拉伸形变 特点:在下拉和上弹(HeaderView高度改变)时候,会自动触发OnDraw事件
FixedBehind,//固定在背后 特点:HeaderView高度不会改变,
FixedFront,//固定在前面 特点:HeaderView高度不会改变,
MatchLayout//填满布局 特点:HeaderView高度不会改变,尺寸充满 RefreshLayout
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/footer/BallPulseFooter.java
================================================
package com.scwang.smartrefresh.layout.footer;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.AttrRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.scwang.smartrefresh.layout.R;
import com.scwang.smartrefresh.layout.api.RefreshFooter;
import com.scwang.smartrefresh.layout.api.RefreshKernel;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.scwang.smartrefresh.layout.constant.SpinnerStyle;
import com.scwang.smartrefresh.layout.footer.ballpulse.BallPulseView;
import com.scwang.smartrefresh.layout.util.DensityUtil;
import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.getSize;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
/**
* 球脉冲底部加载组件
* Created by SCWANG on 2017/5/30.
*/
public class BallPulseFooter extends ViewGroup implements RefreshFooter {
private BallPulseView mBallPulseView;
private SpinnerStyle mSpinnerStyle = SpinnerStyle.Translate;
//
public BallPulseFooter(@NonNull Context context) {
super(context);
initView(context, null, 0);
}
public BallPulseFooter(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context, attrs, 0);
}
public BallPulseFooter(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs, defStyleAttr);
}
private void initView(Context context, AttributeSet attrs, int defStyleAttr) {
mBallPulseView = new BallPulseView(context);
addView(mBallPulseView, WRAP_CONTENT, WRAP_CONTENT);
setMinimumHeight(DensityUtil.dp2px(60));
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.BallPulseFooter);
int primaryColor = ta.getColor(R.styleable.BallPulseFooter_srlPrimaryColor, 0);
int accentColor = ta.getColor(R.styleable.BallPulseFooter_srlAccentColor, 0);
if (primaryColor != 0) {
mBallPulseView.setNormalColor(primaryColor);
}
if (accentColor != 0) {
mBallPulseView.setAnimatingColor(accentColor);
}
mSpinnerStyle = SpinnerStyle.values()[ta.getInt(R.styleable.BallPulseFooter_srlClassicsSpinnerStyle, mSpinnerStyle.ordinal())];
ta.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSpec = makeMeasureSpec(getSize(widthMeasureSpec), AT_MOST);
int heightSpec = makeMeasureSpec(getSize(heightMeasureSpec), AT_MOST);
mBallPulseView.measure(widthSpec, heightSpec);
setMeasuredDimension(
resolveSize(mBallPulseView.getMeasuredWidth(), widthMeasureSpec),
resolveSize(mBallPulseView.getMeasuredHeight(), heightMeasureSpec)
);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int pwidth = getMeasuredWidth();
int pheight = getMeasuredHeight();
int cwidth = mBallPulseView.getMeasuredWidth();
int cheight = mBallPulseView.getMeasuredHeight();
int left = pwidth / 2 - cwidth / 2;
int top = pheight / 2 - cheight / 2;
mBallPulseView.layout(left, top, left + cwidth, top + cheight);
}
//
//
@Override
public void onInitialized(RefreshKernel kernel, int height, int extendHeight) {
}
@Override
public boolean isSupportHorizontalDrag() {
return false;
}
@Override
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
}
@Override
public void onPullingUp(float percent, int offset, int footerHeight, int extendHeight) {
}
@Override
public void onPullReleasing(float percent, int offset, int footerHeight, int extendHeight) {
}
@Override
public void onStartAnimator(RefreshLayout layout, int footerHeight, int extendHeight) {
mBallPulseView.startAnim();
}
@Override
public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) {
}
@Override
public int onFinish(RefreshLayout layout, boolean success) {
mBallPulseView.stopAnim();
return 0;
}
@Override
public boolean setLoadmoreFinished(boolean finished) {
return false;
}
@Override@Deprecated
public void setPrimaryColors(int... colors) {
if (colors.length > 1) {
mBallPulseView.setNormalColor(colors[1]);
mBallPulseView.setAnimatingColor(colors[0]);
} else if (colors.length > 0) {
mBallPulseView.setNormalColor(ColorUtils.compositeColors(0x99ffffff,colors[0]));
mBallPulseView.setAnimatingColor(colors[0]);
}
}
@NonNull
@Override
public View getView() {
return this;
}
@Override
public SpinnerStyle getSpinnerStyle() {
return mSpinnerStyle;
}
//
//
public BallPulseFooter setSpinnerStyle(SpinnerStyle mSpinnerStyle) {
this.mSpinnerStyle = mSpinnerStyle;
return this;
}
//
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/footer/ClassicsFooter.java
================================================
package com.scwang.smartrefresh.layout.footer;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.scwang.smartrefresh.layout.R;
import com.scwang.smartrefresh.layout.api.RefreshFooter;
import com.scwang.smartrefresh.layout.api.RefreshKernel;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.scwang.smartrefresh.layout.constant.SpinnerStyle;
import com.scwang.smartrefresh.layout.internal.ProgressDrawable;
import com.scwang.smartrefresh.layout.internal.pathview.PathsDrawable;
import com.scwang.smartrefresh.layout.util.DensityUtil;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
/**
* 经典上拉底部组件
* Created by SCWANG on 2017/5/28.
*/
@SuppressWarnings("unused")
public class ClassicsFooter extends RelativeLayout implements RefreshFooter {
public static String REFRESH_FOOTER_PULLUP = "上拉加载更多";
public static String REFRESH_FOOTER_RELEASE = "释放立即加载";
public static String REFRESH_FOOTER_LOADING = "正在加载...";
public static String REFRESH_FOOTER_REFRESHING = "正在刷新...";
public static String REFRESH_FOOTER_FINISH = "加载完成";
public static String REFRESH_FOOTER_FAILED = "加载失败";
public static String REFRESH_FOOTER_ALLLOADED = "全部加载完成";
protected TextView mTitleText;
protected ImageView mArrowView;
protected ImageView mProgressView;
protected PathsDrawable mArrowDrawable;
protected ProgressDrawable mProgressDrawable;
protected SpinnerStyle mSpinnerStyle = SpinnerStyle.Translate;
protected RefreshKernel mRefreshKernel;
protected int mFinishDuration = 500;
protected int mBackgroundColor = 0;
protected boolean mLoadmoreFinished = false;
protected int mPaddingTop = 20;
protected int mPaddingBottom = 20;
//
public ClassicsFooter(Context context) {
super(context);
this.initView(context, null, 0);
}
public ClassicsFooter(Context context, AttributeSet attrs) {
super(context, attrs);
this.initView(context, attrs, 0);
}
public ClassicsFooter(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.initView(context, attrs, defStyleAttr);
}
private void initView(Context context, AttributeSet attrs, int defStyleAttr) {
DensityUtil density = new DensityUtil();
mTitleText = new TextView(context);
mTitleText.setId(android.R.id.widget_frame);
mTitleText.setTextColor(0xff666666);
mTitleText.setText(REFRESH_FOOTER_PULLUP);
LayoutParams lpBottomText = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
lpBottomText.addRule(CENTER_IN_PARENT);
addView(mTitleText, lpBottomText);
LayoutParams lpArrow = new LayoutParams(density.dip2px(20), density.dip2px(20));
lpArrow.addRule(CENTER_VERTICAL);
lpArrow.addRule(LEFT_OF, android.R.id.widget_frame);
mArrowView = new ImageView(context);
addView(mArrowView, lpArrow);
LayoutParams lpProgress = new LayoutParams((ViewGroup.LayoutParams)lpArrow);
lpProgress.addRule(CENTER_VERTICAL);
lpProgress.addRule(LEFT_OF, android.R.id.widget_frame);
mProgressView = new ImageView(context);
mProgressView.animate().setInterpolator(new LinearInterpolator());
addView(mProgressView, lpProgress);
if (!isInEditMode()) {
mProgressView.setVisibility(GONE);
} else {
mArrowView.setVisibility(GONE);
}
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClassicsFooter);
lpProgress.rightMargin = ta.getDimensionPixelSize(R.styleable.ClassicsFooter_srlDrawableMarginRight, density.dip2px(20));
lpArrow.rightMargin = lpProgress.rightMargin;
lpArrow.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableArrowSize, lpArrow.width);
lpArrow.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableArrowSize, lpArrow.height);
lpProgress.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableProgressSize, lpProgress.width);
lpProgress.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableProgressSize, lpProgress.height);
lpArrow.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpArrow.width);
lpArrow.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpArrow.height);
lpProgress.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpProgress.width);
lpProgress.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpProgress.height);
mFinishDuration = ta.getInt(R.styleable.ClassicsFooter_srlFinishDuration, mFinishDuration);
mSpinnerStyle = SpinnerStyle.values()[ta.getInt(R.styleable.ClassicsFooter_srlClassicsSpinnerStyle, mSpinnerStyle.ordinal())];
if (ta.hasValue(R.styleable.ClassicsFooter_srlDrawableArrow)) {
mArrowView.setImageDrawable(ta.getDrawable(R.styleable.ClassicsFooter_srlDrawableArrow));
} else {
mArrowDrawable = new PathsDrawable();
mArrowDrawable.parserColors(0xff666666);
mArrowDrawable.parserPaths("M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z");
mArrowView.setImageDrawable(mArrowDrawable);
}
if (ta.hasValue(R.styleable.ClassicsFooter_srlDrawableProgress)) {
mProgressView.setImageDrawable(ta.getDrawable(R.styleable.ClassicsFooter_srlDrawableProgress));
} else {
mProgressDrawable = new ProgressDrawable();
mProgressDrawable.setColor(0xff666666);
mProgressView.setImageDrawable(mProgressDrawable);
}
if (ta.hasValue(R.styleable.ClassicsFooter_srlTextSizeTitle)) {
mTitleText.setTextSize(TypedValue.COMPLEX_UNIT_PX, ta.getDimensionPixelSize(R.styleable.ClassicsFooter_srlTextSizeTitle, DensityUtil.dp2px(16)));
} else {
mTitleText.setTextSize(16);
}
if (ta.hasValue(R.styleable.ClassicsFooter_srlPrimaryColor)) {
setPrimaryColor(ta.getColor(R.styleable.ClassicsFooter_srlPrimaryColor, 0));
}
if (ta.hasValue(R.styleable.ClassicsFooter_srlAccentColor)) {
setAccentColor(ta.getColor(R.styleable.ClassicsFooter_srlAccentColor, 0));
}
ta.recycle();
if (getPaddingTop() == 0) {
if (getPaddingBottom() == 0) {
setPadding(getPaddingLeft(), mPaddingTop = density.dip2px(20), getPaddingRight(), mPaddingBottom = density.dip2px(20));
} else {
setPadding(getPaddingLeft(), mPaddingTop = density.dip2px(20), getPaddingRight(), mPaddingBottom = getPaddingBottom());
}
} else {
if (getPaddingBottom() == 0) {
setPadding(getPaddingLeft(), mPaddingTop = getPaddingTop(), getPaddingRight(), mPaddingBottom = density.dip2px(20));
} else {
mPaddingTop = getPaddingTop();
mPaddingBottom = getPaddingBottom();
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
setPadding(getPaddingLeft(), 0, getPaddingRight(), 0);
} else {
setPadding(getPaddingLeft(), mPaddingTop, getPaddingRight(), mPaddingBottom);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
//
//
@Override
public void onInitialized(RefreshKernel kernel, int height, int extendHeight) {
mRefreshKernel = kernel;
mRefreshKernel.requestDrawBackgoundForFooter(mBackgroundColor);
}
@Override
public boolean isSupportHorizontalDrag() {
return false;
}
@Override
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
}
@Override
public void onPullingUp(float percent, int offset, int footerHeight, int extendHeight) {
}
@Override
public void onPullReleasing(float percent, int offset, int headHeight, int extendHeight) {
}
@Override
public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) {
if (!mLoadmoreFinished) {
mProgressView.setVisibility(VISIBLE);
if (mProgressDrawable != null) {
mProgressDrawable.start();
} else {
mProgressView.animate().rotation(36000).setDuration(100000);
}
}
}
@Override
public int onFinish(RefreshLayout layout, boolean success) {
if (!mLoadmoreFinished) {
if (mProgressDrawable != null) {
mProgressDrawable.stop();
} else {
mProgressView.animate().rotation(0).setDuration(300);
}
mProgressView.setVisibility(GONE);
if (success) {
mTitleText.setText(REFRESH_FOOTER_FINISH);
} else {
mTitleText.setText(REFRESH_FOOTER_FAILED);
}
return mFinishDuration;
}
return 0;
}
/**
* ClassicsFooter 在(SpinnerStyle.FixedBehind)时才有主题色
*/
@Override@Deprecated
public void setPrimaryColors(int... colors) {
if (mSpinnerStyle == SpinnerStyle.FixedBehind) {
if (colors.length > 0) {
if (!(getBackground() instanceof BitmapDrawable)) {
setPrimaryColor(colors[0]);
}
if (colors.length > 1) {
setAccentColor(colors[1]);
} else {
setAccentColor(colors[0] == 0xffffffff ? 0xff666666 : 0xffffffff);
}
}
}
}
/**
* 设置数据全部加载完成,将不能再次触发加载功能
*/
@Override
public boolean setLoadmoreFinished(boolean finished) {
if (mLoadmoreFinished != finished) {
mLoadmoreFinished = finished;
if (finished) {
mTitleText.setText(REFRESH_FOOTER_ALLLOADED);
} else {
mTitleText.setText(REFRESH_FOOTER_PULLUP);
}
if (mProgressDrawable != null) {
mProgressDrawable.stop();
} else {
mProgressView.animate().rotation(0).setDuration(300);
}
mProgressView.setVisibility(GONE);
mArrowView.setVisibility(GONE);
}
return true;
}
@NonNull
public View getView() {
return this;
}
@Override
public SpinnerStyle getSpinnerStyle() {
return mSpinnerStyle;
}
@Override
public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) {
if (!mLoadmoreFinished) {
switch (newState) {
case None:
// restoreRefreshLayoutBackground();
mArrowView.setVisibility(VISIBLE);
case PullToUpLoad:
mTitleText.setText(REFRESH_FOOTER_PULLUP);
mArrowView.animate().rotation(180);
break;
case Loading:
mArrowView.setVisibility(GONE);
mTitleText.setText(REFRESH_FOOTER_LOADING);
break;
case ReleaseToLoad:
mTitleText.setText(REFRESH_FOOTER_RELEASE);
mArrowView.animate().rotation(0);
// replaceRefreshLayoutBackground(refreshLayout);
break;
case Refreshing:
mTitleText.setText(REFRESH_FOOTER_REFRESHING);
mProgressView.setVisibility(GONE);
mArrowView.setVisibility(GONE);
break;
}
}
}
//
//
// private Runnable restoreRunable;
// private void restoreRefreshLayoutBackground() {
// if (restoreRunable != null) {
// restoreRunable.run();
// restoreRunable = null;
// }
// }
//
// private void replaceRefreshLayoutBackground(final RefreshLayout refreshLayout) {
// if (restoreRunable == null && mSpinnerStyle == SpinnerStyle.FixedBehind) {
// restoreRunable = new Runnable() {
// Drawable drawable = refreshLayout.getLayout().getBackground();
// @Override
// public void run() {
// refreshLayout.getLayout().setBackgroundDrawable(drawable);
// }
// };
// refreshLayout.getLayout().setBackgroundDrawable(getBackground());
// }
// }
//
//
public ClassicsFooter setProgressBitmap(Bitmap bitmap) {
mProgressDrawable = null;
mProgressView.setImageBitmap(bitmap);
return this;
}
public ClassicsFooter setProgressDrawable(Drawable drawable) {
mProgressDrawable = null;
mProgressView.setImageDrawable(drawable);
return this;
}
public ClassicsFooter setProgressResource(@DrawableRes int resId) {
mProgressDrawable = null;
mProgressView.setImageResource(resId);
return this;
}
public ClassicsFooter setArrowBitmap(Bitmap bitmap) {
mArrowDrawable = null;
mArrowView.setImageBitmap(bitmap);
return this;
}
public ClassicsFooter setArrowDrawable(Drawable drawable) {
mArrowDrawable = null;
mArrowView.setImageDrawable(drawable);
return this;
}
public ClassicsFooter setArrowResource(@DrawableRes int resId) {
mArrowDrawable = null;
mArrowView.setImageResource(resId);
return this;
}
public ClassicsFooter setSpinnerStyle(SpinnerStyle style) {
this.mSpinnerStyle = style;
return this;
}
public ClassicsFooter setAccentColor(int accentColor) {
mTitleText.setTextColor(accentColor);
if (mProgressDrawable != null) {
mProgressDrawable.setColor(accentColor);
}
if (mArrowDrawable != null) {
mArrowDrawable.parserColors(accentColor);
}
return this;
}
public ClassicsFooter setPrimaryColor(int primaryColor) {
setBackgroundColor(mBackgroundColor = primaryColor);
if (mRefreshKernel != null) {
mRefreshKernel.requestDrawBackgoundForFooter(mBackgroundColor);
}
return this;
}
public ClassicsFooter setFinishDuration(int delay) {
mFinishDuration = delay;
return this;
}
public ClassicsFooter setTextSizeTitle(float size) {
mTitleText.setTextSize(size);
if (mRefreshKernel != null) {
mRefreshKernel.requestRemeasureHeightForFooter();
}
return this;
}
public ClassicsFooter setTextSizeTitle(int unit, float size) {
mTitleText.setTextSize(unit, size);
if (mRefreshKernel != null) {
mRefreshKernel.requestRemeasureHeightForFooter();
}
return this;
}
public ClassicsFooter setDrawableMarginRight(float dp) {
return setDrawableMarginRightPx(DensityUtil.dp2px(dp));
}
public ClassicsFooter setDrawableMarginRightPx(int px) {
MarginLayoutParams lpArrow = (MarginLayoutParams)mArrowView.getLayoutParams();
MarginLayoutParams lpProgress = (MarginLayoutParams)mProgressView.getLayoutParams();
lpArrow.rightMargin = lpProgress.rightMargin = px;
mArrowView.setLayoutParams(lpArrow);
mProgressView.setLayoutParams(lpProgress);
return this;
}
public ClassicsFooter setDrawableSize(float dp) {
return setDrawableSizePx(DensityUtil.dp2px(dp));
}
public ClassicsFooter setDrawableSizePx(int px) {
ViewGroup.LayoutParams lpArrow = mArrowView.getLayoutParams();
ViewGroup.LayoutParams lpProgress = mProgressView.getLayoutParams();
lpArrow.width = lpProgress.width = px;
lpArrow.height = lpProgress.height = px;
mArrowView.setLayoutParams(lpArrow);
mProgressView.setLayoutParams(lpProgress);
return this;
}
public ClassicsFooter setDrawableArrowSize(float dp) {
return setDrawableArrowSizePx(DensityUtil.dp2px(dp));
}
public ClassicsFooter setDrawableArrowSizePx(int px) {
ViewGroup.LayoutParams lpArrow = mArrowView.getLayoutParams();
lpArrow.width = px;
lpArrow.height = px;
mArrowView.setLayoutParams(lpArrow);
return this;
}
public ClassicsFooter setDrawableProgressSize(float dp) {
return setDrawableProgressSizePx(DensityUtil.dp2px(dp));
}
public ClassicsFooter setDrawableProgressSizePx(int px) {
ViewGroup.LayoutParams lpProgress = mProgressView.getLayoutParams();
lpProgress.width = px;
lpProgress.height = px;
mProgressView.setLayoutParams(lpProgress);
return this;
}
public TextView getTitleText() {
return mTitleText;
}
public ImageView getProgressView() {
return mProgressView;
}
public ImageView getArrowView() {
return mArrowView;
}
//
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/footer/FalsifyFooter.java
================================================
package com.scwang.smartrefresh.layout.footer;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.TextView;
import com.scwang.smartrefresh.layout.api.RefreshFooter;
import com.scwang.smartrefresh.layout.header.FalsifyHeader;
import com.scwang.smartrefresh.layout.util.DensityUtil;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
/**
* 虚假的 Footer
* 用于 正真的 Footer 在 RefreshLayout 外部时,
* Created by SCWANG on 2017/6/14.
*/
public class FalsifyFooter extends FalsifyHeader implements RefreshFooter {
//
public FalsifyFooter(Context context) {
super(context);
}
public FalsifyFooter(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FalsifyFooter(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public FalsifyFooter(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override@SuppressLint("DrawAllocation")
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isInEditMode()) {//这段代码在运行时不会执行,只会在Studio编辑预览时运行,不用在意性能问题
int d = DensityUtil.dp2px(5);
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(0x44ffffff);
paint.setStrokeWidth(DensityUtil.dp2px(1));
paint.setPathEffect(new DashPathEffect(new float[]{d, d, d, d}, 1));
canvas.drawRect(d, d, getWidth() - d, getBottom() - d, paint);
TextView textView = new TextView(getContext());
textView.setText(getClass().getSimpleName()+" 虚假区域\n运行时代表上拉Footer的高度【" + DensityUtil.px2dp(getHeight()) + "dp】\n而不会显示任何东西");
textView.setTextColor(0x44ffffff);
textView.setGravity(Gravity.CENTER);
textView.measure(makeMeasureSpec(getWidth(), EXACTLY), makeMeasureSpec(getHeight(), EXACTLY));
textView.layout(0, 0, getWidth(), getHeight());
textView.draw(canvas);
}
}
//
//
@Override
public void onPullingUp(float percent, int offset, int footerHeight, int extendHeight) {
}
@Override
public void onPullReleasing(float percent, int offset, int footerHeight, int extendHeight) {
}
@Override
public boolean setLoadmoreFinished(boolean finished) {
return false;
}
//
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/footer/ballpulse/BallPulseView.java
================================================
package com.scwang.smartrefresh.layout.footer.ballpulse;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.ColorInt;
import android.util.AttributeSet;
import android.view.View;
import com.scwang.smartrefresh.layout.util.DensityUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class BallPulseView extends View {
public static final int DEFAULT_SIZE = 50; //dp
private Paint mPaint;
private int normalColor = 0xffeeeeee;
private int animatingColor = 0xffe75946;
private float circleSpacing;
private float[] scaleFloats = new float[]{1f, 1f, 1f};
private boolean mIsStarted = false;
private ArrayList mAnimators;
private Map mUpdateListeners = new HashMap<>();;
//
public BallPulseView(Context context) {
this(context, null);
}
public BallPulseView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BallPulseView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
circleSpacing = DensityUtil.dp2px(4);
mPaint = new Paint();
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int default_size = DensityUtil.dp2px(DEFAULT_SIZE);
setMeasuredDimension(resolveSize(default_size, widthMeasureSpec),
resolveSize(default_size, heightMeasureSpec));
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mAnimators != null) for (int i = 0; i < mAnimators.size(); i++) {
mAnimators.get(i).cancel();
}
}
@Override
protected void onDraw(Canvas canvas) {
float radius = (Math.min(getWidth(), getHeight()) - circleSpacing * 2) / 6;
float x = getWidth() / 2 - (radius * 2 + circleSpacing);
float y = getHeight() / 2;
for (int i = 0; i < 3; i++) {
canvas.save();
float translateX = x + (radius * 2) * i + circleSpacing * i;
canvas.translate(translateX, y);
canvas.scale(scaleFloats[i], scaleFloats[i]);
canvas.drawCircle(0, 0, radius, mPaint);
canvas.restore();
}
}
//
//
private boolean isStarted() {
// for (ValueAnimator animator : mAnimators) {
// return animator.isStarted();
// }
return mIsStarted;
}
private void createAnimators() {
mAnimators = new ArrayList<>();
int[] delays = new int[]{120, 240, 360};
for (int i = 0; i < 3; i++) {
final int index = i;
ValueAnimator scaleAnim = ValueAnimator.ofFloat(1, 0.3f, 1);
scaleAnim.setDuration(750);
scaleAnim.setRepeatCount(ValueAnimator.INFINITE);
scaleAnim.setStartDelay(delays[i]);
mUpdateListeners.put(scaleAnim, new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
scaleFloats[index] = (float) animation.getAnimatedValue();
postInvalidate();
}
});
mAnimators.add(scaleAnim);
}
}
//
//
public void setIndicatorColor(int color) {
mPaint.setColor(color);
}
public void setNormalColor(@ColorInt int color) {
normalColor = color;
}
public void setAnimatingColor(@ColorInt int color) {
animatingColor = color;
}
public void startAnim() {
if (mAnimators == null) createAnimators();
if (mAnimators == null) return;
if (isStarted()) return;
for (int i = 0; i < mAnimators.size(); i++) {
ValueAnimator animator = mAnimators.get(i);
//when the animator restart , add the updateListener again because they was removed by animator stop .
ValueAnimator.AnimatorUpdateListener updateListener = mUpdateListeners.get(animator);
if (updateListener != null) {
animator.addUpdateListener(updateListener);
}
animator.start();
}
mIsStarted = true;
setIndicatorColor(animatingColor);
}
public void stopAnim() {
if (mAnimators != null && mIsStarted) {
mIsStarted = false;
for (ValueAnimator animator : mAnimators) {
if (animator != null /*&& animator.isStarted()*/) {
animator.removeAllUpdateListeners();
animator.end();
}
}
scaleFloats = new float[]{1f, 1f, 1f};
}
setIndicatorColor(normalColor);
}
//
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/BezierRadarHeader.java
================================================
package com.scwang.smartrefresh.layout.header;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import com.scwang.smartrefresh.layout.R;
import com.scwang.smartrefresh.layout.api.RefreshHeader;
import com.scwang.smartrefresh.layout.api.RefreshKernel;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.scwang.smartrefresh.layout.constant.SpinnerStyle;
import com.scwang.smartrefresh.layout.header.bezierradar.RippleView;
import com.scwang.smartrefresh.layout.header.bezierradar.RoundDotView;
import com.scwang.smartrefresh.layout.header.bezierradar.RoundProgressView;
import com.scwang.smartrefresh.layout.header.bezierradar.WaveView;
import com.scwang.smartrefresh.layout.util.DensityUtil;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
/**
* 贝塞尔曲线类雷达风格刷新组件
* Created by lcodecore on 2016/10/2.
*/
public class BezierRadarHeader extends FrameLayout implements RefreshHeader {
private WaveView mWaveView;
private RippleView mRippleView;
private RoundDotView mDotView;
private RoundProgressView mProgressView;
private boolean mEnableHorizontalDrag = false;
//
public BezierRadarHeader(Context context) {
this(context,null);
}
public BezierRadarHeader(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public BezierRadarHeader(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs, defStyleAttr);
}
private void initView(Context context, AttributeSet attrs, int defStyleAttr) {
setMinimumHeight(DensityUtil.dp2px(100));
/**
* 初始化headView
*/
mWaveView = new WaveView(getContext());
mRippleView = new RippleView(getContext());
mDotView = new RoundDotView(getContext());
mProgressView = new RoundProgressView(getContext());
if (isInEditMode()) {
this.addView(mWaveView, MATCH_PARENT, MATCH_PARENT);
this.addView(mProgressView, MATCH_PARENT, MATCH_PARENT);
mWaveView.setHeadHeight(1000);
} else {
this.addView(mWaveView, MATCH_PARENT, MATCH_PARENT);
this.addView(mDotView, MATCH_PARENT, MATCH_PARENT);
this.addView(mProgressView, MATCH_PARENT, MATCH_PARENT);
this.addView(mRippleView, MATCH_PARENT, MATCH_PARENT);
mProgressView.setScaleX(0);
mProgressView.setScaleY(0);
}
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.BezierRadarHeader);
mEnableHorizontalDrag = ta.getBoolean(R.styleable.BezierRadarHeader_srlEnableHorizontalDrag, mEnableHorizontalDrag);
int primaryColor = ta.getColor(R.styleable.BezierRadarHeader_srlPrimaryColor, 0);
int accentColor = ta.getColor(R.styleable.BezierRadarHeader_srlAccentColor, 0);
if (primaryColor != 0) {
setPrimaryColor(primaryColor);
}
if (accentColor != 0) {
setAccentColor(primaryColor);
}
ta.recycle();
}
//
//
public BezierRadarHeader setPrimaryColor(int color) {
mWaveView.setWaveColor(color);
mProgressView.setBackColor(color);
return this;
}
public BezierRadarHeader setAccentColor(int color) {
mDotView.setDotColor(color);
mRippleView.setFrontColor(color);
mProgressView.setFrontColor(color);
return this;
}
public BezierRadarHeader setPrimaryColorId(int colorId) {
setPrimaryColor(ContextCompat.getColor(getContext(), colorId));
return this;
}
public BezierRadarHeader setAccentColorId(int colorId) {
setAccentColor(ContextCompat.getColor(getContext(), colorId));
return this;
}
public BezierRadarHeader setEnableHorizontalDrag(boolean enable) {
this.mEnableHorizontalDrag = enable;
if (!enable) {
mWaveView.setWaveOffsetX(-1);
}
return this;
}
//
//
@Override@Deprecated
public void setPrimaryColors(int... colors) {
if (colors.length > 0) {
setPrimaryColor(colors[0]);
}
if (colors.length > 1) {
setAccentColor(colors[1]);
}
}
@NonNull
public View getView() {
return this;
}
@Override
public SpinnerStyle getSpinnerStyle() {
return SpinnerStyle.Scale;
}
@Override
public void onInitialized(RefreshKernel kernel, int height, int extendHeight) {
}
@Override
public boolean isSupportHorizontalDrag() {
return mEnableHorizontalDrag;
}
@Override
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
mWaveView.setWaveOffsetX(offsetX);
mWaveView.invalidate();
}
@Override
public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) {
mWaveView.setHeadHeight(Math.min(headHeight, offset));
mWaveView.setWaveHeight((int)(1.9f*Math.max(0, offset - headHeight)));
mDotView.setFraction(percent);
}
@Override
public void onReleasing(float percent, int offset, int headHeight, int extendHeight) {
onPullingDown(percent, offset, headHeight, extendHeight);
}
@Override
public void onStartAnimator(final RefreshLayout layout, int headHeight, int extendHeight) {
mWaveView.setHeadHeight(headHeight);
ValueAnimator animator = ValueAnimator.ofInt(
mWaveView.getWaveHeight(), 0,
-(int)(mWaveView.getWaveHeight()*0.8),0,
-(int)(mWaveView.getWaveHeight()*0.4f),0);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mWaveView.setWaveHeight((int) animation.getAnimatedValue()/2);
mWaveView.invalidate();
}
});
animator.setInterpolator(new DecelerateInterpolator());
animator.setDuration(800);
animator.start();
/*处理圈圈进度条**/
ValueAnimator valueAnimator = ValueAnimator.ofFloat(1, 0);
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mDotView.setVisibility(INVISIBLE);
mProgressView.animate().scaleX((float) 1.0);
mProgressView.animate().scaleY((float) 1.0);
layout.getLayout().postDelayed(new Runnable() {
@Override
public void run() {
mProgressView.startAnim();
}
}, 200);
}
});
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setDuration(300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDotView.setAlpha((Float) animation.getAnimatedValue());
}
});
valueAnimator.start();
}
@Override
public int onFinish(RefreshLayout layout, boolean success) {
mProgressView.stopAnim();
mProgressView.animate().scaleX(0f);
mProgressView.animate().scaleY(0f);
mRippleView.setVisibility(VISIBLE);
mRippleView.startReveal();
return 400;
}
@Override
public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) {
switch (newState) {
case None:
mRippleView.setVisibility(GONE);
mDotView.setAlpha(1);
mDotView.setVisibility(VISIBLE);
break;
case PullDownToRefresh:
mProgressView.setScaleX(0);
mProgressView.setScaleY(0);
break;
case PullToUpLoad:
break;
case Refreshing:
break;
case Loading:
break;
}
}
//
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/ClassicsHeader.java
================================================
package com.scwang.smartrefresh.layout.header;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.scwang.smartrefresh.layout.R;
import com.scwang.smartrefresh.layout.api.RefreshHeader;
import com.scwang.smartrefresh.layout.api.RefreshKernel;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.scwang.smartrefresh.layout.constant.SpinnerStyle;
import com.scwang.smartrefresh.layout.internal.ProgressDrawable;
import com.scwang.smartrefresh.layout.internal.pathview.PathsDrawable;
import com.scwang.smartrefresh.layout.util.DensityUtil;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
/**
* 经典下拉头部
* Created by SCWANG on 2017/5/28.
*/
@SuppressWarnings("unused")
public class ClassicsHeader extends RelativeLayout implements RefreshHeader {
public static String REFRESH_HEADER_PULLDOWN = "下拉可以刷新";
public static String REFRESH_HEADER_REFRESHING = "正在刷新...";
public static String REFRESH_HEADER_LOADING = "正在加载...";
public static String REFRESH_HEADER_RELEASE = "释放立即刷新";
public static String REFRESH_HEADER_FINISH = "刷新完成";
public static String REFRESH_HEADER_FAILED = "刷新失败";
public static String REFRESH_HEADER_LASTTIME = "上次更新 M-d HH:mm";
protected String KEY_LAST_UPDATE_TIME = "LAST_UPDATE_TIME";
protected Date mLastTime;
protected TextView mTitleText;
protected TextView mLastUpdateText;
protected ImageView mArrowView;
protected ImageView mProgressView;
protected SharedPreferences mShared;
protected RefreshKernel mRefreshKernel;
protected PathsDrawable mArrowDrawable;
protected ProgressDrawable mProgressDrawable;
protected SpinnerStyle mSpinnerStyle = SpinnerStyle.Translate;
protected DateFormat mFormat = new SimpleDateFormat(REFRESH_HEADER_LASTTIME, Locale.CHINA);
protected int mFinishDuration = 500;
protected int mBackgroundColor;
protected int mPaddingTop = 20;
protected int mPaddingBottom = 20;
protected boolean mEnableLastTime = true;
//
public ClassicsHeader(Context context) {
super(context);
this.initView(context, null);
}
public ClassicsHeader(Context context, AttributeSet attrs) {
super(context, attrs);
this.initView(context, attrs);
}
public ClassicsHeader(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.initView(context, attrs);
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public ClassicsHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.initView(context, attrs);
}
private void initView(Context context, AttributeSet attrs) {
DensityUtil density = new DensityUtil();
LinearLayout layout = new LinearLayout(context);
layout.setId(android.R.id.widget_frame);
layout.setGravity(Gravity.CENTER_HORIZONTAL);
layout.setOrientation(LinearLayout.VERTICAL);
mTitleText = new TextView(context);
mTitleText.setText(REFRESH_HEADER_PULLDOWN);
mTitleText.setTextColor(0xff666666);
mLastUpdateText = new TextView(context);
mLastUpdateText.setTextColor(0xff7c7c7c);
LinearLayout.LayoutParams lpHeaderText = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
layout.addView(mTitleText, lpHeaderText);
LinearLayout.LayoutParams lpUpdateText = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
layout.addView(mLastUpdateText, lpUpdateText);
LayoutParams lpHeaderLayout = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
lpHeaderLayout.addRule(CENTER_IN_PARENT);
addView(layout,lpHeaderLayout);
LayoutParams lpArrow = new LayoutParams(density.dip2px(20), density.dip2px(20));
lpArrow.addRule(CENTER_VERTICAL);
lpArrow.addRule(LEFT_OF, android.R.id.widget_frame);
mArrowView = new ImageView(context);
addView(mArrowView, lpArrow);
LayoutParams lpProgress = new LayoutParams((ViewGroup.LayoutParams)lpArrow);
lpProgress.addRule(CENTER_VERTICAL);
lpProgress.addRule(LEFT_OF, android.R.id.widget_frame);
mProgressView = new ImageView(context);
mProgressView.animate().setInterpolator(new LinearInterpolator());
addView(mProgressView, lpProgress);
if (isInEditMode()) {
mArrowView.setVisibility(GONE);
mTitleText.setText(REFRESH_HEADER_REFRESHING);
} else {
mProgressView.setVisibility(GONE);
}
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClassicsHeader);
lpUpdateText.topMargin = ta.getDimensionPixelSize(R.styleable.ClassicsHeader_srlTextTimeMarginTop, density.dip2px(0));
lpProgress.rightMargin = ta.getDimensionPixelSize(R.styleable.ClassicsFooter_srlDrawableMarginRight, density.dip2px(20));
lpArrow.rightMargin = lpProgress.rightMargin;
lpArrow.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableArrowSize, lpArrow.width);
lpArrow.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableArrowSize, lpArrow.height);
lpProgress.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableProgressSize, lpProgress.width);
lpProgress.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableProgressSize, lpProgress.height);
lpArrow.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpArrow.width);
lpArrow.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpArrow.height);
lpProgress.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpProgress.width);
lpProgress.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpProgress.height);
mFinishDuration = ta.getInt(R.styleable.ClassicsHeader_srlFinishDuration, mFinishDuration);
mEnableLastTime = ta.getBoolean(R.styleable.ClassicsHeader_srlEnableLastTime, mEnableLastTime);
mSpinnerStyle = SpinnerStyle.values()[ta.getInt(R.styleable.ClassicsHeader_srlClassicsSpinnerStyle,mSpinnerStyle.ordinal())];
mLastUpdateText.setVisibility(mEnableLastTime ? VISIBLE : GONE);
if (ta.hasValue(R.styleable.ClassicsHeader_srlDrawableArrow)) {
mArrowView.setImageDrawable(ta.getDrawable(R.styleable.ClassicsHeader_srlDrawableArrow));
} else {
mArrowDrawable = new PathsDrawable();
mArrowDrawable.parserColors(0xff666666);
mArrowDrawable.parserPaths("M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z");
mArrowView.setImageDrawable(mArrowDrawable);
}
if (ta.hasValue(R.styleable.ClassicsHeader_srlDrawableProgress)) {
mProgressView.setImageDrawable(ta.getDrawable(R.styleable.ClassicsHeader_srlDrawableProgress));
} else {
mProgressDrawable = new ProgressDrawable();
mProgressDrawable.setColor(0xff666666);
mProgressView.setImageDrawable(mProgressDrawable);
}
if (ta.hasValue(R.styleable.ClassicsHeader_srlTextSizeTitle)) {
mTitleText.setTextSize(TypedValue.COMPLEX_UNIT_PX, ta.getDimensionPixelSize(R.styleable.ClassicsHeader_srlTextSizeTitle, DensityUtil.dp2px(16)));
} else {
mTitleText.setTextSize(16);
}
if (ta.hasValue(R.styleable.ClassicsHeader_srlTextSizeTime)) {
mLastUpdateText.setTextSize(TypedValue.COMPLEX_UNIT_PX, ta.getDimensionPixelSize(R.styleable.ClassicsHeader_srlTextSizeTime, DensityUtil.dp2px(12)));
} else {
mLastUpdateText.setTextSize(12);
}
int primaryColor = ta.getColor(R.styleable.ClassicsHeader_srlPrimaryColor, 0);
int accentColor = ta.getColor(R.styleable.ClassicsHeader_srlAccentColor, 0);
if (primaryColor != 0) {
if (accentColor != 0) {
setPrimaryColors(primaryColor, accentColor);
} else {
setPrimaryColors(primaryColor);
}
} else if (accentColor != 0) {
setPrimaryColors(0, accentColor);
}
ta.recycle();
if (getPaddingTop() == 0) {
if (getPaddingBottom() == 0) {
setPadding(getPaddingLeft(), mPaddingTop = density.dip2px(20), getPaddingRight(), mPaddingBottom = density.dip2px(20));
} else {
setPadding(getPaddingLeft(), mPaddingTop = density.dip2px(20), getPaddingRight(), mPaddingBottom = getPaddingBottom());
}
} else {
if (getPaddingBottom() == 0) {
setPadding(getPaddingLeft(), mPaddingTop = getPaddingTop(), getPaddingRight(), mPaddingBottom = density.dip2px(20));
} else {
mPaddingTop = getPaddingTop();
mPaddingBottom = getPaddingBottom();
}
}
try {//try 不能删除-否则会出现兼容性问题
if (context instanceof FragmentActivity) {
FragmentManager manager = ((FragmentActivity) context).getSupportFragmentManager();
if (manager != null) {
List fragments = manager.getFragments();
if (fragments != null && fragments.size() > 0) {
setLastUpdateTime(new Date());
return;
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
KEY_LAST_UPDATE_TIME += context.getClass().getName();
mShared = context.getSharedPreferences("ClassicsHeader", Context.MODE_PRIVATE);
setLastUpdateTime(new Date(mShared.getLong(KEY_LAST_UPDATE_TIME, System.currentTimeMillis())));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
setPadding(getPaddingLeft(), 0, getPaddingRight(), 0);
} else {
setPadding(getPaddingLeft(), mPaddingTop, getPaddingRight(), mPaddingBottom);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
//
//
@Override
public void onInitialized(RefreshKernel kernel, int height, int extendHeight) {
mRefreshKernel = kernel;
mRefreshKernel.requestDrawBackgoundForHeader(mBackgroundColor);
}
@Override
public boolean isSupportHorizontalDrag() {
return false;
}
@Override
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
}
@Override
public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) {
}
@Override
public void onReleasing(float percent, int offset, int headHeight, int extendHeight) {
}
@Override
public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) {
if (mProgressDrawable != null) {
mProgressDrawable.start();
} else {
Drawable drawable = mProgressView.getDrawable();
if (drawable instanceof Animatable) {
((Animatable) drawable).start();
} else {
mProgressView.animate().rotation(36000).setDuration(100000);
}
}
}
@Override
public int onFinish(RefreshLayout layout, boolean success) {
if (mProgressDrawable != null) {
mProgressDrawable.stop();
} else {
Drawable drawable = mProgressView.getDrawable();
if (drawable instanceof Animatable) {
((Animatable) drawable).stop();
} else {
mProgressView.animate().rotation(0).setDuration(300);
}
}
mProgressView.setVisibility(GONE);
if (success) {
mTitleText.setText(REFRESH_HEADER_FINISH);
setLastUpdateTime(new Date());
} else {
mTitleText.setText(REFRESH_HEADER_FAILED);
}
return mFinishDuration;//延迟500毫秒之后再弹回
}
@Override@Deprecated
public void setPrimaryColors(int... colors) {
if (colors.length > 0) {
if (!(getBackground() instanceof BitmapDrawable)) {
setPrimaryColor(colors[0]);
}
if (colors.length > 1) {
setAccentColor(colors[1]);
} else {
setAccentColor(colors[0] == 0xffffffff ? 0xff666666 : 0xffffffff);
}
}
}
@NonNull
public View getView() {
return this;
}
@Override
public SpinnerStyle getSpinnerStyle() {
return mSpinnerStyle;
}
@Override
public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) {
switch (newState) {
case None:
// restoreRefreshLayoutBackground();
mLastUpdateText.setVisibility(mEnableLastTime ? VISIBLE : GONE);
case PullDownToRefresh:
mTitleText.setText(REFRESH_HEADER_PULLDOWN);
mArrowView.setVisibility(VISIBLE);
mProgressView.setVisibility(GONE);
mArrowView.animate().rotation(0);
break;
case Refreshing:
mTitleText.setText(REFRESH_HEADER_REFRESHING);
mProgressView.setVisibility(VISIBLE);
mArrowView.setVisibility(GONE);
break;
case ReleaseToRefresh:
mTitleText.setText(REFRESH_HEADER_RELEASE);
mArrowView.animate().rotation(180);
// replaceRefreshLayoutBackground(refreshLayout);
break;
case Loading:
mArrowView.setVisibility(GONE);
mProgressView.setVisibility(GONE);
mLastUpdateText.setVisibility(GONE);
mTitleText.setText(REFRESH_HEADER_LOADING);
break;
}
}
//
//
// private Runnable restoreRunable;
// private void restoreRefreshLayoutBackground() {
// if (restoreRunable != null) {
// restoreRunable.run();
// restoreRunable = null;
// }
// }
//
// private void replaceRefreshLayoutBackground(final RefreshLayout refreshLayout) {
// if (restoreRunable == null && mSpinnerStyle == SpinnerStyle.FixedBehind) {
// restoreRunable = new Runnable() {
// Drawable drawable = refreshLayout.getLayout().getBackground();
// @Override
// public void run() {
// refreshLayout.getLayout().setBackgroundDrawable(drawable);
// }
// };
// refreshLayout.getLayout().setBackgroundDrawable(getBackground());
// }
// }
//
//
public ClassicsHeader setProgressBitmap(Bitmap bitmap) {
mProgressDrawable = null;
mProgressView.setImageBitmap(bitmap);
return this;
}
public ClassicsHeader setProgressDrawable(Drawable drawable) {
mProgressDrawable = null;
mProgressView.setImageDrawable(drawable);
return this;
}
public ClassicsHeader setProgressResource(@DrawableRes int resId) {
mProgressDrawable = null;
mProgressView.setImageResource(resId);
return this;
}
public ClassicsHeader setArrowBitmap(Bitmap bitmap) {
mArrowDrawable = null;
mArrowView.setImageBitmap(bitmap);
return this;
}
public ClassicsHeader setArrowDrawable(Drawable drawable) {
mArrowDrawable = null;
mArrowView.setImageDrawable(drawable);
return this;
}
public ClassicsHeader setArrowResource(@DrawableRes int resId) {
mArrowDrawable = null;
mArrowView.setImageResource(resId);
return this;
}
public ClassicsHeader setLastUpdateTime(Date time) {
mLastTime = time;
mLastUpdateText.setText(mFormat.format(time));
if (mShared != null && !isInEditMode()) {
mShared.edit().putLong(KEY_LAST_UPDATE_TIME, time.getTime()).apply();
}
return this;
}
public ClassicsHeader setTimeFormat(DateFormat format) {
mFormat = format;
mLastUpdateText.setText(mFormat.format(mLastTime));
return this;
}
public ClassicsHeader setSpinnerStyle(SpinnerStyle style) {
this.mSpinnerStyle = style;
return this;
}
public ClassicsHeader setPrimaryColor(int primaryColor) {
setBackgroundColor(mBackgroundColor = primaryColor);
if (mRefreshKernel != null) {
mRefreshKernel.requestDrawBackgoundForHeader(mBackgroundColor);
}
return this;
}
public ClassicsHeader setAccentColor(int accentColor) {
if (mArrowDrawable != null) {
mArrowDrawable.parserColors(accentColor);
}
if (mProgressDrawable != null) {
mProgressDrawable.setColor(accentColor);
}
mTitleText.setTextColor(accentColor);
mLastUpdateText.setTextColor(accentColor&0x00ffffff|0xcc000000);
return this;
}
public ClassicsHeader setFinishDuration(int delay) {
mFinishDuration = delay;
return this;
}
public ClassicsHeader setEnableLastTime(boolean enable) {
mEnableLastTime = enable;
mLastUpdateText.setVisibility(enable ? VISIBLE : GONE);
if (mRefreshKernel != null) {
mRefreshKernel.requestRemeasureHeightForHeader();
}
return this;
}
public ClassicsHeader setTextSizeTitle(float size) {
mTitleText.setTextSize(size);
if (mRefreshKernel != null) {
mRefreshKernel.requestRemeasureHeightForHeader();
}
return this;
}
public ClassicsHeader setTextSizeTitle(int unit, float size) {
mTitleText.setTextSize(unit, size);
if (mRefreshKernel != null) {
mRefreshKernel.requestRemeasureHeightForHeader();
}
return this;
}
public ClassicsHeader setTextSizeTime(float size) {
mLastUpdateText.setTextSize(size);
if (mRefreshKernel != null) {
mRefreshKernel.requestRemeasureHeightForHeader();
}
return this;
}
public ClassicsHeader setTextSizeTime(int unit, float size) {
mLastUpdateText.setTextSize(unit, size);
if (mRefreshKernel != null) {
mRefreshKernel.requestRemeasureHeightForHeader();
}
return this;
}
public ClassicsHeader setTextTimeMarginTop(float dp) {
return setTextTimeMarginTopPx(DensityUtil.dp2px(dp));
}
public ClassicsHeader setTextTimeMarginTopPx(int px) {
MarginLayoutParams lp = (MarginLayoutParams)mLastUpdateText.getLayoutParams();
lp.topMargin = px;
mLastUpdateText.setLayoutParams(lp);
return this;
}
public ClassicsHeader setDrawableMarginRight(float dp) {
return setDrawableMarginRightPx(DensityUtil.dp2px(dp));
}
public ClassicsHeader setDrawableMarginRightPx(int px) {
MarginLayoutParams lpArrow = (MarginLayoutParams)mArrowView.getLayoutParams();
MarginLayoutParams lpProgress = (MarginLayoutParams)mProgressView.getLayoutParams();
lpArrow.rightMargin = lpProgress.rightMargin = px;
mArrowView.setLayoutParams(lpArrow);
mProgressView.setLayoutParams(lpProgress);
return this;
}
public ClassicsHeader setDrawableSize(float dp) {
return setDrawableSizePx(DensityUtil.dp2px(dp));
}
public ClassicsHeader setDrawableSizePx(int px) {
ViewGroup.LayoutParams lpArrow = mArrowView.getLayoutParams();
ViewGroup.LayoutParams lpProgress = mProgressView.getLayoutParams();
lpArrow.width = lpProgress.width = px;
lpArrow.height = lpProgress.height = px;
mArrowView.setLayoutParams(lpArrow);
mProgressView.setLayoutParams(lpProgress);
return this;
}
public ClassicsHeader setDrawableArrowSize(float dp) {
return setDrawableArrowSizePx(DensityUtil.dp2px(dp));
}
public ClassicsHeader setDrawableArrowSizePx(int px) {
ViewGroup.LayoutParams lpArrow = mArrowView.getLayoutParams();
lpArrow.width = px;
lpArrow.height = px;
mArrowView.setLayoutParams(lpArrow);
return this;
}
public ClassicsHeader setDrawableProgressSize(float dp) {
return setDrawableProgressSizePx(DensityUtil.dp2px(dp));
}
public ClassicsHeader setDrawableProgressSizePx(int px) {
ViewGroup.LayoutParams lpProgress = mProgressView.getLayoutParams();
lpProgress.width = px;
lpProgress.height = px;
mProgressView.setLayoutParams(lpProgress);
return this;
}
public ImageView getArrowView() {
return mArrowView;
}
public ImageView getProgressView() {
return mProgressView;
}
public TextView getTitleText() {
return mTitleText;
}
public TextView getLastUpdateText() {
return mLastUpdateText;
}
//
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/FalsifyHeader.java
================================================
package com.scwang.smartrefresh.layout.header;
import android.annotation.SuppressLint;
import android.support.annotation.RequiresApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.TextView;
import com.scwang.smartrefresh.layout.api.RefreshHeader;
import com.scwang.smartrefresh.layout.api.RefreshKernel;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.scwang.smartrefresh.layout.constant.SpinnerStyle;
import com.scwang.smartrefresh.layout.util.DensityUtil;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
/**
* 虚假的 Header
* 用于 正真的 Header 在 RefreshLayout 外部时,
* 使用本虚假的 FalsifyHeader 填充在 RefreshLayout 内部
* 具体使用方法 参考 QQ空间风格(QzoneHeader) 和 纸飞机(FlyRefreshHeader)
* Created by SCWANG on 2017/6/14.
*/
public class FalsifyHeader extends View implements RefreshHeader {
protected RefreshKernel mRefreshKernel;
//
public FalsifyHeader(Context context) {
super(context);
}
public FalsifyHeader(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FalsifyHeader(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public FalsifyHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
@Override@SuppressLint("DrawAllocation")
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isInEditMode()) {//这段代码在运行时不会执行,只会在Studio编辑预览时运行,不用在意性能问题
int d = DensityUtil.dp2px(5);
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(0x44ffffff);
paint.setStrokeWidth(DensityUtil.dp2px(1));
paint.setPathEffect(new DashPathEffect(new float[]{d, d, d, d}, 1));
canvas.drawRect(d, d, getWidth() - d, getBottom() - d, paint);
TextView textView = new TextView(getContext());
textView.setText(getClass().getSimpleName()+" 虚假区域\n运行时代表下拉Header的高度【" + DensityUtil.px2dp(getHeight()) + "dp】\n而不会显示任何东西");
textView.setTextColor(0x44ffffff);
textView.setGravity(Gravity.CENTER);
textView.measure(makeMeasureSpec(getWidth(), EXACTLY), makeMeasureSpec(getHeight(), EXACTLY));
textView.layout(0, 0, getWidth(), getHeight());
textView.draw(canvas);
}
}
//
//
@Override
public void onInitialized(RefreshKernel kernel, int height, int extendHeight) {
mRefreshKernel = kernel;
}
@Override
public boolean isSupportHorizontalDrag() {
return false;
}
@Override
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
}
@Override
public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) {
}
@Override
public void onReleasing(float percent, int offset, int headHeight, int extendHeight) {
}
@Override
public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) {
if (mRefreshKernel != null) {
mRefreshKernel.resetStatus();
}
}
@Override
public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) {
}
@Override
public int onFinish(RefreshLayout layout, boolean success) {
return 0;
}
@Override@Deprecated
public void setPrimaryColors(int... colors) {
}
@NonNull
@Override
public View getView() {
return this;
}
@Override
public SpinnerStyle getSpinnerStyle() {
return SpinnerStyle.Scale;
}
//
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/bezierradar/RippleView.java
================================================
package com.scwang.smartrefresh.layout.header.bezierradar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;
/**
* cjj
*/
public class RippleView extends View {
private int mRadius;
private Paint mPaint;
private ValueAnimator mAnimator;
public RippleView(Context context) {
super(context);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(0xffffffff);
mPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public void setFrontColor(int color) {
mPaint.setColor(color);
}
public void startReveal() {
if (mAnimator == null) {
int bigRadius = (int) (Math.sqrt(Math.pow(getHeight(), 2) + Math.pow(getWidth(), 2)));
mAnimator = ValueAnimator.ofInt(0, bigRadius);
mAnimator.setDuration(400);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mRadius = (int) animation.getAnimatedValue();
invalidate();
}
});
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
}
mAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mRadius, mPaint);
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/bezierradar/RoundDotView.java
================================================
package com.scwang.smartrefresh.layout.header.bezierradar;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
import com.scwang.smartrefresh.layout.util.DensityUtil;
/**
*
* Created by cjj on 2015/8/27.
*/
public class RoundDotView extends View {
private int num = 7;
private Paint mPath;
private float mRadius;
private float fraction;
public RoundDotView(Context context) {
super(context);
mPath = new Paint();
mPath.setAntiAlias(true);
mPath.setColor(Color.WHITE);
mRadius = DensityUtil.dp2px(7);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public void setDotColor(int color) {
mPath.setColor(color);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
float wide = (width / num) * fraction-((fraction>1)?((fraction-1)*(width / num)/fraction):0);//y1 = t*(w/n)-(t>1)*((t-1)*(w/n)/t)
float high = height - ((fraction>1)?((fraction-1)*height/2/fraction):0);//y2 = x - (t>1)*((t-1)*x/t);
for (int i = 0 ; i < num; i++) {
float index = 1f + i - (1f + num) / 2;//y3 = (x + 1) - (n + 1)/2; 居中 index 变量:0 1 2 3 4 结果: -2 -1 0 1 2
float alpha = 255 * (1 - (2 * (Math.abs(index) / num)));//y4 = m * ( 1 - 2 * abs(y3) / n); 横向 alpha 差
float x = DensityUtil.px2dp(height);
mPath.setAlpha((int) (alpha * (1d - 1d / Math.pow((x / 800d + 1d), 15))));//y5 = y4 * (1-1/((x/800+1)^15));竖直 alpha 差
float radius = mRadius * (1-1/((x/10+1)));//y6 = mRadius*(1-1/(x/10+1));半径
canvas.drawCircle(width / 2- radius/2 + wide * index , high / 2, radius, mPath);
}
}
public void setFraction(float fraction) {
this.fraction = fraction;
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/bezierradar/RoundProgressView.java
================================================
package com.scwang.smartrefresh.layout.header.bezierradar;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.scwang.smartrefresh.layout.util.DensityUtil;
/**
* 中心圆形加载进度视图
* Created by Administrator on 2015/8/27.
*/
public class RoundProgressView extends View {
private Paint mPath;
private Paint mPantR;
private ValueAnimator mAnimator;
private int endAngle = 0;
private int stratAngle = 270;
private int mRadius = 0;
private int mOutsideCircle = 0;
private RectF mRect = new RectF(0,0,0,0);
public RoundProgressView(Context context) {
super(context);
initView();
}
private void initView() {
mPath = new Paint();
mPantR = new Paint();
mPath.setAntiAlias(true);
mPantR.setAntiAlias(true);
mPath.setColor(Color.WHITE);
mPantR.setColor(0x55000000);
DensityUtil density = new DensityUtil();
mRadius = density.dip2px(20);
mOutsideCircle = density.dip2px(7);
mPath.setStrokeWidth(density.dip2px(3));
mPantR.setStrokeWidth(density.dip2px(3));
mAnimator = ValueAnimator.ofInt(0,360);
mAnimator.setDuration(720);
mAnimator.setRepeatCount(ValueAnimator.INFINITE);
mAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
endAngle = (int) animation.getAnimatedValue();
postInvalidate();
}
});
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mAnimator.removeAllUpdateListeners();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public void setBackColor(int backColor) {
mPantR.setColor(backColor&0x00ffffff|0x55000000);
}
public void setFrontColor(int color) {
mPath.setColor(color);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
if (isInEditMode()) {
stratAngle = 0;
endAngle = 270;
}
mPath.setStyle(Paint.Style.FILL);
canvas.drawCircle(width / 2, height / 2, mRadius, mPath);
mPath.setStyle(Paint.Style.STROKE);//设置为空心
canvas.drawCircle(width / 2, height / 2, mRadius + mOutsideCircle, mPath);
mPantR.setStyle(Paint.Style.FILL);
mRect.set(width/2- mRadius, height/2- mRadius, width/2+ mRadius, height/2+ mRadius);
canvas.drawArc(mRect, stratAngle, endAngle, true, mPantR);
mRadius += mOutsideCircle;
mPantR.setStyle(Paint.Style.STROKE);
mRect.set(width/2- mRadius, height/2- mRadius, width/2+ mRadius, height/2+ mRadius);
canvas.drawArc(mRect, stratAngle, endAngle, false, mPantR);
mRadius -= mOutsideCircle;
}
public void startAnim(){
if (mAnimator !=null) mAnimator.start();
}
public void stopAnim(){
if (mAnimator !=null && mAnimator.isRunning()) mAnimator.cancel();
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/bezierradar/WaveView.java
================================================
package com.scwang.smartrefresh.layout.header.bezierradar;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by cjj on 2015/8/5.
* 绘制贝塞尔来绘制波浪形
*/
public class WaveView extends View {
private int waveHeight;
private int headHeight;
private Path path;
private Paint paint;
private int mOffsetX = -1;
public WaveView(Context context) {
this(context, null, 0);
}
public WaveView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
path = new Path();
paint = new Paint();
paint.setColor(0xff1F2426);
paint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public void setWaveColor(int color) {
paint.setColor(color);
}
public int getHeadHeight() {
return headHeight;
}
public void setHeadHeight(int headHeight) {
this.headHeight = headHeight;
}
public int getWaveHeight() {
return waveHeight;
}
public void setWaveHeight(int waveHeight) {
this.waveHeight = waveHeight;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int width = getWidth();
//重置画笔
path.reset();
//绘制贝塞尔曲线
path.lineTo(0, headHeight);
path.quadTo(mOffsetX >= 0 ? (mOffsetX) : width / 2, headHeight + waveHeight, width, headHeight);
path.lineTo(width, 0);
canvas.drawPath(path, paint);
}
public void setWaveOffsetX(int offset) {
mOffsetX = offset;
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/impl/RefreshContentWrapper.java
================================================
package com.scwang.smartrefresh.layout.impl;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.database.DataSetObserver;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.app.Fragment;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.PagerAdapterWrapper;
import android.support.v4.view.ScrollingView;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.ListViewCompat;
import android.support.v4.widget.NestedScrollView;
import android.support.v4.widget.Space;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.ScrollView;
import com.scwang.smartrefresh.layout.api.RefreshContent;
import com.scwang.smartrefresh.layout.api.RefreshKernel;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.api.ScrollBoundaryDecider;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.scwang.smartrefresh.layout.util.ScrollBoundaryUtil.canScrollDown;
import static com.scwang.smartrefresh.layout.util.ScrollBoundaryUtil.canScrollUp;
/**
* 刷新内容包装
* Created by SCWANG on 2017/5/26.
*/
@SuppressWarnings("WeakerAccess")
public class RefreshContentWrapper implements RefreshContent {
protected static final String TAG_REFRESH_CONTENT_WRAPPER = "TAG_REFRESH_CONTENT_WRAPPER";
protected int mHeaderHeight = Integer.MAX_VALUE;
protected int mFooterHeight = mHeaderHeight - 1;
protected View mContentView;//直接内容视图
protected View mRealContentView;//被包裹的原真实视图
protected View mScrollableView;
protected View mFixedHeader;
protected View mFixedFooter;
protected boolean mEnableRefresh = true;
protected boolean mEnableLoadmore = true;
protected MotionEvent mMotionEvent;
protected ScrollBoundaryDeciderAdapter mBoundaryAdapter = new ScrollBoundaryDeciderAdapter();
public RefreshContentWrapper(View view) {
this.mContentView = mRealContentView = view;
this.mContentView.setTag(TAG_REFRESH_CONTENT_WRAPPER.hashCode(), TAG_REFRESH_CONTENT_WRAPPER);
}
public RefreshContentWrapper(Context context) {
this.mContentView = mRealContentView = new View(context);
this.mContentView.setTag(TAG_REFRESH_CONTENT_WRAPPER.hashCode(), TAG_REFRESH_CONTENT_WRAPPER);
}
public static boolean isTagedContent(View view) {
return TAG_REFRESH_CONTENT_WRAPPER.equals(view.getTag(TAG_REFRESH_CONTENT_WRAPPER.hashCode()));
}
//
protected void findScrollableView(View content, RefreshKernel kernel) {
mScrollableView = findScrollableViewInternal(content, true);
try {//try 不能删除,不然会出现兼容性问题
if (mScrollableView instanceof CoordinatorLayout) {
kernel.getRefreshLayout().setEnableNestedScroll(false);
wrapperCoordinatorLayout(((CoordinatorLayout) mScrollableView), kernel.getRefreshLayout());
}
} catch (Throwable ignored) {
}
try {//try 不能删除,不然会出现兼容性问题
if (mScrollableView instanceof ViewPager) {
wrapperViewPager((ViewPager) this.mScrollableView);
}
} catch (Throwable ignored) {
}
if (mScrollableView instanceof NestedScrollingParent
&& !(mScrollableView instanceof NestedScrollingChild)) {
mScrollableView = findScrollableViewInternal(mScrollableView, false);
}
if (mScrollableView == null) {
mScrollableView = content;
}
}
protected void wrapperCoordinatorLayout(CoordinatorLayout layout, final RefreshLayout refreshLayout) {
for (int i = layout.getChildCount() - 1; i >= 0; i--) {
View view = layout.getChildAt(i);
if (view instanceof AppBarLayout) {
((AppBarLayout) view).addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
mEnableRefresh = verticalOffset >= 0;
mEnableLoadmore = refreshLayout.isEnableLoadmore() && (appBarLayout.getTotalScrollRange() + verticalOffset) <= 0;
}
});
}
}
}
protected void wrapperViewPager(final ViewPager viewPager) {
wrapperViewPager(viewPager, null);
}
protected void wrapperViewPager(final ViewPager viewPager, final PagerPrimaryAdapter primaryAdapter) {
viewPager.post(new Runnable() {
int count = 0;
PagerPrimaryAdapter mAdapter = primaryAdapter;
@Override
public void run() {
count++;
PagerAdapter adapter = viewPager.getAdapter();
if (adapter != null) {
if (adapter instanceof PagerPrimaryAdapter) {
if (adapter == primaryAdapter && count < 10) {
viewPager.postDelayed(this, 500);
}
} else {
if (mAdapter == null) {
mAdapter = new PagerPrimaryAdapter(adapter);
} else {
mAdapter.wrapper(adapter);
}
mAdapter.attachViewPager(viewPager);
}
} else if (count < 10) {
viewPager.postDelayed(this, 500);
}
}
});
}
protected View findScrollableViewInternal(View content, boolean selfable) {
View scrollableView = null;
Queue views = new LinkedBlockingQueue<>(Collections.singletonList(content));
while (!views.isEmpty() && scrollableView == null) {
View view = views.poll();
if (view != null) {
if ((selfable || view != content) && (view instanceof AbsListView
|| view instanceof ScrollView
|| view instanceof ScrollingView
|| view instanceof NestedScrollingChild
|| view instanceof NestedScrollingParent
|| view instanceof WebView
|| view instanceof ViewPager)) {
scrollableView = view;
} else if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
for (int j = 0; j < group.getChildCount(); j++) {
views.add(group.getChildAt(j));
}
}
}
}
return scrollableView;
}
//
//
@NonNull
public View getView() {
return mContentView;
}
@Override
public void moveSpinner(int spinner) {
mRealContentView.setTranslationY(spinner);
if (mFixedHeader != null) {
mFixedHeader.setTranslationY(Math.max(0, spinner));
}
if (mFixedFooter != null) {
mFixedFooter.setTranslationY(Math.min(0, spinner));
}
}
@Override
public boolean canRefresh() {
return mEnableRefresh && mBoundaryAdapter.canRefresh(mContentView);
}
@Override
public boolean canLoadmore() {
return mEnableLoadmore && mBoundaryAdapter.canLoadmore(mContentView);
}
@Override
public void measure(int widthSpec, int heightSpec) {
mContentView.measure(widthSpec, heightSpec);
}
@Override
public ViewGroup.LayoutParams getLayoutParams() {
return mContentView.getLayoutParams();
}
@Override
public int getMeasuredWidth() {
return mContentView.getMeasuredWidth();
}
@Override
public int getMeasuredHeight() {
return mContentView.getMeasuredHeight();
}
@Override
public void layout(int left, int top, int right, int bottom) {
mContentView.layout(left, top, right, bottom);
}
@Override
public View getScrollableView() {
return mScrollableView;
}
@Override
public void onActionDown(MotionEvent e) {
mMotionEvent = MotionEvent.obtain(e);
mMotionEvent.offsetLocation(-mContentView.getLeft(), -mContentView.getTop());
mBoundaryAdapter.setActionEvent(mMotionEvent);
}
@Override
public void onActionUpOrCancel() {
mMotionEvent = null;
mBoundaryAdapter.setActionEvent(null);
}
@Override
public void setupComponent(RefreshKernel kernel, View fixedHeader, View fixedFooter) {
this.findScrollableView(mContentView, kernel);
try {//try 不能删除,不然会出现兼容性问题
if (mScrollableView instanceof RecyclerView) {
RecyclerViewScrollComponent component = new RecyclerViewScrollComponent(kernel);
component.attach((RecyclerView) mScrollableView);
}
} catch (Throwable ignored) {
}
try {//try 不能删除,不然会出现兼容性问题
if (mScrollableView instanceof NestedScrollView) {
NestedScrollViewScrollComponent component = new NestedScrollViewScrollComponent(kernel);
component.attach((NestedScrollView) mScrollableView);
}
} catch (Throwable ignored) {
}
if (mScrollableView instanceof AbsListView) {
AbsListViewScrollComponent component = new AbsListViewScrollComponent(kernel);
component.attach(((AbsListView) mScrollableView));
} else if (Build.VERSION.SDK_INT >= 23 && mScrollableView != null) {
Api23ViewScrollComponent component = new Api23ViewScrollComponent(kernel);
component.attach(mScrollableView);
}
if (fixedHeader != null || fixedFooter != null) {
mFixedHeader = fixedHeader;
mFixedFooter = fixedFooter;
FrameLayout frameLayout = new FrameLayout(mContentView.getContext());
kernel.getRefreshLayout().getLayout().removeView(mContentView);
ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
frameLayout.addView(mContentView, MATCH_PARENT, MATCH_PARENT);
kernel.getRefreshLayout().getLayout().addView(frameLayout, layoutParams);
mContentView = frameLayout;
if (fixedHeader != null) {
fixedHeader.setClickable(true);
ViewGroup.LayoutParams lp = fixedHeader.getLayoutParams();
ViewGroup parent = (ViewGroup) fixedHeader.getParent();
int index = parent.indexOfChild(fixedHeader);
parent.removeView(fixedHeader);
lp.height = measureViewHeight(fixedHeader);
parent.addView(new Space(mContentView.getContext()), index, lp);
frameLayout.addView(fixedHeader);
}
if (fixedFooter != null) {
fixedFooter.setClickable(true);
ViewGroup.LayoutParams lp = fixedFooter.getLayoutParams();
ViewGroup parent = (ViewGroup) fixedFooter.getParent();
int index = parent.indexOfChild(fixedFooter);
parent.removeView(fixedFooter);
FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(lp);
lp.height = measureViewHeight(fixedFooter);
parent.addView(new Space(mContentView.getContext()), index, lp);
flp.gravity = Gravity.BOTTOM;
frameLayout.addView(fixedFooter, flp);
}
}
}
@Override
public void onInitialHeaderAndFooter(int headerHeight, int footerHeight) {
mHeaderHeight = headerHeight;
mFooterHeight = footerHeight;
}
@Override
public void setScrollBoundaryDecider(ScrollBoundaryDecider boundary) {
if (boundary instanceof ScrollBoundaryDeciderAdapter) {
mBoundaryAdapter = ((ScrollBoundaryDeciderAdapter) boundary);
} else {
mBoundaryAdapter.setScrollBoundaryDecider(boundary);
}
}
@Override
public void setEnableLoadmoreWhenContentNotFull(boolean enable) {
mBoundaryAdapter.setEnableLoadmoreWhenContentNotFull(enable);
}
@Override
public AnimatorUpdateListener onLoadingFinish(final RefreshKernel kernel, final int footerHeight, int startDelay, final int duration) {
if (mScrollableView != null && kernel.getRefreshLayout().isEnableScrollContentWhenLoaded()) {
if (!canScrollDown(mScrollableView)) {
return null;
}
if (mScrollableView instanceof AbsListView && !(mScrollableView instanceof ListView) && Build.VERSION.SDK_INT < 19) {
if (startDelay > 0) {
kernel.getRefreshLayout().getLayout().postDelayed(new Runnable() {
@Override
public void run() {
((AbsListView) mScrollableView).smoothScrollBy(footerHeight, duration);
}
}, startDelay);
} else {
((AbsListView) mScrollableView).smoothScrollBy(footerHeight, duration);
}
return null;
}
return new AnimatorUpdateListener() {
int lastValue = kernel.getSpinner();
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
if (mScrollableView instanceof ListView) {
ListViewCompat.scrollListBy((ListView) mScrollableView, value - lastValue);
} else {
mScrollableView.scrollBy(0, value - lastValue);
}
lastValue = value;
}
};
}
return null;
}
//
//
@RequiresApi(api = Build.VERSION_CODES.M)
protected class Api23ViewScrollComponent implements View.OnScrollChangeListener {
long lastTime = 0;
long lastTimeOld = 0;
int lastScrollY = 0;
int lastOldScrollY = 0;
RefreshKernel kernel;
View.OnScrollChangeListener mScrollListener;
Api23ViewScrollComponent(RefreshKernel kernel) {
this.kernel = kernel;
}
@Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (mScrollListener != null) {
mScrollListener.onScrollChange(v, scrollX, scrollY, oldScrollX, oldScrollY);
}
if (lastScrollY == scrollY && lastOldScrollY == oldScrollY) {
return;
}
RefreshLayout layout = kernel.getRefreshLayout();
boolean overScroll = layout.isEnableOverScrollBounce() || layout.isRefreshing() || layout.isLoading();
if (scrollY <= 0 && oldScrollY > 0 && mMotionEvent == null && lastTime - lastTimeOld > 1000 && overScroll && layout.isEnableRefresh()) {
//time:16000000 value:160
final int velocity = (lastOldScrollY - oldScrollY) * 16000 / (int)((lastTime - lastTimeOld)/1000f);
kernel.animSpinnerBounce(Math.min(velocity, mHeaderHeight));
} else if (oldScrollY < scrollY && mMotionEvent == null && layout.isEnableLoadmore()) {
if (!layout.isLoadmoreFinished() && layout.isEnableAutoLoadmore()
&& !layout.isEnablePureScrollMode()
&& layout.getState() == RefreshState.None
&& !canScrollDown(v)) {
kernel.getRefreshLayout().autoLoadmore(0, 1);
} else
if (overScroll && lastTime - lastTimeOld > 1000 && !canScrollDown(v)) {
final int velocity = (lastOldScrollY - oldScrollY) * 16000 / (int)((lastTime - lastTimeOld)/1000f);
kernel.animSpinnerBounce(Math.max(velocity, -mFooterHeight));
}
}
lastScrollY = scrollY;
lastOldScrollY = oldScrollY;
lastTimeOld = lastTime;
lastTime = System.nanoTime();
}
void attach(View view) {
Field[] declaredFields = View.class.getDeclaredFields();
if (declaredFields != null) {
for (Field field : declaredFields) {
if (View.OnScrollChangeListener.class.equals(field.getType())) {
try {
field.setAccessible(true);
Object listener = field.get(view);
if (listener != null && !view.equals(listener)) {
mScrollListener = (View.OnScrollChangeListener) listener;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
view.setOnScrollChangeListener(new Api23ViewScrollComponent(kernel));
}
}
protected class NestedScrollViewScrollComponent implements NestedScrollView.OnScrollChangeListener {
long lastTime = 0;
long lastTimeOld = 0;
int lastScrollY = 0;
int lastOldScrollY = 0;
RefreshKernel kernel;
NestedScrollView.OnScrollChangeListener mScrollChangeListener;
NestedScrollViewScrollComponent(RefreshKernel kernel) {
this.kernel = kernel;
}
@Override
public void onScrollChange(NestedScrollView scrollView, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (mScrollChangeListener != null) {
mScrollChangeListener.onScrollChange(scrollView, scrollX, scrollY, oldScrollX, oldScrollY);
}
if (lastScrollY == scrollY && lastOldScrollY == oldScrollY) {
return;
}
RefreshLayout layout = kernel.getRefreshLayout();
boolean overScroll = layout.isEnableOverScrollBounce() || layout.isRefreshing() || layout.isLoading();
if (scrollY <= 0 && oldScrollY > 0 && mMotionEvent == null && lastTime - lastTimeOld > 1000 && overScroll && layout.isEnableRefresh()) {
final int velocity = (lastOldScrollY - oldScrollY) * 16000 / (int)((lastTime - lastTimeOld)/1000f);
kernel.animSpinnerBounce(Math.min(velocity, mHeaderHeight));
} else if (oldScrollY < scrollY && mMotionEvent == null && layout.isEnableLoadmore()) {
if (!layout.isLoadmoreFinished() && layout.isEnableAutoLoadmore()
&& !layout.isEnablePureScrollMode()
&& layout.getState() == RefreshState.None
&& !canScrollDown(scrollView)) {
kernel.getRefreshLayout().autoLoadmore(0, 1);
} else if (overScroll && lastTime - lastTimeOld > 1000 && !canScrollDown(mScrollableView)) {
final int velocity = (lastOldScrollY - oldScrollY) * 16000 / (int)((lastTime - lastTimeOld)/1000f);
kernel.animSpinnerBounce(Math.max(velocity, -mFooterHeight));
}
}
lastScrollY = scrollY;
lastOldScrollY = oldScrollY;
lastTimeOld = lastTime;
lastTime = System.nanoTime();
}
void attach(NestedScrollView scrollView) {
//获得原始监听器,用作转发
Field[] declaredFields = NestedScrollView.class.getDeclaredFields();
if (declaredFields != null) {
for (Field field : declaredFields) {
if (NestedScrollView.OnScrollChangeListener.class.equals(field.getType())) {
try {
field.setAccessible(true);
Object listener = field.get(scrollView);
if (listener != null && !scrollView.equals(listener)) {
mScrollChangeListener = (NestedScrollView.OnScrollChangeListener) listener;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
scrollView.setOnScrollChangeListener(this);
}
}
protected class AbsListViewScrollComponent implements AbsListView.OnScrollListener {
int scrollY;
int scrollDy;
int lastScrolly;
int lastScrollDy;
RefreshKernel kernel;
SparseArray recordSp = new SparseArray<>(0);
AbsListView.OnScrollListener mScrollListener;
AbsListViewScrollComponent(RefreshKernel kernel) {
this.kernel = kernel;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mScrollListener != null) {
mScrollListener.onScrollStateChanged(view, scrollState);
}
}
@Override
public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mScrollListener != null) {
mScrollListener.onScroll(absListView, firstVisibleItem, visibleItemCount, totalItemCount);
}
lastScrolly = scrollY;
lastScrollDy = scrollDy;
scrollY = getScrollY(absListView, firstVisibleItem);
scrollDy = lastScrolly - scrollY;
final int dy = lastScrollDy + scrollDy;
if (totalItemCount > 0 && mMotionEvent == null) {
RefreshLayout layout = kernel.getRefreshLayout();
if (dy > 0) {
if (firstVisibleItem == 0
&& layout.isEnableRefresh()
&& (layout.isEnableOverScrollBounce() || layout.isRefreshing())
&& !canScrollUp(absListView)) {
kernel.animSpinnerBounce(Math.min(dy, mHeaderHeight));
}
} else if (dy < 0) {
int lastVisiblePosition = absListView.getLastVisiblePosition();
if (lastVisiblePosition == totalItemCount - 1 && lastVisiblePosition > 0
&& layout.isEnableLoadmore()
&& !canScrollDown(absListView)) {
if (layout.getState() == RefreshState.None
&& layout.isEnableAutoLoadmore()
&& !layout.isLoadmoreFinished()
&& !layout.isEnablePureScrollMode()) {
layout.autoLoadmore(0, 1);
} else if (layout.isEnableOverScrollBounce() || layout.isLoading()) {
kernel.animSpinnerBounce(Math.max(dy, -mFooterHeight));
}
}
}
}
}
void attach(AbsListView listView) {
//获得原始监听器,用作转发
Field[] declaredFields = AbsListView.class.getDeclaredFields();
if (declaredFields != null) {
for (Field field : declaredFields) {
if (AbsListView.OnScrollListener.class.equals(field.getType())) {
try {
field.setAccessible(true);
Object listener = field.get(listView);
if (listener != null && !listView.equals(listener)) {
mScrollListener = (AbsListView.OnScrollListener) listener;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
listView.setOnScrollListener(this);
}
protected int getScrollY(AbsListView view, int firstVisibleItem) {
View firstView = view.getChildAt(0);
if (null != firstView) {
ItemRecod itemRecord = recordSp.get(firstVisibleItem);
if (null == itemRecord) {
itemRecord = new ItemRecod();
}
itemRecord.height = firstView.getHeight();
itemRecord.top = firstView.getTop();
recordSp.append(firstVisibleItem, itemRecord);
int height = 0,lastheight = 0;
for (int i = 0; i < firstVisibleItem; i++) {
ItemRecod itemRecod = recordSp.get(i);
if (itemRecod != null) {
height += itemRecod.height;
lastheight = itemRecod.height;
} else {
height += lastheight;
}
}
ItemRecod itemRecod = recordSp.get(firstVisibleItem);
if (null == itemRecod) {
itemRecod = new ItemRecod();
}
return height - itemRecod.top;
}
return 0;
}
class ItemRecod {
int height = 0;
int top = 0;
}
}
protected class RecyclerViewScrollComponent extends RecyclerView.OnScrollListener {
RefreshKernel kernel;
RecyclerViewScrollComponent(RefreshKernel kernel) {
this.kernel = kernel;
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (mMotionEvent == null) {
final RefreshLayout layout = kernel.getRefreshLayout();
if (dy < 0 && layout.isEnableRefresh()
&& (layout.isEnableOverScrollBounce() || layout.isRefreshing())
&& !canScrollUp(recyclerView)) {
kernel.animSpinnerBounce(Math.min(-dy * 2, mHeaderHeight));
} else if (dy > 0 && layout.isEnableLoadmore() && !canScrollDown(recyclerView)) {
if (layout.getState() == RefreshState.None
&& layout.isEnableAutoLoadmore()
&& !layout.isLoadmoreFinished()
&& !layout.isEnablePureScrollMode()) {
layout.autoLoadmore(0,1);
} else if (layout.isEnableOverScrollBounce() || layout.isLoading()) {
kernel.animSpinnerBounce(Math.max(-dy * 2, -mFooterHeight));
}
}
}
}
void attach(RecyclerView recyclerView) {
recyclerView.addOnScrollListener(this);
}
}
//
//
protected static int measureViewHeight(View view) {
ViewGroup.LayoutParams p = view.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(MATCH_PARENT,WRAP_CONTENT);
}
int childHeightSpec;
int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, p.width);
if (p.height > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(p.height, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
view.measure(childWidthSpec, childHeightSpec);
return view.getMeasuredHeight();
}
//
protected class PagerPrimaryAdapter extends PagerAdapterWrapper {
protected ViewPager mViewPager;
PagerPrimaryAdapter(PagerAdapter wrapped) {
super(wrapped);
}
void wrapper(PagerAdapter adapter) {
wrapped = adapter;
}
@Override
public void attachViewPager(ViewPager viewPager) {
mViewPager = viewPager;
super.attachViewPager(viewPager);
}
@Override
public void setViewPagerObserver(DataSetObserver observer) {
super.setViewPagerObserver(observer);
if (observer == null) {
wrapperViewPager(mViewPager, this);
}
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (object instanceof View) {
mScrollableView = ((View) object);
} else if (object instanceof Fragment) {
mScrollableView = ((Fragment) object).getView();
}
if (mScrollableView != null) {
mScrollableView = findScrollableViewInternal(mScrollableView, true);
if (mScrollableView instanceof NestedScrollingParent
&& !(mScrollableView instanceof NestedScrollingChild)) {
mScrollableView = findScrollableViewInternal(mScrollableView, false);
}
}
}
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/impl/RefreshFooterWrapper.java
================================================
package com.scwang.smartrefresh.layout.impl;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.scwang.smartrefresh.layout.api.RefreshFooter;
import com.scwang.smartrefresh.layout.api.RefreshKernel;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.scwang.smartrefresh.layout.constant.SpinnerStyle;
/**
* 刷新底部包装
* Created by SCWANG on 2017/5/26.
*/
public class RefreshFooterWrapper implements RefreshFooter {
private static final String TAG_REFRESH_FOOTER_WRAPPER = "TAG_REFRESH_FOOTER_WRAPPER";
private View mWrapperView;
private SpinnerStyle mSpinnerStyle;
public RefreshFooterWrapper(View wrapper) {
this.mWrapperView = wrapper;
this.mWrapperView.setTag(TAG_REFRESH_FOOTER_WRAPPER.hashCode(), TAG_REFRESH_FOOTER_WRAPPER);
}
public static boolean isTagedFooter(View view) {
return TAG_REFRESH_FOOTER_WRAPPER.equals(view.getTag(TAG_REFRESH_FOOTER_WRAPPER.hashCode()));
}
@NonNull
public View getView() {
return mWrapperView;
}
@Override
public int onFinish(RefreshLayout layout, boolean success) {
return 0;
}
@Override@Deprecated
public void setPrimaryColors(int... colors) {
}
@Override
public SpinnerStyle getSpinnerStyle() {
if (mSpinnerStyle != null) {
return mSpinnerStyle;
}
ViewGroup.LayoutParams params = mWrapperView.getLayoutParams();
if (params instanceof SmartRefreshLayout.LayoutParams) {
mSpinnerStyle = ((SmartRefreshLayout.LayoutParams) params).spinnerStyle;
if (mSpinnerStyle != null) {
return mSpinnerStyle;
}
}
if (params != null) {
if (params.height == 0) {
return mSpinnerStyle = SpinnerStyle.Scale;
}
}
return mSpinnerStyle = SpinnerStyle.Translate;
}
@Override
public void onInitialized(RefreshKernel kernel, int height, int extendHeight) {
ViewGroup.LayoutParams params = mWrapperView.getLayoutParams();
if (params instanceof SmartRefreshLayout.LayoutParams) {
kernel.requestDrawBackgoundForFooter(((SmartRefreshLayout.LayoutParams) params).backgroundColor);
}
}
@Override
public boolean isSupportHorizontalDrag() {
return false;
}
@Override
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
}
@Override
public void onPullingUp(float percent, int offset, int footerHeight, int extendHeight) {
}
@Override
public void onPullReleasing(float percent, int offset, int footerHeight, int extendHeight) {
}
@Override
public void onStartAnimator(RefreshLayout layout, int footerHeight, int extendHeight) {
}
@Override
public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) {
}
@Override
public boolean setLoadmoreFinished(boolean finished) {
return false;
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/impl/RefreshHeaderWrapper.java
================================================
package com.scwang.smartrefresh.layout.impl;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.scwang.smartrefresh.layout.api.RefreshHeader;
import com.scwang.smartrefresh.layout.api.RefreshKernel;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.scwang.smartrefresh.layout.constant.SpinnerStyle;
/**
* 刷新头部包装
* Created by SCWANG on 2017/5/26.
*/
public class RefreshHeaderWrapper implements RefreshHeader {
private static final String TAG_REFRESH_HEADER_WRAPPER = "TAG_REFRESH_HEADER_WRAPPER";
private View mWrapperView;
private SpinnerStyle mSpinnerStyle;
public RefreshHeaderWrapper(View wrapper) {
this.mWrapperView = wrapper;
this.mWrapperView.setTag(TAG_REFRESH_HEADER_WRAPPER.hashCode(), TAG_REFRESH_HEADER_WRAPPER);
}
public static boolean isTagedHeader(View view) {
return TAG_REFRESH_HEADER_WRAPPER.equals(view.getTag(TAG_REFRESH_HEADER_WRAPPER.hashCode()));
}
@NonNull
public View getView() {
return mWrapperView;
}
@Override
public int onFinish(RefreshLayout layout, boolean success) {
return 0;
}
@Override@Deprecated
public void setPrimaryColors(int... colors) {
}
@NonNull
@Override
public SpinnerStyle getSpinnerStyle() {
if (mSpinnerStyle != null) {
return mSpinnerStyle;
}
ViewGroup.LayoutParams params = mWrapperView.getLayoutParams();
if (params instanceof SmartRefreshLayout.LayoutParams) {
mSpinnerStyle = ((SmartRefreshLayout.LayoutParams) params).spinnerStyle;
if (mSpinnerStyle != null) {
return mSpinnerStyle;
}
}
if (params != null) {
if (params.height == ViewGroup.LayoutParams.MATCH_PARENT) {
return mSpinnerStyle = SpinnerStyle.Scale;
}
}
return mSpinnerStyle = SpinnerStyle.Translate;
}
@Override
public void onInitialized(RefreshKernel kernel, int height, int extendHeight) {
ViewGroup.LayoutParams params = mWrapperView.getLayoutParams();
if (params instanceof SmartRefreshLayout.LayoutParams) {
kernel.requestDrawBackgoundForHeader(((SmartRefreshLayout.LayoutParams) params).backgroundColor);
}
}
@Override
public boolean isSupportHorizontalDrag() {
return false;
}
@Override
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
}
@Override
public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) {
}
@Override
public void onReleasing(float percent, int offset, int headHeight, int extendHeight) {
}
@Override
public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) {
}
@Override
public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) {
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/impl/ScrollBoundaryDeciderAdapter.java
================================================
package com.scwang.smartrefresh.layout.impl;
import android.view.MotionEvent;
import android.view.View;
import com.scwang.smartrefresh.layout.api.ScrollBoundaryDecider;
import com.scwang.smartrefresh.layout.util.ScrollBoundaryUtil;
/**
* 滚动边界
* Created by SCWANG on 2017/7/8.
*/
@SuppressWarnings("WeakerAccess")
public class ScrollBoundaryDeciderAdapter implements ScrollBoundaryDecider {
//
protected MotionEvent mActionEvent;
protected ScrollBoundaryDecider boundary;
protected boolean mEnableLoadmoreWhenContentNotFull;
void setScrollBoundaryDecider(ScrollBoundaryDecider boundary){
this.boundary = boundary;
}
void setActionEvent(MotionEvent event) {
mActionEvent = event;
}
//
//
@Override
public boolean canRefresh(View content) {
if (boundary != null) {
return boundary.canRefresh(content);
}
return ScrollBoundaryUtil.canRefresh(content, mActionEvent);
}
@Override
public boolean canLoadmore(View content) {
if (boundary != null) {
return boundary.canLoadmore(content);
}
if (mEnableLoadmoreWhenContentNotFull) {
return !ScrollBoundaryUtil.canScrollDown(content, mActionEvent);
}
return ScrollBoundaryUtil.canLoadmore(content, mActionEvent);
}
public void setEnableLoadmoreWhenContentNotFull(boolean enable) {
mEnableLoadmoreWhenContentNotFull = enable;
}
//
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/internal/ProgressDrawable.java
================================================
package com.scwang.smartrefresh.layout.internal;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.view.animation.LinearInterpolator;
/**
* 旋转动画
* Created by SCWANG on 2017/6/16.
*/
public class ProgressDrawable extends Drawable implements Animatable {
private int mProgressDegree = 0;
private ValueAnimator mValueAnimator;
private Path mPath = new Path();
private Paint mPaint = new Paint();
public ProgressDrawable() {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
mPaint.setColor(0xffaaaaaa);
setupAnimators();
}
public void setColor(int color) {
mPaint.setColor(color);
}
//
@Override
public void draw(@NonNull Canvas canvas) {
Rect bounds = getBounds();
int width = bounds.width();
int height = bounds.height();
canvas.save();
canvas.rotate(mProgressDegree, (width) / 2, (height) / 2);
final int r = Math.max(1, width / 20);
for (int i = 0; i < 12; i++) {
mPath.reset();
mPath.addCircle(width - r, height / 2, r, Path.Direction.CW);
mPath.addRect(width - 5 * r, height / 2 - r, width - r, height / 2 + r, Path.Direction.CW);
mPath.addCircle(width - 5 * r, height / 2, r, Path.Direction.CW);
mPaint.setAlpha((i+5) * 0x11);
canvas.rotate(30, (width) / 2, (height) / 2);
canvas.drawPath(mPath, mPaint);
}
canvas.restore();
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
//
private void setupAnimators() {
mValueAnimator = ValueAnimator.ofInt(30, 3600);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
mProgressDegree = 30 * (value / 30);
invalidateSelf();
}
});
mValueAnimator.setDuration(10000);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.setRepeatMode(ValueAnimator.RESTART);
}
@Override
public void start() {
if (!mValueAnimator.isRunning()) {
mValueAnimator.start();
}
}
@Override
public void stop() {
if (mValueAnimator.isRunning()) {
mValueAnimator.cancel();
}
}
@Override
public boolean isRunning() {
return mValueAnimator.isRunning();
}
public int width() {
return getBounds().width();
}
public int height() {
return getBounds().height();
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/internal/pathview/PathParser.java
================================================
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.scwang.smartrefresh.layout.internal.pathview;
import android.graphics.Matrix;
import android.graphics.Path;
import android.os.Build;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
// This class is a duplicate from the PathParser.java of frameworks/base, with slight
// update on incompatible API like copyOfRange().
class PathParser {
private static final String LOGTAG = "PathParser";
// Copy from Arrays.copyOfRange() which is only available from API level 9.
/**
* Copies elements from {@code original} into a new array, from indexes start (inclusive) to
* end (exclusive). The original order of elements is preserved.
* If {@code end} is greater than {@code original.length}, the result is padded
* with the value {@code 0.0f}.
*
* @param original the original array
* @param start the start index, inclusive
* @param end the end index, exclusive
* @return the new array
* @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length}
* @throws IllegalArgumentException if {@code start > end}
* @throws NullPointerException if {@code original == null}
*/
static float[] copyOfRange(float[] original, int start, int end) {
if (start > end) {
throw new IllegalArgumentException();
}
int originalLength = original.length;
if (start < 0 || start > originalLength) {
throw new ArrayIndexOutOfBoundsException();
}
int resultLength = end - start;
int copyLength = Math.min(resultLength, originalLength - start);
float[] result = new float[resultLength];
System.arraycopy(original, start, result, 0, copyLength);
return result;
}
public static List transformScale(float ratioWidth, float ratioHeight, List orginPaths, List orginSvgs) {
Matrix matrix = new Matrix();
matrix.setScale(ratioWidth, ratioHeight);
List paths = new ArrayList<>();
if (Build.VERSION.SDK_INT > 16) {
for (Path path : orginPaths) {
Path npath = new Path();
path.transform(matrix, npath);
paths.add(npath);
}
} else {
for (String svgPath : orginSvgs) {
Path path = new Path();
PathDataNode[] nodes = createNodesFromPathData(svgPath);
transformScaleNodes(ratioWidth, ratioHeight, nodes);
PathDataNode.nodesToPath(nodes, path);
paths.add(path);
}
}
return paths;
}
private static void transformScaleNodes(float ratioWidth, float ratioHeight, PathDataNode[] node) {
for (int i = 0; i < node.length; i++) {
transformScaleCommand(ratioWidth, ratioHeight, node[i].type, node[i].params);
}
}
private static void transformScaleCommand(float ratioWidth, float ratioHeight, char cmd, float[] val) {
int incr = 2;
switch (cmd) {
case 'z':
case 'Z':
break;
case 'm':
case 'M':
case 'l':
case 'L':
case 't':
case 'T':
incr = 2;
break;
case 'h':
case 'H':
case 'v':
case 'V':
incr = 1;
break;
case 'c':
case 'C':
incr = 6;
break;
case 's':
case 'S':
case 'q':
case 'Q':
incr = 4;
break;
case 'a':
case 'A':
incr = 7;
break;
}
for (int k = 0; k < val.length; k += incr) {
switch (cmd) {
case 'm': // moveto - Start a new sub-path (relative)
case 'M': // moveto - Start a new sub-path
case 'l': // lineto - Draw a line from the current point (relative)
case 'L': // lineto - Draw a line from the current point
case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
case 'T': // Draws a quadratic Bézier curve (reflective control point)
val[k] *= ratioWidth;
val[k + 1] *= ratioHeight;
break;
case 'h': // horizontal lineto - Draws a horizontal line (relative)
case 'H': // horizontal lineto - Draws a horizontal line
val[k] *= ratioWidth;
break;
case 'v': // vertical lineto - Draws a vertical line from the current point (r)
case 'V': // vertical lineto - Draws a vertical line from the current point
val[k] *= ratioHeight;
break;
case 'c': // curveto - Draws a cubic Bézier curve (relative)
case 'C': // curveto - Draws a cubic Bézier curve
val[k] *= ratioWidth;
val[k + 1] *= ratioHeight;
val[k + 2] *= ratioWidth;
val[k + 3] *= ratioHeight;
val[k + 4] *= ratioWidth;
val[k + 5] *= ratioHeight;
break;
case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
case 'q': // Draws a quadratic Bézier (relative)
case 'Q': // Draws a quadratic Bézier
val[k] *= ratioWidth;
val[k + 1] *= ratioHeight;
val[k + 2] *= ratioWidth;
val[k + 3] *= ratioHeight;
case 'a': // Draws an elliptical arc
case 'A': // Draws an elliptical arc
val[k] *= ratioWidth;
val[k + 1] *= ratioHeight;
val[k + 5] *= ratioWidth;
val[k + 6] *= ratioHeight;
break;
}
}
}
/**
* @param pathData The string representing a path, the same as "d" string in svg file.
* @return the generated Path object.
*/
public static Path createPathFromPathData(String pathData) {
Path path = new Path();
PathDataNode[] nodes = createNodesFromPathData(pathData);
if (nodes != null) {
try {
PathDataNode.nodesToPath(nodes, path);
} catch (RuntimeException e) {
throw new RuntimeException("Error in parsing " + pathData, e);
}
return path;
}
return null;
}
/**
* @param pathData The string representing a path, the same as "d" string in svg file.
* @return an array of the PathDataNode.
*/
public static PathDataNode[] createNodesFromPathData(String pathData) {
if (pathData == null) {
return null;
}
int start = 0;
int end = 1;
ArrayList list = new ArrayList();
while (end < pathData.length()) {
end = nextStart(pathData, end);
String s = pathData.substring(start, end).trim();
if (s.length() > 0) {
float[] val = getFloats(s);
addNode(list, s.charAt(0), val);
}
start = end;
end++;
}
if ((end - start) == 1 && start < pathData.length()) {
addNode(list, pathData.charAt(start), new float[0]);
}
return list.toArray(new PathDataNode[list.size()]);
}
/**
* @param source The array of PathDataNode to be duplicated.
* @return a deep copy of the source.
*/
public static PathDataNode[] deepCopyNodes(PathDataNode[] source) {
if (source == null) {
return null;
}
PathDataNode[] copy = new PathParser.PathDataNode[source.length];
for (int i = 0; i < source.length; i++) {
copy[i] = new PathDataNode(source[i]);
}
return copy;
}
/**
* @param nodesFrom The source path represented in an array of PathDataNode
* @param nodesTo The target path represented in an array of PathDataNode
* @return whether the nodesFrom can morph into nodesTo
*/
public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {
if (nodesFrom == null || nodesTo == null) {
return false;
}
if (nodesFrom.length != nodesTo.length) {
return false;
}
for (int i = 0; i < nodesFrom.length; i++) {
if (nodesFrom[i].type != nodesTo[i].type
|| nodesFrom[i].params.length != nodesTo[i].params.length) {
return false;
}
}
return true;
}
/**
* Update the target's data to match the source.
* Before calling this, make sure canMorph(target, source) is true.
*
* @param target The target path represented in an array of PathDataNode
* @param source The source path represented in an array of PathDataNode
*/
public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {
for (int i = 0; i < source.length; i++) {
target[i].type = source[i].type;
for (int j = 0; j < source[i].params.length; j++) {
target[i].params[j] = source[i].params[j];
}
}
}
private static int nextStart(String s, int end) {
char c;
while (end < s.length()) {
c = s.charAt(end);
// Note that 'e' or 'E' are not valid path commands, but could be
// used for floating point numbers' scientific notation.
// Therefore, when searching for next command, we should ignore 'e'
// and 'E'.
if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
&& c != 'e' && c != 'E') {
return end;
}
end++;
}
return end;
}
private static void addNode(ArrayList list, char cmd, float[] val) {
list.add(new PathDataNode(cmd, val));
}
private static class ExtractFloatResult {
// We need to return the position of the next separator and whether the
// next float starts with a '-' or a '.'.
int mEndPosition;
boolean mEndWithNegOrDot;
ExtractFloatResult() {
}
}
/**
* Parse the floats in the string.
* This is an optimized version of parseFloat(s.split(",|\\s"));
*
* @param s the string containing a command and list of floats
* @return array of floats
*/
private static float[] getFloats(String s) {
if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {
return new float[0];
}
try {
float[] results = new float[s.length()];
int count = 0;
int startPosition = 1;
int endPosition = 0;
ExtractFloatResult result = new ExtractFloatResult();
int totalLength = s.length();
// The startPosition should always be the first character of the
// current number, and endPosition is the character after the current
// number.
while (startPosition < totalLength) {
extract(s, startPosition, result);
endPosition = result.mEndPosition;
if (startPosition < endPosition) {
results[count++] = Float.parseFloat(
s.substring(startPosition, endPosition));
}
if (result.mEndWithNegOrDot) {
// Keep the '-' or '.' sign with next number.
startPosition = endPosition;
} else {
startPosition = endPosition + 1;
}
}
return copyOfRange(results, 0, count);
} catch (NumberFormatException e) {
throw new RuntimeException("error in parsing \"" + s + "\"", e);
}
}
/**
* Calculate the position of the next comma or space or negative sign
*
* @param s the string to search
* @param start the position to start searching
* @param result the result of the extraction, including the position of the
* the starting position of next number, whether it is ending with a '-'.
*/
private static void extract(String s, int start, ExtractFloatResult result) {
// Now looking for ' ', ',', '.' or '-' from the start.
int currentIndex = start;
boolean foundSeparator = false;
result.mEndWithNegOrDot = false;
boolean secondDot = false;
boolean isExponential = false;
for (; currentIndex < s.length(); currentIndex++) {
boolean isPrevExponential = isExponential;
isExponential = false;
char currentChar = s.charAt(currentIndex);
switch (currentChar) {
case ' ':
case ',':
foundSeparator = true;
break;
case '-':
// The negative sign following a 'e' or 'E' is not a separator.
if (currentIndex != start && !isPrevExponential) {
foundSeparator = true;
result.mEndWithNegOrDot = true;
}
break;
case '.':
if (!secondDot) {
secondDot = true;
} else {
// This is the second dot, and it is considered as a separator.
foundSeparator = true;
result.mEndWithNegOrDot = true;
}
break;
case 'e':
case 'E':
isExponential = true;
break;
}
if (foundSeparator) {
break;
}
}
// When there is nothing found, then we put the end position to the end
// of the string.
result.mEndPosition = currentIndex;
}
/**
* Each PathDataNode represents one command in the "d" attribute of the svg
* file.
* An array of PathDataNode can represent the whole "d" attribute.
*/
public static class PathDataNode {
/*package*/
char type;
float[] params;
PathDataNode(char type, float[] params) {
this.type = type;
this.params = params;
}
PathDataNode(PathDataNode n) {
type = n.type;
params = copyOfRange(n.params, 0, n.params.length);
}
/**
* Convert an array of PathDataNode to Path.
*
* @param node The source array of PathDataNode.
* @param path The target Path object.
*/
public static void nodesToPath(PathDataNode[] node, Path path) {
float[] current = new float[6];
char previousCommand = 'm';
for (int i = 0; i < node.length; i++) {
addCommand(path, current, previousCommand, node[i].type, node[i].params);
previousCommand = node[i].type;
}
}
/**
* The current PathDataNode will be interpolated between the
* nodeFrom and nodeTo according to the
* fraction.
*
* @param nodeFrom The start value as a PathDataNode.
* @param nodeTo The end value as a PathDataNode
* @param fraction The fraction to interpolate.
*/
public void interpolatePathDataNode(PathDataNode nodeFrom,
PathDataNode nodeTo, float fraction) {
for (int i = 0; i < nodeFrom.params.length; i++) {
params[i] = nodeFrom.params[i] * (1 - fraction)
+ nodeTo.params[i] * fraction;
}
}
private static void addCommand(Path path, float[] current,
char previousCmd, char cmd, float[] val) {
int incr = 2;
float currentX = current[0];
float currentY = current[1];
float ctrlPointX = current[2];
float ctrlPointY = current[3];
float currentSegmentStartX = current[4];
float currentSegmentStartY = current[5];
float reflectiveCtrlPointX;
float reflectiveCtrlPointY;
switch (cmd) {
case 'z':
case 'Z':
path.close();
// Path is closed here, but we need to move the pen to the
// closed position. So we cache the segment's starting position,
// and restore it here.
currentX = currentSegmentStartX;
currentY = currentSegmentStartY;
ctrlPointX = currentSegmentStartX;
ctrlPointY = currentSegmentStartY;
path.moveTo(currentX, currentY);
break;
case 'm':
case 'M':
case 'l':
case 'L':
case 't':
case 'T':
incr = 2;
break;
case 'h':
case 'H':
case 'v':
case 'V':
incr = 1;
break;
case 'c':
case 'C':
incr = 6;
break;
case 's':
case 'S':
case 'q':
case 'Q':
incr = 4;
break;
case 'a':
case 'A':
incr = 7;
break;
}
for (int k = 0; k < val.length; k += incr) {
switch (cmd) {
case 'm': // moveto - Start a new sub-path (relative)
currentX += val[k + 0];
currentY += val[k + 1];
if (k > 0) {
// According to the spec, if a moveto is followed by multiple
// pairs of coordinates, the subsequent pairs are treated as
// implicit lineto commands.
path.rLineTo(val[k + 0], val[k + 1]);
} else {
path.rMoveTo(val[k + 0], val[k + 1]);
currentSegmentStartX = currentX;
currentSegmentStartY = currentY;
}
break;
case 'M': // moveto - Start a new sub-path
currentX = val[k + 0];
currentY = val[k + 1];
if (k > 0) {
// According to the spec, if a moveto is followed by multiple
// pairs of coordinates, the subsequent pairs are treated as
// implicit lineto commands.
path.lineTo(val[k + 0], val[k + 1]);
} else {
path.moveTo(val[k + 0], val[k + 1]);
currentSegmentStartX = currentX;
currentSegmentStartY = currentY;
}
break;
case 'l': // lineto - Draw a line from the current point (relative)
path.rLineTo(val[k + 0], val[k + 1]);
currentX += val[k + 0];
currentY += val[k + 1];
break;
case 'L': // lineto - Draw a line from the current point
path.lineTo(val[k + 0], val[k + 1]);
currentX = val[k + 0];
currentY = val[k + 1];
break;
case 'h': // horizontal lineto - Draws a horizontal line (relative)
path.rLineTo(val[k + 0], 0);
currentX += val[k + 0];
break;
case 'H': // horizontal lineto - Draws a horizontal line
path.lineTo(val[k + 0], currentY);
currentX = val[k + 0];
break;
case 'v': // vertical lineto - Draws a vertical line from the current point (r)
path.rLineTo(0, val[k + 0]);
currentY += val[k + 0];
break;
case 'V': // vertical lineto - Draws a vertical line from the current point
path.lineTo(currentX, val[k + 0]);
currentY = val[k + 0];
break;
case 'c': // curveto - Draws a cubic Bézier curve (relative)
path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
val[k + 4], val[k + 5]);
ctrlPointX = currentX + val[k + 2];
ctrlPointY = currentY + val[k + 3];
currentX += val[k + 4];
currentY += val[k + 5];
break;
case 'C': // curveto - Draws a cubic Bézier curve
path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
val[k + 4], val[k + 5]);
currentX = val[k + 4];
currentY = val[k + 5];
ctrlPointX = val[k + 2];
ctrlPointY = val[k + 3];
break;
case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
reflectiveCtrlPointX = 0;
reflectiveCtrlPointY = 0;
if (previousCmd == 'c' || previousCmd == 's'
|| previousCmd == 'C' || previousCmd == 'S') {
reflectiveCtrlPointX = currentX - ctrlPointX;
reflectiveCtrlPointY = currentY - ctrlPointY;
}
path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
val[k + 0], val[k + 1],
val[k + 2], val[k + 3]);
ctrlPointX = currentX + val[k + 0];
ctrlPointY = currentY + val[k + 1];
currentX += val[k + 2];
currentY += val[k + 3];
break;
case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
reflectiveCtrlPointX = currentX;
reflectiveCtrlPointY = currentY;
if (previousCmd == 'c' || previousCmd == 's'
|| previousCmd == 'C' || previousCmd == 'S') {
reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
}
path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
ctrlPointX = val[k + 0];
ctrlPointY = val[k + 1];
currentX = val[k + 2];
currentY = val[k + 3];
break;
case 'q': // Draws a quadratic Bézier (relative)
path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
ctrlPointX = currentX + val[k + 0];
ctrlPointY = currentY + val[k + 1];
currentX += val[k + 2];
currentY += val[k + 3];
break;
case 'Q': // Draws a quadratic Bézier
path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
ctrlPointX = val[k + 0];
ctrlPointY = val[k + 1];
currentX = val[k + 2];
currentY = val[k + 3];
break;
case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
reflectiveCtrlPointX = 0;
reflectiveCtrlPointY = 0;
if (previousCmd == 'q' || previousCmd == 't'
|| previousCmd == 'Q' || previousCmd == 'T') {
reflectiveCtrlPointX = currentX - ctrlPointX;
reflectiveCtrlPointY = currentY - ctrlPointY;
}
path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
val[k + 0], val[k + 1]);
ctrlPointX = currentX + reflectiveCtrlPointX;
ctrlPointY = currentY + reflectiveCtrlPointY;
currentX += val[k + 0];
currentY += val[k + 1];
break;
case 'T': // Draws a quadratic Bézier curve (reflective control point)
reflectiveCtrlPointX = currentX;
reflectiveCtrlPointY = currentY;
if (previousCmd == 'q' || previousCmd == 't'
|| previousCmd == 'Q' || previousCmd == 'T') {
reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
}
path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
val[k + 0], val[k + 1]);
ctrlPointX = reflectiveCtrlPointX;
ctrlPointY = reflectiveCtrlPointY;
currentX = val[k + 0];
currentY = val[k + 1];
break;
case 'a': // Draws an elliptical arc
// (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
drawArc(path,
currentX,
currentY,
val[k + 5] + currentX,
val[k + 6] + currentY,
val[k + 0],
val[k + 1],
val[k + 2],
val[k + 3] != 0,
val[k + 4] != 0);
currentX += val[k + 5];
currentY += val[k + 6];
ctrlPointX = currentX;
ctrlPointY = currentY;
break;
case 'A': // Draws an elliptical arc
drawArc(path,
currentX,
currentY,
val[k + 5],
val[k + 6],
val[k + 0],
val[k + 1],
val[k + 2],
val[k + 3] != 0,
val[k + 4] != 0);
currentX = val[k + 5];
currentY = val[k + 6];
ctrlPointX = currentX;
ctrlPointY = currentY;
break;
}
previousCmd = cmd;
}
current[0] = currentX;
current[1] = currentY;
current[2] = ctrlPointX;
current[3] = ctrlPointY;
current[4] = currentSegmentStartX;
current[5] = currentSegmentStartY;
}
private static void drawArc(Path p,
float x0,
float y0,
float x1,
float y1,
float a,
float b,
float theta,
boolean isMoreThanHalf,
boolean isPositiveArc) {
/* Convert rotation angle from degrees to radians */
double thetaD = Math.toRadians(theta);
/* Pre-compute rotation matrix entries */
double cosTheta = Math.cos(thetaD);
double sinTheta = Math.sin(thetaD);
/* Transform (x0, y0) and (x1, y1) into unit space */
/* using (inverse) rotation, followed by (inverse) scale */
double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
/* Compute differences and averages */
double dx = x0p - x1p;
double dy = y0p - y1p;
double xm = (x0p + x1p) / 2;
double ym = (y0p + y1p) / 2;
/* Solve for intersecting unit circles */
double dsq = dx * dx + dy * dy;
if (dsq == 0.0) {
Log.w(LOGTAG, " Points are coincident");
return; /* Points are coincident */
}
double disc = 1.0 / dsq - 1.0 / 4.0;
if (disc < 0.0) {
Log.w(LOGTAG, "Points are too far apart " + dsq);
float adjust = (float) (Math.sqrt(dsq) / 1.99999);
drawArc(p, x0, y0, x1, y1, a * adjust,
b * adjust, theta, isMoreThanHalf, isPositiveArc);
return; /* Points are too far apart */
}
double s = Math.sqrt(disc);
double sdx = s * dx;
double sdy = s * dy;
double cx;
double cy;
if (isMoreThanHalf == isPositiveArc) {
cx = xm - sdy;
cy = ym + sdx;
} else {
cx = xm + sdy;
cy = ym - sdx;
}
double eta0 = Math.atan2((y0p - cy), (x0p - cx));
double eta1 = Math.atan2((y1p - cy), (x1p - cx));
double sweep = (eta1 - eta0);
if (isPositiveArc != (sweep >= 0)) {
if (sweep > 0) {
sweep -= 2 * Math.PI;
} else {
sweep += 2 * Math.PI;
}
}
cx *= a;
cy *= b;
double tcx = cx;
cx = cx * cosTheta - cy * sinTheta;
cy = tcx * sinTheta + cy * cosTheta;
arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
}
/**
* Converts an arc to cubic Bezier segments and records them in p.
*
* @param p The target for the cubic Bezier segments
* @param cx The x coordinate center of the ellipse
* @param cy The y coordinate center of the ellipse
* @param a The radius of the ellipse in the horizontal direction
* @param b The radius of the ellipse in the vertical direction
* @param e1x E(eta1) x coordinate of the starting point of the arc
* @param e1y E(eta2) y coordinate of the starting point of the arc
* @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
* @param start The start angle of the arc on the ellipse
* @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
*/
private static void arcToBezier(Path p,
double cx,
double cy,
double a,
double b,
double e1x,
double e1y,
double theta,
double start,
double sweep) {
// Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
// and http://www.spaceroots.org/documents/ellipse/node22.html
// Maximum of 45 degrees per cubic Bezier segment
int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
double eta1 = start;
double cosTheta = Math.cos(theta);
double sinTheta = Math.sin(theta);
double cosEta1 = Math.cos(eta1);
double sinEta1 = Math.sin(eta1);
double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
double anglePerSegment = sweep / numSegments;
for (int i = 0; i < numSegments; i++) {
double eta2 = eta1 + anglePerSegment;
double sinEta2 = Math.sin(eta2);
double cosEta2 = Math.cos(eta2);
double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
double tanDiff2 = Math.tan((eta2 - eta1) / 2);
double alpha =
Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
double q1x = e1x + alpha * ep1x;
double q1y = e1y + alpha * ep1y;
double q2x = e2x - alpha * ep2x;
double q2y = e2y - alpha * ep2y;
// Adding this no-op call to workaround a proguard related issue.
p.rLineTo(0, 0);
p.cubicTo((float) q1x,
(float) q1y,
(float) q2x,
(float) q2y,
(float) e2x,
(float) e2y);
eta1 = eta2;
e1x = e2x;
e1y = e2y;
ep1x = ep2x;
ep1y = ep2y;
}
}
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/internal/pathview/PathsDrawable.java
================================================
package com.scwang.smartrefresh.layout.internal.pathview;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
/**
* 路径
* Created by SCWANG on 2017/6/1.
*/
public class PathsDrawable extends Drawable {
protected Paint mPaint;
protected List mPaths;
protected List mColors;
protected int mWidth = 1,mHeight = 1;
protected int mStartX = 0,mStartY = 0;
protected int mOrginWidth;
protected int mOrginHeight;
protected static final Region REGION = new Region();
protected static final Region MAX_CLIP = new Region(Integer.MIN_VALUE,
Integer.MIN_VALUE,Integer.MAX_VALUE, Integer.MAX_VALUE);
protected List mOrginPaths;
protected List mOrginSvgs;
public PathsDrawable() {
mPaint = new Paint();
mPaint.setColor(0xff11bbff);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
}
protected void onMeasure() {
Integer top = null,left = null,right = null,bottom = null;
if (mPaths != null) {
for (Path path : mPaths) {
REGION.setPath(path, MAX_CLIP);
Rect bounds = REGION.getBounds();
top = Math.min(top == null ? bounds.top : top, bounds.top);
left = Math.min(left == null ? bounds.left : left, bounds.left);
right = Math.max(right == null ? bounds.right : right, bounds.right);
bottom = Math.max(bottom == null ? bounds.bottom : bottom, bounds.bottom);
}
}
mStartX = left == null ? 0 : left;
mStartY = top == null ? 0 : top;
mWidth = right == null ? 0 : right - mStartX;
mHeight = bottom == null ? 0 : bottom - mStartY;
if (mOrginWidth == 0) {
mOrginWidth = mWidth;
}
if (mOrginHeight == 0) {
mOrginHeight = mHeight;
}
Rect bounds = getBounds();
super.setBounds(bounds.left, bounds.top, bounds.left + mWidth, bounds.top + mHeight);
}
@Override
public void setBounds(int left, int top, int right, int bottom) {
final int width = right - left;
final int height = bottom - top;
if (mOrginPaths != null && mOrginPaths.size() > 0 &&
(width != mWidth || height != mHeight)) {
float ratioWidth = 1f * width / mOrginWidth;
float ratioHeight = 1f * height / mOrginHeight;
mPaths = PathParser.transformScale(ratioWidth, ratioHeight, mOrginPaths, mOrginSvgs);
onMeasure();
} else {
super.setBounds(left, top, right, bottom);
}
}
public void setBounds(@NonNull Rect bounds) {
setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom);
}
public void parserPaths(String... paths) {
mOrginWidth = mOrginHeight = 0;
mOrginSvgs = new ArrayList<>();
mPaths = mOrginPaths = new ArrayList<>();
for (String path : paths) {
mOrginSvgs.add(path);
mOrginPaths.add(PathParser.createPathFromPathData(path));
}
onMeasure();
}
public void parserColors(int... colors) {
mColors = new ArrayList<>();
for (int color : colors) {
mColors.add(color);
}
}
//
@Override
public void draw(@NonNull Canvas canvas) {
Rect bounds = getBounds();
int width = bounds.width();
int height = bounds.height();
if (mPaint.getAlpha() == 0xFF) {
canvas.save();
canvas.translate(bounds.left-mStartX, bounds.top-mStartY);
if (mPaths != null) {
for (int i = 0; i < mPaths.size(); i++) {
if (mColors != null && i < mColors.size()) {
mPaint.setColor(mColors.get(i));
}
canvas.drawPath(mPaths.get(i), mPaint);
}
mPaint.setAlpha(0xFF);
}
canvas.restore();
} else {
createCachedBitmapIfNeeded(width, height);
if (!canReuseCache()) {
updateCachedBitmap(width, height);
updateCacheStates();
}
canvas.drawBitmap(mCachedBitmap, bounds.left, bounds.top, mPaint);
}
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
//
//
public int width() {
return getBounds().width();
}
public int height() {
return getBounds().height();
}
public void setGeometricWidth(int width) {
Rect bounds = getBounds();
float rate = 1f * width / bounds.width();
setBounds(
(int) (bounds.left * rate),
(int) (bounds.top * rate),
(int) (bounds.right * rate),
(int) (bounds.bottom * rate)
);
}
public void setGeometricHeight(int height) {
Rect bounds = getBounds();
float rate = 1f * height / bounds.height();
setBounds(
(int) (bounds.left * rate),
(int) (bounds.top * rate),
(int) (bounds.right * rate),
(int) (bounds.bottom * rate)
);
}
public Paint getPaint() {
return mPaint;
}
//
//
private Bitmap mCachedBitmap;
private boolean mCacheDirty;
public void updateCachedBitmap(int width, int height) {
mCachedBitmap.eraseColor(Color.TRANSPARENT);
Canvas tmpCanvas = new Canvas(mCachedBitmap);
drawCachedBitmap(tmpCanvas);
}
private void drawCachedBitmap(Canvas canvas) {
canvas.translate(-mStartX, -mStartY);
if (mPaths != null) {
for (int i = 0; i < mPaths.size(); i++) {
if (mColors != null && i < mColors.size()) {
mPaint.setColor(mColors.get(i));
}
canvas.drawPath(mPaths.get(i), mPaint);
}
}
}
public void createCachedBitmapIfNeeded(int width, int height) {
if (mCachedBitmap == null || !canReuseBitmap(width, height)) {
mCachedBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
mCacheDirty = true;
}
}
public boolean canReuseBitmap(int width, int height) {
if (width == mCachedBitmap.getWidth()
&& height == mCachedBitmap.getHeight()) {
return true;
}
return false;
}
public boolean canReuseCache() {
if (!mCacheDirty) {
return true;
}
return false;
}
public void updateCacheStates() {
// Use shallow copy here and shallow comparison in canReuseCache(),
// likely hit cache miss more, but practically not much difference.
mCacheDirty = false;
}
//
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/internal/pathview/PathsView.java
================================================
package com.scwang.smartrefresh.layout.internal.pathview;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
/**
* 路径视图
* Created by SCWANG on 2017/5/29.
*/
public class PathsView extends View {
protected PathsDrawable mPathsDrawable = new PathsDrawable();
public PathsView(Context context) {
super(context);
this.initView(context, null, 0);
}
public PathsView(Context context, AttributeSet attrs) {
super(context, attrs);
this.initView(context, attrs, 0);
}
public PathsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.initView(context, attrs, defStyleAttr);
}
private void initView(Context context, AttributeSet attrs, int defStyleAttr) {
mPathsDrawable = new PathsDrawable();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getTag() instanceof String) {
parserPaths(getTag().toString());
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(resolveSize(mPathsDrawable.width()+getPaddingLeft()+getPaddingRight(), widthMeasureSpec),
resolveSize(mPathsDrawable.height()+getPaddingTop()+getPaddingBottom(), heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mPathsDrawable.setBounds(getPaddingLeft(), getPaddingTop(),
Math.max((right - left) - getPaddingRight(), getPaddingLeft()),
Math.max((bottom - top) - getPaddingTop(), getPaddingTop()));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPathsDrawable.draw(canvas);
}
public void parserPaths(String... paths) {
mPathsDrawable.parserPaths(paths);
}
public void parserColors(int... colors) {
mPathsDrawable.parserColors(colors);
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/AnimationEndListener.java
================================================
package com.scwang.smartrefresh.layout.listener;
/**
* 动画
* Created by SCWANG on 2017/6/21.
*/
public interface AnimationEndListener {
void onAnimationEnd();
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/OnLoadmoreListener.java
================================================
package com.scwang.smartrefresh.layout.listener;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
/**
* 加载更多监听器
* Created by SCWANG on 2017/5/26.
*/
public interface OnLoadmoreListener {
void onLoadmore(RefreshLayout refreshlayout);
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/OnMultiPurposeListener.java
================================================
package com.scwang.smartrefresh.layout.listener;
import com.scwang.smartrefresh.layout.api.RefreshFooter;
import com.scwang.smartrefresh.layout.api.RefreshHeader;
/**
* 多功能监听器
* Created by SCWANG on 2017/5/26.
*/
public interface OnMultiPurposeListener extends OnRefreshLoadmoreListener, OnStateChangedListener {
void onHeaderPulling(RefreshHeader header, float percent, int offset, int headerHeight, int extendHeight);
void onHeaderReleasing(RefreshHeader header, float percent, int offset, int headerHeight, int extendHeight);
void onHeaderStartAnimator(RefreshHeader header, int headerHeight, int extendHeight);
void onHeaderFinish(RefreshHeader header, boolean success);
void onFooterPulling(RefreshFooter footer, float percent, int offset, int footerHeight, int extendHeight);
void onFooterReleasing(RefreshFooter footer, float percent, int offset, int footerHeight, int extendHeight);
void onFooterStartAnimator(RefreshFooter footer, int footerHeight, int extendHeight);
void onFooterFinish(RefreshFooter footer, boolean success);
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/OnRefreshListener.java
================================================
package com.scwang.smartrefresh.layout.listener;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
/**
* 刷新监听器
* Created by SCWANG on 2017/5/26.
*/
public interface OnRefreshListener {
void onRefresh(RefreshLayout refreshlayout);
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/OnRefreshLoadmoreListener.java
================================================
package com.scwang.smartrefresh.layout.listener;
/**
* 刷新加载组合监听器
* Created by SCWANG on 2017/5/26.
*/
public interface OnRefreshLoadmoreListener extends OnRefreshListener, OnLoadmoreListener{
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/OnStateChangedListener.java
================================================
package com.scwang.smartrefresh.layout.listener;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.constant.RefreshState;
/**
* 刷新状态改变监听器
* Created by SCWANG on 2017/5/26.
*/
public interface OnStateChangedListener {
/**
* 状态改变事件 {@link RefreshState}
* @param refreshLayout RefreshLayout
* @param oldState 改变之前的状态
* @param newState 改变之后的状态
*/
void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState);
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/SimpleMultiPurposeListener.java
================================================
package com.scwang.smartrefresh.layout.listener;
import com.scwang.smartrefresh.layout.api.RefreshFooter;
import com.scwang.smartrefresh.layout.api.RefreshHeader;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.constant.RefreshState;
/**
* 多功能监听器
* Created by SCWANG on 2017/5/26.
*/
public class SimpleMultiPurposeListener implements OnMultiPurposeListener {
@Override
public void onHeaderPulling(RefreshHeader header, float percent, int offset, int headerHeight, int extendHeight) {
}
@Override
public void onHeaderReleasing(RefreshHeader header, float percent, int offset, int footerHeight, int extendHeight) {
}
@Override
public void onHeaderStartAnimator(RefreshHeader header, int footerHeight, int extendHeight) {
}
@Override
public void onHeaderFinish(RefreshHeader header, boolean success) {
}
@Override
public void onFooterPulling(RefreshFooter footer, float percent, int offset, int footerHeight, int extendHeight) {
}
@Override
public void onFooterReleasing(RefreshFooter footer, float percent, int offset, int footerHeight, int extendHeight) {
}
@Override
public void onFooterStartAnimator(RefreshFooter footer, int headHeight, int extendHeight) {
}
@Override
public void onFooterFinish(RefreshFooter footer, boolean success) {
}
@Override
public void onRefresh(RefreshLayout refreshlayout) {
}
@Override
public void onLoadmore(RefreshLayout refreshlayout) {
}
@Override
public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) {
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/util/ColorUtils.java
================================================
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.scwang.smartrefresh.layout.util;
import android.graphics.Color;
import android.support.annotation.ColorInt;
import android.support.annotation.FloatRange;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
/**
* A set of color-related utility methods, building upon those available in {@code Color}.
*/
public final class ColorUtils {
private static final double XYZ_WHITE_REFERENCE_X = 95.047;
private static final double XYZ_WHITE_REFERENCE_Y = 100;
private static final double XYZ_WHITE_REFERENCE_Z = 108.883;
private static final double XYZ_EPSILON = 0.008856;
private static final double XYZ_KAPPA = 903.3;
private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10;
private static final int MIN_ALPHA_SEARCH_PRECISION = 1;
private static final ThreadLocal TEMP_ARRAY = new ThreadLocal<>();
private ColorUtils() {}
/**
* Composite two potentially translucent colors over each other and returns the result.
*/
public static int compositeColors(@ColorInt int foreground, @ColorInt int background) {
int bgAlpha = Color.alpha(background);
int fgAlpha = Color.alpha(foreground);
int a = compositeAlpha(fgAlpha, bgAlpha);
int r = compositeComponent(Color.red(foreground), fgAlpha,
Color.red(background), bgAlpha, a);
int g = compositeComponent(Color.green(foreground), fgAlpha,
Color.green(background), bgAlpha, a);
int b = compositeComponent(Color.blue(foreground), fgAlpha,
Color.blue(background), bgAlpha, a);
return Color.argb(a, r, g, b);
}
private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) {
return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF);
}
private static int compositeComponent(int fgC, int fgA, int bgC, int bgA, int a) {
if (a == 0) return 0;
return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (a * 0xFF);
}
/**
* Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}.
* Defined as the Y component in the XYZ representation of {@code color}.
*/
@FloatRange(from = 0.0, to = 1.0)
public static double calculateLuminance(@ColorInt int color) {
final double[] result = getTempDouble3Array();
colorToXYZ(color, result);
// Luminance is the Y component
return result[1] / 100;
}
/**
* Returns the contrast ratio between {@code foreground} and {@code background}.
* {@code background} must be opaque.
*
* Formula defined
* here.
*/
public static double calculateContrast(@ColorInt int foreground, @ColorInt int background) {
if (Color.alpha(background) != 255) {
throw new IllegalArgumentException("background can not be translucent: #"
+ Integer.toHexString(background));
}
if (Color.alpha(foreground) < 255) {
// If the foreground is translucent, composite the foreground over the background
foreground = compositeColors(foreground, background);
}
final double luminance1 = calculateLuminance(foreground) + 0.05;
final double luminance2 = calculateLuminance(background) + 0.05;
// Now return the lighter luminance divided by the darker luminance
return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2);
}
/**
* Calculates the minimum alpha value which can be applied to {@code foreground} so that would
* have a contrast value of at least {@code minContrastRatio} when compared to
* {@code background}.
*
* @param foreground the foreground color
* @param background the opaque background color
* @param minContrastRatio the minimum contrast ratio
* @return the alpha value in the range 0-255, or -1 if no value could be calculated
*/
public static int calculateMinimumAlpha(@ColorInt int foreground, @ColorInt int background,
float minContrastRatio) {
if (Color.alpha(background) != 255) {
throw new IllegalArgumentException("background can not be translucent: #"
+ Integer.toHexString(background));
}
// First lets check that a fully opaque foreground has sufficient contrast
int testForeground = setAlphaComponent(foreground, 255);
double testRatio = calculateContrast(testForeground, background);
if (testRatio < minContrastRatio) {
// Fully opaque foreground does not have sufficient contrast, return error
return -1;
}
// Binary search to find a value with the minimum value which provides sufficient contrast
int numIterations = 0;
int minAlpha = 0;
int maxAlpha = 255;
while (numIterations <= MIN_ALPHA_SEARCH_MAX_ITERATIONS &&
(maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) {
final int testAlpha = (minAlpha + maxAlpha) / 2;
testForeground = setAlphaComponent(foreground, testAlpha);
testRatio = calculateContrast(testForeground, background);
if (testRatio < minContrastRatio) {
minAlpha = testAlpha;
} else {
maxAlpha = testAlpha;
}
numIterations++;
}
// Conservatively return the max of the range of possible alphas, which is known to pass.
return maxAlpha;
}
/**
* Convert RGB components to HSL (hue-saturation-lightness).
*
* - outHsl[0] is Hue [0 .. 360)
* - outHsl[1] is Saturation [0...1]
* - outHsl[2] is Lightness [0...1]
*
*
* @param r red component value [0..255]
* @param g green component value [0..255]
* @param b blue component value [0..255]
* @param outHsl 3-element array which holds the resulting HSL components
*/
public static void RGBToHSL(@IntRange(from = 0x0, to = 0xFF) int r,
@IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
@NonNull float[] outHsl) {
final float rf = r / 255f;
final float gf = g / 255f;
final float bf = b / 255f;
final float max = Math.max(rf, Math.max(gf, bf));
final float min = Math.min(rf, Math.min(gf, bf));
final float deltaMaxMin = max - min;
float h, s;
float l = (max + min) / 2f;
if (max == min) {
// Monochromatic
h = s = 0f;
} else {
if (max == rf) {
h = ((gf - bf) / deltaMaxMin) % 6f;
} else if (max == gf) {
h = ((bf - rf) / deltaMaxMin) + 2f;
} else {
h = ((rf - gf) / deltaMaxMin) + 4f;
}
s = deltaMaxMin / (1f - Math.abs(2f * l - 1f));
}
h = (h * 60f) % 360f;
if (h < 0) {
h += 360f;
}
outHsl[0] = constrain(h, 0f, 360f);
outHsl[1] = constrain(s, 0f, 1f);
outHsl[2] = constrain(l, 0f, 1f);
}
/**
* Convert the ARGB color to its HSL (hue-saturation-lightness) components.
*
* - outHsl[0] is Hue [0 .. 360)
* - outHsl[1] is Saturation [0...1]
* - outHsl[2] is Lightness [0...1]
*
*
* @param color the ARGB color to convert. The alpha component is ignored
* @param outHsl 3-element array which holds the resulting HSL components
*/
public static void colorToHSL(@ColorInt int color, @NonNull float[] outHsl) {
RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), outHsl);
}
/**
* Convert HSL (hue-saturation-lightness) components to a RGB color.
*
* - hsl[0] is Hue [0 .. 360)
* - hsl[1] is Saturation [0...1]
* - hsl[2] is Lightness [0...1]
*
* If hsv values are out of range, they are pinned.
*
* @param hsl 3-element array which holds the input HSL components
* @return the resulting RGB color
*/
@ColorInt
public static int HSLToColor(@NonNull float[] hsl) {
final float h = hsl[0];
final float s = hsl[1];
final float l = hsl[2];
final float c = (1f - Math.abs(2 * l - 1f)) * s;
final float m = l - 0.5f * c;
final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f));
final int hueSegment = (int) h / 60;
int r = 0, g = 0, b = 0;
switch (hueSegment) {
case 0:
r = Math.round(255 * (c + m));
g = Math.round(255 * (x + m));
b = Math.round(255 * m);
break;
case 1:
r = Math.round(255 * (x + m));
g = Math.round(255 * (c + m));
b = Math.round(255 * m);
break;
case 2:
r = Math.round(255 * m);
g = Math.round(255 * (c + m));
b = Math.round(255 * (x + m));
break;
case 3:
r = Math.round(255 * m);
g = Math.round(255 * (x + m));
b = Math.round(255 * (c + m));
break;
case 4:
r = Math.round(255 * (x + m));
g = Math.round(255 * m);
b = Math.round(255 * (c + m));
break;
case 5:
case 6:
r = Math.round(255 * (c + m));
g = Math.round(255 * m);
b = Math.round(255 * (x + m));
break;
}
r = constrain(r, 0, 255);
g = constrain(g, 0, 255);
b = constrain(b, 0, 255);
return Color.rgb(r, g, b);
}
/**
* Set the alpha component of {@code color} to be {@code alpha}.
*/
@ColorInt
public static int setAlphaComponent(@ColorInt int color,
@IntRange(from = 0x0, to = 0xFF) int alpha) {
if (alpha < 0 || alpha > 255) {
throw new IllegalArgumentException("alpha must be between 0 and 255.");
}
return (color & 0x00ffffff) | (alpha << 24);
}
/**
* Convert the ARGB color to its CIE Lab representative components.
*
* @param color the ARGB color to convert. The alpha component is ignored
* @param outLab 3-element array which holds the resulting LAB components
*/
public static void colorToLAB(@ColorInt int color, @NonNull double[] outLab) {
RGBToLAB(Color.red(color), Color.green(color), Color.blue(color), outLab);
}
/**
* Convert RGB components to its CIE Lab representative components.
*
*
* - outLab[0] is L [0 ...1)
* - outLab[1] is a [-128...127)
* - outLab[2] is b [-128...127)
*
*
* @param r red component value [0..255]
* @param g green component value [0..255]
* @param b blue component value [0..255]
* @param outLab 3-element array which holds the resulting LAB components
*/
public static void RGBToLAB(@IntRange(from = 0x0, to = 0xFF) int r,
@IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
@NonNull double[] outLab) {
// First we convert RGB to XYZ
RGBToXYZ(r, g, b, outLab);
// outLab now contains XYZ
XYZToLAB(outLab[0], outLab[1], outLab[2], outLab);
// outLab now contains LAB representation
}
/**
* Convert the ARGB color to it's CIE XYZ representative components.
*
* The resulting XYZ representation will use the D65 illuminant and the CIE
* 2° Standard Observer (1931).
*
*
* - outXyz[0] is X [0 ...95.047)
* - outXyz[1] is Y [0...100)
* - outXyz[2] is Z [0...108.883)
*
*
* @param color the ARGB color to convert. The alpha component is ignored
* @param outXyz 3-element array which holds the resulting LAB components
*/
public static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) {
RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz);
}
/**
* Convert RGB components to it's CIE XYZ representative components.
*
* The resulting XYZ representation will use the D65 illuminant and the CIE
* 2° Standard Observer (1931).
*
*
* - outXyz[0] is X [0 ...95.047)
* - outXyz[1] is Y [0...100)
* - outXyz[2] is Z [0...108.883)
*
*
* @param r red component value [0..255]
* @param g green component value [0..255]
* @param b blue component value [0..255]
* @param outXyz 3-element array which holds the resulting XYZ components
*/
public static void RGBToXYZ(@IntRange(from = 0x0, to = 0xFF) int r,
@IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
@NonNull double[] outXyz) {
if (outXyz.length != 3) {
throw new IllegalArgumentException("outXyz must have a length of 3.");
}
double sr = r / 255.0;
sr = sr < 0.04045 ? sr / 12.92 : Math.pow((sr + 0.055) / 1.055, 2.4);
double sg = g / 255.0;
sg = sg < 0.04045 ? sg / 12.92 : Math.pow((sg + 0.055) / 1.055, 2.4);
double sb = b / 255.0;
sb = sb < 0.04045 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4);
outXyz[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805);
outXyz[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722);
outXyz[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505);
}
/**
* Converts a color from CIE XYZ to CIE Lab representation.
*
* This method expects the XYZ representation to use the D65 illuminant and the CIE
* 2° Standard Observer (1931).
*
*
* - outLab[0] is L [0 ...1)
* - outLab[1] is a [-128...127)
* - outLab[2] is b [-128...127)
*
*
* @param x X component value [0...95.047)
* @param y Y component value [0...100)
* @param z Z component value [0...108.883)
* @param outLab 3-element array which holds the resulting Lab components
*/
public static void XYZToLAB(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x,
@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y,
@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z,
@NonNull double[] outLab) {
if (outLab.length != 3) {
throw new IllegalArgumentException("outLab must have a length of 3.");
}
x = pivotXyzComponent(x / XYZ_WHITE_REFERENCE_X);
y = pivotXyzComponent(y / XYZ_WHITE_REFERENCE_Y);
z = pivotXyzComponent(z / XYZ_WHITE_REFERENCE_Z);
outLab[0] = Math.max(0, 116 * y - 16);
outLab[1] = 500 * (x - y);
outLab[2] = 200 * (y - z);
}
/**
* Converts a color from CIE Lab to CIE XYZ representation.
*
* The resulting XYZ representation will use the D65 illuminant and the CIE
* 2° Standard Observer (1931).
*
*
* - outXyz[0] is X [0 ...95.047)
* - outXyz[1] is Y [0...100)
* - outXyz[2] is Z [0...108.883)
*
*
* @param l L component value [0...100)
* @param a A component value [-128...127)
* @param b B component value [-128...127)
* @param outXyz 3-element array which holds the resulting XYZ components
*/
public static void LABToXYZ(@FloatRange(from = 0f, to = 100) final double l,
@FloatRange(from = -128, to = 127) final double a,
@FloatRange(from = -128, to = 127) final double b,
@NonNull double[] outXyz) {
final double fy = (l + 16) / 116;
final double fx = a / 500 + fy;
final double fz = fy - b / 200;
double tmp = Math.pow(fx, 3);
final double xr = tmp > XYZ_EPSILON ? tmp : (116 * fx - 16) / XYZ_KAPPA;
final double yr = l > XYZ_KAPPA * XYZ_EPSILON ? Math.pow(fy, 3) : l / XYZ_KAPPA;
tmp = Math.pow(fz, 3);
final double zr = tmp > XYZ_EPSILON ? tmp : (116 * fz - 16) / XYZ_KAPPA;
outXyz[0] = xr * XYZ_WHITE_REFERENCE_X;
outXyz[1] = yr * XYZ_WHITE_REFERENCE_Y;
outXyz[2] = zr * XYZ_WHITE_REFERENCE_Z;
}
/**
* Converts a color from CIE XYZ to its RGB representation.
*
* This method expects the XYZ representation to use the D65 illuminant and the CIE
* 2° Standard Observer (1931).
*
* @param x X component value [0...95.047)
* @param y Y component value [0...100)
* @param z Z component value [0...108.883)
* @return int containing the RGB representation
*/
@ColorInt
public static int XYZToColor(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x,
@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y,
@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z) {
double r = (x * 3.2406 + y * -1.5372 + z * -0.4986) / 100;
double g = (x * -0.9689 + y * 1.8758 + z * 0.0415) / 100;
double b = (x * 0.0557 + y * -0.2040 + z * 1.0570) / 100;
r = r > 0.0031308 ? 1.055 * Math.pow(r, 1 / 2.4) - 0.055 : 12.92 * r;
g = g > 0.0031308 ? 1.055 * Math.pow(g, 1 / 2.4) - 0.055 : 12.92 * g;
b = b > 0.0031308 ? 1.055 * Math.pow(b, 1 / 2.4) - 0.055 : 12.92 * b;
return Color.rgb(
constrain((int) Math.round(r * 255), 0, 255),
constrain((int) Math.round(g * 255), 0, 255),
constrain((int) Math.round(b * 255), 0, 255));
}
/**
* Converts a color from CIE Lab to its RGB representation.
*
* @param l L component value [0...100]
* @param a A component value [-128...127]
* @param b B component value [-128...127]
* @return int containing the RGB representation
*/
@ColorInt
public static int LABToColor(@FloatRange(from = 0f, to = 100) final double l,
@FloatRange(from = -128, to = 127) final double a,
@FloatRange(from = -128, to = 127) final double b) {
final double[] result = getTempDouble3Array();
LABToXYZ(l, a, b, result);
return XYZToColor(result[0], result[1], result[2]);
}
/**
* Returns the euclidean distance between two LAB colors.
*/
public static double distanceEuclidean(@NonNull double[] labX, @NonNull double[] labY) {
return Math.sqrt(Math.pow(labX[0] - labY[0], 2)
+ Math.pow(labX[1] - labY[1], 2)
+ Math.pow(labX[2] - labY[2], 2));
}
private static float constrain(float amount, float low, float high) {
return amount < low ? low : (amount > high ? high : amount);
}
private static int constrain(int amount, int low, int high) {
return amount < low ? low : (amount > high ? high : amount);
}
private static double pivotXyzComponent(double component) {
return component > XYZ_EPSILON
? Math.pow(component, 1 / 3.0)
: (XYZ_KAPPA * component + 16) / 116;
}
/**
* Blend between two ARGB colors using the given ratio.
*
* A blend ratio of 0.0 will result in {@code color1}, 0.5 will give an even blend,
* 1.0 will result in {@code color2}.
*
* @param color1 the first ARGB color
* @param color2 the second ARGB color
* @param ratio the blend ratio of {@code color1} to {@code color2}
*/
@ColorInt
public static int blendARGB(@ColorInt int color1, @ColorInt int color2,
@FloatRange(from = 0.0, to = 1.0) float ratio) {
final float inverseRatio = 1 - ratio;
float a = Color.alpha(color1) * inverseRatio + Color.alpha(color2) * ratio;
float r = Color.red(color1) * inverseRatio + Color.red(color2) * ratio;
float g = Color.green(color1) * inverseRatio + Color.green(color2) * ratio;
float b = Color.blue(color1) * inverseRatio + Color.blue(color2) * ratio;
return Color.argb((int) a, (int) r, (int) g, (int) b);
}
/**
* Blend between {@code hsl1} and {@code hsl2} using the given ratio. This will interpolate
* the hue using the shortest angle.
*
* A blend ratio of 0.0 will result in {@code hsl1}, 0.5 will give an even blend,
* 1.0 will result in {@code hsl2}.
*
* @param hsl1 3-element array which holds the first HSL color
* @param hsl2 3-element array which holds the second HSL color
* @param ratio the blend ratio of {@code hsl1} to {@code hsl2}
* @param outResult 3-element array which holds the resulting HSL components
*/
public static void blendHSL(@NonNull float[] hsl1, @NonNull float[] hsl2,
@FloatRange(from = 0.0, to = 1.0) float ratio, @NonNull float[] outResult) {
if (outResult.length != 3) {
throw new IllegalArgumentException("result must have a length of 3.");
}
final float inverseRatio = 1 - ratio;
// Since hue is circular we will need to interpolate carefully
outResult[0] = circularInterpolate(hsl1[0], hsl2[0], ratio);
outResult[1] = hsl1[1] * inverseRatio + hsl2[1] * ratio;
outResult[2] = hsl1[2] * inverseRatio + hsl2[2] * ratio;
}
/**
* Blend between two CIE-LAB colors using the given ratio.
*
* A blend ratio of 0.0 will result in {@code lab1}, 0.5 will give an even blend,
* 1.0 will result in {@code lab2}.
*
* @param lab1 3-element array which holds the first LAB color
* @param lab2 3-element array which holds the second LAB color
* @param ratio the blend ratio of {@code lab1} to {@code lab2}
* @param outResult 3-element array which holds the resulting LAB components
*/
public static void blendLAB(@NonNull double[] lab1, @NonNull double[] lab2,
@FloatRange(from = 0.0, to = 1.0) double ratio, @NonNull double[] outResult) {
if (outResult.length != 3) {
throw new IllegalArgumentException("outResult must have a length of 3.");
}
final double inverseRatio = 1 - ratio;
outResult[0] = lab1[0] * inverseRatio + lab2[0] * ratio;
outResult[1] = lab1[1] * inverseRatio + lab2[1] * ratio;
outResult[2] = lab1[2] * inverseRatio + lab2[2] * ratio;
}
@VisibleForTesting
static float circularInterpolate(float a, float b, float f) {
if (Math.abs(b - a) > 180) {
if (b > a) {
a += 360;
} else {
b += 360;
}
}
return (a + ((b - a) * f)) % 360;
}
private static double[] getTempDouble3Array() {
double[] result = TEMP_ARRAY.get();
if (result == null) {
result = new double[3];
TEMP_ARRAY.set(result);
}
return result;
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/util/DelayedRunable.java
================================================
package com.scwang.smartrefresh.layout.util;
public class DelayedRunable implements Runnable {
public long delayMillis;
public Runnable runnable = null;
public DelayedRunable(Runnable runnable) {
this.runnable = runnable;
}
public DelayedRunable(Runnable runnable, long delayMillis) {
this.runnable = runnable;
this.delayMillis = delayMillis;
}
@Override
public void run() {
if (runnable != null) {
runnable.run();
runnable = null;
}
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/util/DensityUtil.java
================================================
package com.scwang.smartrefresh.layout.util;
import android.content.res.Resources;
public class DensityUtil {
float density;
public DensityUtil() {
density = Resources.getSystem().getDisplayMetrics().density;
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dp2px(float dpValue) {
return (int) (0.5f + dpValue * Resources.getSystem().getDisplayMetrics().density);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public static float px2dp(float pxValue) {
return (pxValue / Resources.getSystem().getDisplayMetrics().density);
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public int dip2px(float dpValue) {
return (int) (0.5f + dpValue * density);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public float px2dip(float pxValue) {
return (pxValue / density);
}
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/util/ScrollBoundaryUtil.java
================================================
package com.scwang.smartrefresh.layout.util;
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
/**
* 滚动边界
* Created by SCWANG on 2017/7/8.
*/
@SuppressWarnings("WeakerAccess")
public class ScrollBoundaryUtil {
//
public static boolean canRefresh(View targetView, MotionEvent event) {
if (canScrollUp(targetView)) {
return false;
}
if (targetView instanceof ViewGroup && event != null) {
ViewGroup viewGroup = (ViewGroup) targetView;
final int childCount = viewGroup.getChildCount();
PointF point = new PointF();
for (int i = childCount; i > 0; i--) {
View child = viewGroup.getChildAt(i - 1);
if (isTransformedTouchPointInView(viewGroup,child, event.getX(), event.getY() , point)) {
event = MotionEvent.obtain(event);
event.offsetLocation(point.x, point.y);
return canRefresh(child, event);
}
}
}
return true;
}
public static boolean canLoadmore(View targetView, MotionEvent event) {
if (!canScrollDown(targetView) && canScrollUp(targetView)) {
return true;
}
if (targetView instanceof ViewGroup && event != null) {
ViewGroup viewGroup = (ViewGroup) targetView;
final int childCount = viewGroup.getChildCount();
PointF point = new PointF();
for (int i = 0; i < childCount; i++) {
View child = viewGroup.getChildAt(i);
if (isTransformedTouchPointInView(viewGroup,child, event.getX(), event.getY() , point)) {
event = MotionEvent.obtain(event);
event.offsetLocation(point.x, point.y);
return canLoadmore(child, event);
}
}
}
return false;
}
public static boolean canScrollDown(View targetView, MotionEvent event) {
if (canScrollDown(targetView)) {
return true;
}
if (targetView instanceof ViewGroup && event != null) {
ViewGroup viewGroup = (ViewGroup) targetView;
final int childCount = viewGroup.getChildCount();
PointF point = new PointF();
for (int i = 0; i < childCount; i++) {
View child = viewGroup.getChildAt(i);
if (isTransformedTouchPointInView(viewGroup,child, event.getX(), event.getY() , point)) {
event = MotionEvent.obtain(event);
event.offsetLocation(point.x, point.y);
return canScrollDown(child, event);
}
}
}
return false;
}
public static boolean canScrollUp(View targetView) {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (targetView instanceof AbsListView) {
final AbsListView absListView = (AbsListView) targetView;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return targetView.getScrollY() > 0;
}
} else {
return targetView.canScrollVertically(-1);
}
}
public static boolean canScrollDown(View targetView) {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (targetView instanceof AbsListView) {
final AbsListView absListView = (AbsListView) targetView;
return absListView.getChildCount() > 0
&& (absListView.getLastVisiblePosition() < absListView.getChildCount() - 1
|| absListView.getChildAt(absListView.getChildCount() - 1).getBottom() > absListView.getPaddingBottom());
} else {
return targetView.getScrollY() < 0;
}
} else {
return targetView.canScrollVertically(1);
}
}
//
//
public static boolean pointInView(View view, float localX, float localY, float slop) {
final float left = /*Math.max(view.getPaddingLeft(), 0)*/ - slop;
final float top = /*Math.max(view.getPaddingTop(), 0)*/ - slop;
final float width = view.getWidth()/* - Math.max(view.getPaddingLeft(), 0) - Math.max(view.getPaddingRight(), 0)*/;
final float height = view.getHeight()/* - Math.max(view.getPaddingTop(), 0) - Math.max(view.getPaddingBottom(), 0)*/;
return localX >= left && localY >= top && localX < ((width) + slop) &&
localY < ((height) + slop);
}
public static boolean isTransformedTouchPointInView(ViewGroup group, View child, float x, float y,PointF outLocalPoint) {
final float[] point = new float[2];
point[0] = x;
point[1] = y;
transformPointToViewLocal(group, child, point);
final boolean isInView = pointInView(child, point[0], point[1], 0);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0]-x, point[1]-y);
}
return isInView;
}
public static void transformPointToViewLocal(ViewGroup group, View child, float[] point) {
point[0] += group.getScrollX() - child.getLeft();
point[1] += group.getScrollY() - child.getTop();
}
//
}
================================================
FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/util/ViscousFluidInterpolator.java
================================================
package com.scwang.smartrefresh.layout.util;
import android.view.animation.Interpolator;
public class ViscousFluidInterpolator implements Interpolator {
/** Controls the viscous fluid effect (how much of it). */
private static final float VISCOUS_FLUID_SCALE = 8.0f;
private static final float VISCOUS_FLUID_NORMALIZE;
private static final float VISCOUS_FLUID_OFFSET;
static {
// must be set to 1.0 (used in viscousFluid())
VISCOUS_FLUID_NORMALIZE = 1.0f / viscousFluid(1.0f);
// account for very small floating-point error
VISCOUS_FLUID_OFFSET = 1.0f - VISCOUS_FLUID_NORMALIZE * viscousFluid(1.0f);
}
private static float viscousFluid(float x) {
x *= VISCOUS_FLUID_SCALE;
if (x < 1.0f) {
x -= (1.0f - (float)Math.exp(-x));
} else {
float start = 0.36787944117f; // 1/e == exp(-1)
x = 1.0f - (float)Math.exp(1.0f - x);
x = start + x * (1.0f - start);
}
return x;
}
@Override
public float getInterpolation(float input) {
final float interpolated = VISCOUS_FLUID_NORMALIZE * viscousFluid(input);
if (interpolated > 0) {
return interpolated + VISCOUS_FLUID_OFFSET;
}
return interpolated;
}
}
================================================
FILE: refresh-layout/src/main/res/values/attrs.xml
================================================
================================================
FILE: refresh-layout/src/main/res/values/strings.xml
================================================
SmartRefreshLayout
================================================
FILE: refresh-layout/src/test/java/com/scwang/smartrefresh/layout/ExampleUnitTest.java
================================================
package com.scwang.smartrefresh.layout;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see Testing documentation
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
================================================
FILE: settings.gradle
================================================
include ':app', ':refresh-layout', ':refresh-header', ':refresh-footer'