下拉刷新
从框架自带的ClassicsHeader分析下拉刷新。
public class ClassicsHeader extends ClassicsAbstract<ClassicsHeader> implements RefreshHeader {}
继承自ClassicsAbstract类,并实现了RefreshHeader接口。我们先看看接口的继承关系。
public interface RefreshHeader extends RefreshComponent {}
RefreshHeader接口是个空实现,他继承了RefreshComponent接口。
public interface RefreshComponent extends OnStateChangedListener {}
RefreshComponent接口继承了OnStateChangedListener,用于监听刷新状态的改变。
/**
* 刷新状态改变监听器
*/
public interface OnStateChangedListener {
/**
* 【仅限框架内调用】状态改变事件 {@link RefreshState}
* @param refreshLayout RefreshLayout
* @param oldState 改变之前的状态
* @param newState 改变之后的状态
*/
void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState);
}
我们接着看RefreshComponent接口。
public interface RefreshComponent extends OnStateChangedListener {
/**
* 获取实体视图
* @return 实体视图
*/
@NonNull
View getView();
/**
* 获取变换方式 {@link SpinnerStyle} 必须返回 非空
* @return 变换方式
*/
@NonNull
SpinnerStyle getSpinnerStyle();
/**
* 【仅限框架内调用】设置主题颜色
* @param colors 对应Xml中配置的 srlPrimaryColor srlAccentColor
*/
@RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES})
void setPrimaryColors(@ColorInt int... colors);
/**
* 【仅限框架内调用】尺寸定义完成 (如果高度不改变(代码修改:setHeader),只调用一次, 在RefreshLayout#onMeasure中调用)
* @param kernel RefreshKernel
* @param height HeaderHeight or FooterHeight
* @param maxDragHeight 最大拖动高度
*/
@RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES})
void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight);
/**
* 【仅限框架内调用】手指拖动下拉(会连续多次调用,添加isDragging并取代之前的onPulling、onReleasing)
* @param isDragging true 手指正在拖动 false 回弹动画
* @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+maxDragHeight) / footerHeight )
* @param offset 下拉的像素偏移量 0 - offset - (footerHeight+maxDragHeight)
* @param height 高度 HeaderHeight or FooterHeight (offset 可以超过 height 此时 percent 大于 1)
* @param maxDragHeight 最大拖动高度 offset 可以超过 height 参数 但是不会超过 maxDragHeight
*/
@RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES})
void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight);
/**
* 【仅限框架内调用】释放时刻(调用一次,将会触发加载)
* @param refreshLayout RefreshLayout
* @param height 高度 HeaderHeight or FooterHeight
* @param maxDragHeight 最大拖动高度
*/
@RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES})
void onReleased(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight);
/**
* 【仅限框架内调用】开始动画
* @param refreshLayout RefreshLayout
* @param height HeaderHeight or FooterHeight
* @param maxDragHeight 最大拖动高度
*/
@RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES})
void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight);
/**
* 【仅限框架内调用】动画结束
* @param refreshLayout RefreshLayout
* @param success 数据是否成功刷新或加载
* @return 完成动画所需时间 如果返回 Integer.MAX_VALUE 将取消本次完成事件,继续保持原有状态
*/
@RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES})
int onFinish(@NonNull RefreshLayout refreshLayout, boolean success);
/**
* 【仅限框架内调用】水平方向的拖动
* @param percentX 下拉时,手指水平坐标对屏幕的占比(0 - percentX - 1)
* @param offsetX 下拉时,手指水平坐标对屏幕的偏移(0 - offsetX - LayoutWidth)
* @param offsetMax 最大的偏移量
*/
@RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES})
void onHorizontalDrag(float percentX, int offsetX, int offsetMax);
/**
* 是否支持水平方向的拖动(将会影响到onHorizontalDrag的调用)
* @return 水平拖动需要消耗更多的时间和资源,所以如果不支持请返回false
*/
boolean isSupportHorizontalDrag();
}
接口的继承关系看完了,接着看实现类。
public class ClassicsHeader extends ClassicsAbstract<ClassicsHeader> implements RefreshHeader {}
继承自ClassicsAbstract类。它是一个抽象类。继承自SimpleComponent。
public abstract class ClassicsAbstract<T extends ClassicsAbstract> extends SimpleComponent implements RefreshComponent {}
SimpleComponent很有趣。它是一个抽象类RelativeLayout的子类。
/**
* Component 初步实现
* 实现 Header 和 Footer 时,继承 ComponentAbstract 的话可以少写很多接口方法
*/
public abstract class SimpleComponent extends RelativeLayout implements RefreshComponent {}
在构造方法中传入了上拉刷新的布局,然后SimpleComponent就保存了对应的刷新View和刷新接口。
protected View mWrappedView;
protected SpinnerStyle mSpinnerStyle;
protected RefreshComponent mWrappedInternal;
protected SimpleComponent(@NonNull View wrapped) {
this(wrapped, wrapped instanceof RefreshComponent ? (RefreshComponent) wrapped : null);
}
protected SimpleComponent(@NonNull View wrappedView, @Nullable RefreshComponent wrappedInternal) {
super(wrappedView.getContext(), null, 0);
this.mWrappedView = wrappedView;
this.mWrappedInternal = wrappedInternal;
if (this instanceof RefreshFooter && mWrappedInternal instanceof RefreshHeader && mWrappedInternal.getSpinnerStyle() == SpinnerStyle.MatchLayout) {
wrappedInternal.getView().setScaleY(-1);
} else if (this instanceof RefreshHeader && mWrappedInternal instanceof RefreshFooter && mWrappedInternal.getSpinnerStyle() == SpinnerStyle.MatchLayout) {
wrappedInternal.getView().setScaleY(-1);
}
}
protected SimpleComponent(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
其他的就是实现了对应的接口方法,在对应的接口里面调用传入的mWrappedInternal的接口,相当于包装了一层,在调用mWrappedInternal方法前做了一些逻辑处理。
@NonNull
public View getView() {
return mWrappedView == null ? this : mWrappedView;
}
@Override
public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) {
if (mWrappedInternal != null && mWrappedInternal != this) {
return mWrappedInternal.onFinish(refreshLayout, success);
}
return 0;
}
@Override
public void setPrimaryColors(@ColorInt int ... colors) {
if (mWrappedInternal != null && mWrappedInternal != this) {
mWrappedInternal.setPrimaryColors(colors);
}
}
@NonNull
@Override
public SpinnerStyle getSpinnerStyle() {
if (mSpinnerStyle != null) {
return mSpinnerStyle;
}
if (mWrappedInternal != null && mWrappedInternal != this) {
return mWrappedInternal.getSpinnerStyle();
}
if (mWrappedView != null) {
ViewGroup.LayoutParams params = mWrappedView.getLayoutParams();
if (params instanceof SmartRefreshLayout.LayoutParams) {
mSpinnerStyle = ((SmartRefreshLayout.LayoutParams) params).spinnerStyle;
if (mSpinnerStyle != null) {
return mSpinnerStyle;
}
}
if (params != null) {
if (params.height == 0 || params.height == MATCH_PARENT) {
for (SpinnerStyle style : SpinnerStyle.values) {
if (style.scale) {
return mSpinnerStyle = style;
}
}
}
}
}
return mSpinnerStyle = SpinnerStyle.Translate;
}
@Override
public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) {
if (mWrappedInternal != null && mWrappedInternal != this) {
mWrappedInternal.onInitialized(kernel, height, maxDragHeight);
} else if (mWrappedView != null) {
ViewGroup.LayoutParams params = mWrappedView.getLayoutParams();
if (params instanceof SmartRefreshLayout.LayoutParams) {
kernel.requestDrawBackgroundFor(this, ((SmartRefreshLayout.LayoutParams) params).backgroundColor);
}
}
}
@Override
public boolean isSupportHorizontalDrag() {
return mWrappedInternal != null && mWrappedInternal != this && mWrappedInternal.isSupportHorizontalDrag();
}
@Override
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
if (mWrappedInternal != null && mWrappedInternal != this) {
mWrappedInternal.onHorizontalDrag(percentX, offsetX, offsetMax);
}
}
@Override
public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) {
if (mWrappedInternal != null && mWrappedInternal != this) {
mWrappedInternal.onMoving(isDragging, percent, offset, height, maxDragHeight);
}
}
@Override
public void onReleased(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
if (mWrappedInternal != null && mWrappedInternal != this) {
mWrappedInternal.onReleased(refreshLayout, height, maxDragHeight);
}
}
@Override
public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
if (mWrappedInternal != null && mWrappedInternal != this) {
mWrappedInternal.onStartAnimator(refreshLayout, height, maxDragHeight);
}
}
@Override
public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
if (mWrappedInternal != null && mWrappedInternal != this) {
if (this instanceof RefreshFooter && mWrappedInternal instanceof RefreshHeader) {
if (oldState.isFooter) {
oldState = oldState.toHeader();
}
if (newState.isFooter) {
newState = newState.toHeader();
}
} else if (this instanceof RefreshHeader && mWrappedInternal instanceof RefreshFooter) {
if (oldState.isHeader) {
oldState = oldState.toFooter();
}
if (newState.isHeader) {
newState = newState.toFooter();
}
}
final OnStateChangedListener listener = mWrappedInternal;
if (listener != null) {
listener.onStateChanged(refreshLayout, oldState, newState);
}
}
}
@SuppressLint("RestrictedApi")
public boolean setNoMoreData(boolean noMoreData) {
return mWrappedInternal instanceof RefreshFooter && ((RefreshFooter) mWrappedInternal).setNoMoreData(noMoreData);
}
我们需要实现经典的刷新头,ClassicsAbstract继承了SimpleComponent。调用父类的构造方法之后,设置了SpinnerStyle的类型。
public ClassicsAbstract(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mSpinnerStyle = SpinnerStyle.Translate;
}
然后实现RefreshComponent接口。根据自己的刷新布局实现逻辑。
@Override
public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) {
mRefreshKernel = kernel;
mRefreshKernel.requestDrawBackgroundFor(this, mBackgroundColor);
}
@Override
public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
final View progressView = mProgressView;
if (progressView.getVisibility() != VISIBLE) {
progressView.setVisibility(VISIBLE);
Drawable drawable = mProgressView.getDrawable();
if (drawable instanceof Animatable) {
((Animatable) drawable).start();
} else {
progressView.animate().rotation(36000).setDuration(100000);
}
}
}
@Override
public void onReleased(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
onStartAnimator(refreshLayout, height, maxDragHeight);// 释放时开始动画
}
@Override
public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) {
final View progressView = mProgressView;
Drawable drawable = mProgressView.getDrawable();
if (drawable instanceof Animatable) {
if (((Animatable) drawable).isRunning()) {
((Animatable) drawable).stop();
}
} else {
progressView.animate().rotation(0).setDuration(0);
}
progressView.setVisibility(GONE);
return mFinishDuration;//延迟500毫秒之后再弹回
}
@Override
public void setPrimaryColors(@ColorInt int... colors) {
if (colors.length > 0) {
final View thisView = this;
if (!(thisView.getBackground() instanceof BitmapDrawable) && !mSetPrimaryColor) {
setPrimaryColor(colors[0]);
mSetPrimaryColor = false;
}
if (!mSetAccentColor) {
if (colors.length > 1) {
setAccentColor(colors[1]);
}
mSetAccentColor = false;
}
}
}
@Override
public void setPrimaryColors(@ColorInt int... colors) {
if (colors.length > 0) {
final View thisView = this;
if (!(thisView.getBackground() instanceof BitmapDrawable) && !mSetPrimaryColor) {
setPrimaryColor(colors[0]);
mSetPrimaryColor = false;
}
if (!mSetAccentColor) {
if (colors.length > 1) {
setAccentColor(colors[1]);
}
mSetAccentColor = false;
}
}
}
public T setPrimaryColor(@ColorInt int primaryColor) {
mSetPrimaryColor = true;
mBackgroundColor = primaryColor;
if (mRefreshKernel != null) {
mRefreshKernel.requestDrawBackgroundFor(this, primaryColor);
}
return self();
}
public T setAccentColor(@ColorInt int accentColor) {
mSetAccentColor = true;
mTitleText.setTextColor(accentColor);
if (mArrowDrawable != null) {
mArrowDrawable.setColor(accentColor);
mArrowView.invalidateDrawable(mArrowDrawable);
}
if (mProgressDrawable != null) {
mProgressDrawable.setColor(accentColor);
mProgressView.invalidateDrawable(mProgressDrawable);
}
return self();
}
protected T self() {
return (T) this;
}
接着就是ClassicsHeader实现类。通过自定义布局加载View。
public ClassicsHeader(Context context) {
this(context, null);
}
public ClassicsHeader(Context context, AttributeSet attrs) {
super(context, attrs, 0);
View.inflate(context, R.layout.srl_classics_header, this);
final View thisView = this;
final View arrowView = mArrowView = thisView.findViewById(R.id.srl_classics_arrow);
final View updateView = mLastUpdateText = thisView.findViewById(R.id.srl_classics_update);
final View progressView = mProgressView = thisView.findViewById(R.id.srl_classics_progress);
mTitleText = thisView.findViewById(R.id.srl_classics_title);
}
自定义布局。
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:paddingTop="20dp"
tools:paddingBottom="20dp"
tools:background="#eee"
tools:parentTag="android.widget.RelativeLayout">
<ImageView
android:id="@+id/srl_classics_arrow"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/srl_classics_center"
android:layout_toStartOf="@+id/srl_classics_center"
android:contentDescription="@android:string/untitled"
/>
<ImageView
android:id="@+id/srl_classics_progress"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/srl_classics_center"
android:layout_toStartOf="@+id/srl_classics_center"
android:contentDescription="@android:string/untitled"
tools:tint="#666666"
tools:src="@android:drawable/stat_notify_sync"/>
<LinearLayout
android:id="@+id/srl_classics_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_centerInParent="true">
<TextView
android:id="@+id/srl_classics_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:textColor="#666666"
android:textSize="15sp"
android:text="@string/srl_header_pulling"/>
<TextView
android:id="@+id/srl_classics_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:textColor="#7c7c7c"
android:textSize="12sp"
android:text="@string/srl_header_update"/>
</LinearLayout>
</merge>
因为涉及到自定义View,所以配置了自定义属性
<declare-styleable name="ClassicsHeader">
<attr name="srlClassicsSpinnerStyle"/>
<attr name="srlPrimaryColor"/>
<attr name="srlAccentColor"/>
<attr name="srlFinishDuration"/>
<attr name="srlDrawableArrow"/>
<attr name="srlDrawableProgress"/>
<attr name="srlDrawableMarginRight"/>
<attr name="srlDrawableSize"/>
<attr name="srlDrawableArrowSize"/>
<attr name="srlDrawableProgressSize"/>
<attr name="srlTextSizeTitle"/>
<attr name="srlTextSizeTime"/>
<attr format="dimension" name="srlTextTimeMarginTop"/>
<attr format="boolean" name="srlEnableLastTime"/>
<attr name="srlTextPulling"/>
<attr name="srlTextLoading"/>
<attr name="srlTextRelease"/>
<attr name="srlTextFinish"/>
<attr name="srlTextFailed"/>
<attr name="srlTextUpdate"/>
<attr name="srlTextSecondary"/>
<attr name="srlTextRefreshing"/>
</declare-styleable>
加载自定义属性。
public static String REFRESH_HEADER_PULLING = null;//"下拉可以刷新";
public static String REFRESH_HEADER_REFRESHING = null;//"正在刷新...";
public static String REFRESH_HEADER_LOADING = null;//"正在加载...";
public static String REFRESH_HEADER_RELEASE = null;//"释放立即刷新";
public static String REFRESH_HEADER_FINISH = null;//"刷新完成";
public static String REFRESH_HEADER_FAILED = null;//"刷新失败";
public static String REFRESH_HEADER_UPDATE = null;//"上次更新 M-d HH:mm";
public static String REFRESH_HEADER_SECONDARY = null;//"释放进入二楼";
// public static String REFRESH_HEADER_UPDATE = "'Last update' M-d HH:mm";
protected String KEY_LAST_UPDATE_TIME = "LAST_UPDATE_TIME";
protected Date mLastTime;
protected TextView mLastUpdateText;
protected SharedPreferences mShared;
protected DateFormat mLastUpdateFormat;
protected boolean mEnableLastTime = true;
protected String mTextPulling;//"下拉可以刷新";
protected String mTextRefreshing;//"正在刷新...";
protected String mTextLoading;//"正在加载...";
protected String mTextRelease;//"释放立即刷新";
protected String mTextFinish;//"刷新完成";
protected String mTextFailed;//"刷新失败";
protected String mTextUpdate;//"上次更新 M-d HH:mm";
protected String mTextSecondary;//"释放进入二楼";
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClassicsHeader);
RelativeLayout.LayoutParams lpArrow = (RelativeLayout.LayoutParams) arrowView.getLayoutParams();
RelativeLayout.LayoutParams lpProgress = (RelativeLayout.LayoutParams) progressView.getLayoutParams();
LinearLayout.LayoutParams lpUpdateText = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
lpUpdateText.topMargin = ta.getDimensionPixelSize(R.styleable.ClassicsHeader_srlTextTimeMarginTop, SmartUtil.dp2px(0));
lpProgress.rightMargin = ta.getDimensionPixelSize(R.styleable.ClassicsHeader_srlDrawableMarginRight, SmartUtil.dp2px(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)];
if (ta.hasValue(R.styleable.ClassicsHeader_srlDrawableArrow)) {
mArrowView.setImageDrawable(ta.getDrawable(R.styleable.ClassicsHeader_srlDrawableArrow));
} else if (mArrowView.getDrawable() == null){
mArrowDrawable = new ArrowDrawable();
mArrowDrawable.setColor(0xff666666);
mArrowView.setImageDrawable(mArrowDrawable);
}
if (ta.hasValue(R.styleable.ClassicsHeader_srlDrawableProgress)) {
mProgressView.setImageDrawable(ta.getDrawable(R.styleable.ClassicsHeader_srlDrawableProgress));
} else if (mProgressView.getDrawable() == null) {
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, SmartUtil.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, SmartUtil.dp2px(12)));
// } else {
// mLastUpdateText.setTextSize(12);
}
if (ta.hasValue(R.styleable.ClassicsHeader_srlPrimaryColor)) {
super.setPrimaryColor(ta.getColor(R.styleable.ClassicsHeader_srlPrimaryColor, 0));
}
if (ta.hasValue(R.styleable.ClassicsHeader_srlAccentColor)) {
setAccentColor(ta.getColor(R.styleable.ClassicsHeader_srlAccentColor, 0));
}
if(ta.hasValue(R.styleable.ClassicsHeader_srlTextPulling)){
mTextPulling = ta.getString(R.styleable.ClassicsHeader_srlTextPulling);
} else if(REFRESH_HEADER_PULLING != null) {
mTextPulling = REFRESH_HEADER_PULLING;
} else {
mTextPulling = context.getString(R.string.srl_header_pulling);
}
if(ta.hasValue(R.styleable.ClassicsHeader_srlTextLoading)){
mTextLoading = ta.getString(R.styleable.ClassicsHeader_srlTextLoading);
} else if(REFRESH_HEADER_LOADING != null) {
mTextLoading = REFRESH_HEADER_LOADING;
} else {
mTextLoading = context.getString(R.string.srl_header_loading);
}
if(ta.hasValue(R.styleable.ClassicsHeader_srlTextRelease)){
mTextRelease = ta.getString(R.styleable.ClassicsHeader_srlTextRelease);
} else if(REFRESH_HEADER_RELEASE != null) {
mTextRelease = REFRESH_HEADER_RELEASE;
} else {
mTextRelease = context.getString(R.string.srl_header_release);
}
if(ta.hasValue(R.styleable.ClassicsHeader_srlTextFinish)){
mTextFinish = ta.getString(R.styleable.ClassicsHeader_srlTextFinish);
} else if(REFRESH_HEADER_FINISH != null) {
mTextFinish = REFRESH_HEADER_FINISH;
} else {
mTextFinish = context.getString(R.string.srl_header_finish);
}
if(ta.hasValue(R.styleable.ClassicsHeader_srlTextFailed)){
mTextFailed = ta.getString(R.styleable.ClassicsHeader_srlTextFailed);
} else if(REFRESH_HEADER_FAILED != null) {
mTextFailed = REFRESH_HEADER_FAILED;
} else {
mTextFailed = context.getString(R.string.srl_header_failed);
}
if(ta.hasValue(R.styleable.ClassicsHeader_srlTextSecondary)){
mTextSecondary = ta.getString(R.styleable.ClassicsHeader_srlTextSecondary);
} else if(REFRESH_HEADER_SECONDARY != null) {
mTextSecondary = REFRESH_HEADER_SECONDARY;
} else {
mTextSecondary = context.getString(R.string.srl_header_secondary);
}
if(ta.hasValue(R.styleable.ClassicsHeader_srlTextRefreshing)){
mTextRefreshing = ta.getString(R.styleable.ClassicsHeader_srlTextRefreshing);
} else if(REFRESH_HEADER_REFRESHING != null) {
mTextRefreshing = REFRESH_HEADER_REFRESHING;
} else {
mTextRefreshing = context.getString(R.string.srl_header_refreshing);
}
if(ta.hasValue(R.styleable.ClassicsHeader_srlTextUpdate)){
mTextUpdate = ta.getString(R.styleable.ClassicsHeader_srlTextUpdate);
} else if(REFRESH_HEADER_UPDATE != null) {
mTextUpdate = REFRESH_HEADER_UPDATE;
} else {
mTextUpdate = context.getString(R.string.srl_header_update);
}
mLastUpdateFormat = new SimpleDateFormat(mTextUpdate, Locale.getDefault());
ta.recycle();
初始化自定义下拉View逻辑。
progressView.animate().setInterpolator(null);
updateView.setVisibility(mEnableLastTime ? VISIBLE : GONE);
mTitleText.setText(thisView.isInEditMode() ? mTextRefreshing : mTextPulling);
if (thisView.isInEditMode()) {
arrowView.setVisibility(GONE);
} else {
progressView.setVisibility(GONE);
}
try {//try 不能删除-否则会出现兼容性问题
if (context instanceof FragmentActivity) {
FragmentManager manager = ((FragmentActivity) context).getSupportFragmentManager();
if (manager != null) {
@SuppressLint("RestrictedApi")
List<Fragment> fragments = manager.getFragments();
if (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())));
public ClassicsHeader setLastUpdateTime(Date time) {
final View thisView = this;
mLastTime = time;
mLastUpdateText.setText(mLastUpdateFormat.format(time));
if (mShared != null && !thisView.isInEditMode()) {
mShared.edit().putLong(KEY_LAST_UPDATE_TIME, time.getTime()).apply();
}
return this;
}
结合自定义View,处理下拉逻辑。
@Override
public int onFinish(@NonNull RefreshLayout layout, boolean success) {
if (success) {
mTitleText.setText(mTextFinish);
if (mLastTime != null) {
setLastUpdateTime(new Date());
}
} else {
mTitleText.setText(mTextFailed);
}
return super.onFinish(layout, success);//延迟500毫秒之后再弹回
}
@Override
public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
final View arrowView = mArrowView;
final View updateView = mLastUpdateText;
switch (newState) {
case None:
updateView.setVisibility(mEnableLastTime ? VISIBLE : GONE);
case PullDownToRefresh:
mTitleText.setText(mTextPulling);
arrowView.setVisibility(VISIBLE);
arrowView.animate().rotation(0);
break;
case Refreshing:
case RefreshReleased:
mTitleText.setText(mTextRefreshing);
arrowView.setVisibility(GONE);
break;
case ReleaseToRefresh:
mTitleText.setText(mTextRelease);
arrowView.animate().rotation(180);
break;
case ReleaseToTwoLevel:
mTitleText.setText(mTextSecondary);
arrowView.animate().rotation(0);
break;
case Loading:
arrowView.setVisibility(GONE);
updateView.setVisibility(mEnableLastTime ? INVISIBLE : GONE);
mTitleText.setText(mTextLoading);
break;
}
}
上拉加载
类似下拉刷新,上拉加载也是继承了ClassicsAbstract,并实现了RefreshFooter。
public class ClassicsFooter extends ClassicsAbstract<ClassicsFooter> implements RefreshFooter {}
RefreshFooter也继承了RefreshComponent接口,不过多了一个功能设置没有更多数据的提示。
public interface RefreshFooter extends RefreshComponent {
/**
* 【仅限框架内调用】设置数据全部加载完成,将不能再次触发加载功能
* @param noMoreData 是否有更多数据
* @return true 支持全部加载完成的状态显示 false 不支持
*/
boolean setNoMoreData(boolean noMoreData);
}
加载自定义布局。
public ClassicsFooter(Context context) {
this(context, null);
}
public ClassicsFooter(Context context, AttributeSet attrs) {
super(context, attrs, 0);
View.inflate(context, R.layout.srl_classics_footer, this);
final View thisView = this;
final View arrowView = mArrowView = thisView.findViewById(R.id.srl_classics_arrow);
final View progressView = mProgressView = thisView.findViewById(R.id.srl_classics_progress);
mTitleText = thisView.findViewById(R.id.srl_classics_title);
}
自定义布局。
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:paddingTop="20dp"
tools:paddingBottom="20dp"
tools:background="#eee"
tools:parentTag="android.widget.RelativeLayout">
<ImageView
android:id="@+id/srl_classics_arrow"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/srl_classics_title"
android:layout_toStartOf="@+id/srl_classics_title"
android:contentDescription="@android:string/untitled"
/>
<ImageView
android:id="@+id/srl_classics_progress"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/srl_classics_title"
android:layout_toStartOf="@+id/srl_classics_title"
android:contentDescription="@android:string/untitled"
tools:tint="#666"
tools:src="@android:drawable/stat_notify_sync"/>
<TextView
android:id="@+id/srl_classics_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:maxLines="1"
android:textColor="#666666"
android:textSize="15sp"
android:text="@string/srl_footer_pulling"/>
</merge>
加载自定义属性。
public static String REFRESH_FOOTER_PULLING = null;//"上拉加载更多";
public static String REFRESH_FOOTER_RELEASE = null;//"释放立即加载";
public static String REFRESH_FOOTER_LOADING = null;//"正在加载...";
public static String REFRESH_FOOTER_REFRESHING = null;//"正在刷新...";
public static String REFRESH_FOOTER_FINISH = null;//"加载完成";
public static String REFRESH_FOOTER_FAILED = null;//"加载失败";
public static String REFRESH_FOOTER_NOTHING = null;//"没有更多数据了";
protected String mTextPulling;//"上拉加载更多";
protected String mTextRelease;//"释放立即加载";
protected String mTextLoading;//"正在加载...";
protected String mTextRefreshing;//"正在刷新...";
protected String mTextFinish;//"加载完成";
protected String mTextFailed;//"加载失败";
protected String mTextNothing;//"没有更多数据了";
protected boolean mNoMoreData = false;
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClassicsFooter);
LayoutParams lpArrow = (LayoutParams) arrowView.getLayoutParams();
LayoutParams lpProgress = (LayoutParams) progressView.getLayoutParams();
lpProgress.rightMargin = ta.getDimensionPixelSize(R.styleable.ClassicsFooter_srlDrawableMarginRight, SmartUtil.dp2px(20));
lpArrow.rightMargin = lpProgress.rightMargin;
lpArrow.width = ta.getLayoutDimension(R.styleable.ClassicsFooter_srlDrawableArrowSize, lpArrow.width);
lpArrow.height = ta.getLayoutDimension(R.styleable.ClassicsFooter_srlDrawableArrowSize, lpArrow.height);
lpProgress.width = ta.getLayoutDimension(R.styleable.ClassicsFooter_srlDrawableProgressSize, lpProgress.width);
lpProgress.height = ta.getLayoutDimension(R.styleable.ClassicsFooter_srlDrawableProgressSize, lpProgress.height);
lpArrow.width = ta.getLayoutDimension(R.styleable.ClassicsFooter_srlDrawableSize, lpArrow.width);
lpArrow.height = ta.getLayoutDimension(R.styleable.ClassicsFooter_srlDrawableSize, lpArrow.height);
lpProgress.width = ta.getLayoutDimension(R.styleable.ClassicsFooter_srlDrawableSize, lpProgress.width);
lpProgress.height = ta.getLayoutDimension(R.styleable.ClassicsFooter_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 if (mArrowView.getDrawable() == null) {
mArrowDrawable = new ArrowDrawable();
mArrowDrawable.setColor(0xff666666);
mArrowView.setImageDrawable(mArrowDrawable);
}
if (ta.hasValue(R.styleable.ClassicsFooter_srlDrawableProgress)) {
mProgressView.setImageDrawable(ta.getDrawable(R.styleable.ClassicsFooter_srlDrawableProgress));
} else if (mProgressView.getDrawable() == null) {
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, SmartUtil.dp2px(16)));
}
if (ta.hasValue(R.styleable.ClassicsFooter_srlPrimaryColor)) {
super.setPrimaryColor(ta.getColor(R.styleable.ClassicsFooter_srlPrimaryColor, 0));
}
if (ta.hasValue(R.styleable.ClassicsFooter_srlAccentColor)) {
super.setAccentColor(ta.getColor(R.styleable.ClassicsFooter_srlAccentColor, 0));
}
if(ta.hasValue(R.styleable.ClassicsFooter_srlTextPulling)){
mTextPulling = ta.getString(R.styleable.ClassicsFooter_srlTextPulling);
} else if(REFRESH_FOOTER_PULLING != null) {
mTextPulling = REFRESH_FOOTER_PULLING;
} else {
mTextPulling = context.getString(R.string.srl_footer_pulling);
}
if(ta.hasValue(R.styleable.ClassicsFooter_srlTextRelease)){
mTextRelease = ta.getString(R.styleable.ClassicsFooter_srlTextRelease);
} else if(REFRESH_FOOTER_RELEASE != null) {
mTextRelease = REFRESH_FOOTER_RELEASE;
} else {
mTextRelease = context.getString(R.string.srl_footer_release);
}
if(ta.hasValue(R.styleable.ClassicsFooter_srlTextLoading)){
mTextLoading = ta.getString(R.styleable.ClassicsFooter_srlTextLoading);
} else if(REFRESH_FOOTER_LOADING != null) {
mTextLoading = REFRESH_FOOTER_LOADING;
} else {
mTextLoading = context.getString(R.string.srl_footer_loading);
}
if(ta.hasValue(R.styleable.ClassicsFooter_srlTextRefreshing)){
mTextRefreshing = ta.getString(R.styleable.ClassicsFooter_srlTextRefreshing);
} else if(REFRESH_FOOTER_REFRESHING != null) {
mTextRefreshing = REFRESH_FOOTER_REFRESHING;
} else {
mTextRefreshing = context.getString(R.string.srl_footer_refreshing);
}
if(ta.hasValue(R.styleable.ClassicsFooter_srlTextFinish)){
mTextFinish = ta.getString(R.styleable.ClassicsFooter_srlTextFinish);
} else if(REFRESH_FOOTER_FINISH != null) {
mTextFinish = REFRESH_FOOTER_FINISH;
} else {
mTextFinish = context.getString(R.string.srl_footer_finish);
}
if(ta.hasValue(R.styleable.ClassicsFooter_srlTextFailed)){
mTextFailed = ta.getString(R.styleable.ClassicsFooter_srlTextFailed);
} else if(REFRESH_FOOTER_FAILED != null) {
mTextFailed = REFRESH_FOOTER_FAILED;
} else {
mTextFailed = context.getString(R.string.srl_footer_failed);
}
if(ta.hasValue(R.styleable.ClassicsFooter_srlTextNothing)){
mTextNothing = ta.getString(R.styleable.ClassicsFooter_srlTextNothing);
} else if(REFRESH_FOOTER_NOTHING != null) {
mTextNothing = REFRESH_FOOTER_NOTHING;
} else {
mTextNothing = context.getString(R.string.srl_footer_nothing);
}
ta.recycle();
自定义属性。
<declare-styleable name="ClassicsFooter">
<attr name="srlClassicsSpinnerStyle"/>
<attr name="srlPrimaryColor"/>
<attr name="srlAccentColor"/>
<attr name="srlFinishDuration"/>
<attr name="srlTextSizeTitle"/>
<attr name="srlDrawableArrow"/>
<attr name="srlDrawableProgress"/>
<attr name="srlDrawableMarginRight"/>
<attr name="srlDrawableSize"/>
<attr name="srlDrawableArrowSize"/>
<attr name="srlDrawableProgressSize"/>
<attr name="srlTextPulling"/>
<attr name="srlTextRelease"/>
<attr name="srlTextLoading"/>
<attr name="srlTextRefreshing"/>
<attr name="srlTextFinish"/>
<attr name="srlTextFailed"/>
<attr name="srlTextNothing"/>
</declare-styleable>
处理自定义View属性。
progressView.animate().setInterpolator(null);
mTitleText.setText(thisView.isInEditMode() ? mTextLoading : mTextPulling);
if (thisView.isInEditMode()) {
arrowView.setVisibility(GONE);
} else {
progressView.setVisibility(GONE);
}
根据自己的下拉刷新处理业务逻辑。
@Override
public int onFinish(@NonNull RefreshLayout layout, boolean success) {
/*
* 2020-5-15 修复BUG
* https://github.com/scwang90/SmartRefreshLayout/issues/1003
* 修复 没有更多数据之后 loading 还在显示问题
*/
super.onFinish(layout, success);
if (!mNoMoreData) {
mTitleText.setText(success ? mTextFinish : mTextFailed);
return mFinishDuration;
}
return 0;
}
/**
* ClassicsFooter 在(SpinnerStyle.FixedBehind)时才有主题色
*/
@Override@Deprecated
public void setPrimaryColors(@ColorInt int ... colors) {
if (mSpinnerStyle == SpinnerStyle.FixedBehind) {
super.setPrimaryColors(colors);
}
}
/**
* 设置数据全部加载完成,将不能再次触发加载功能
*/
@Override
public boolean setNoMoreData(boolean noMoreData) {
if (mNoMoreData != noMoreData) {
mNoMoreData = noMoreData;
final View arrowView = mArrowView;
if (noMoreData) {
mTitleText.setText(mTextNothing);
arrowView.setVisibility(GONE);
} else {
mTitleText.setText(mTextPulling);
arrowView.setVisibility(VISIBLE);
}
}
return true;
}
@Override
public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
final View arrowView = mArrowView;
if (!mNoMoreData) {
switch (newState) {
case None:
arrowView.setVisibility(VISIBLE);
case PullUpToLoad:
mTitleText.setText(mTextPulling);
arrowView.animate().rotation(180);
break;
case Loading:
case LoadReleased:
arrowView.setVisibility(GONE);
mTitleText.setText(mTextLoading);
break;
case ReleaseToLoad:
mTitleText.setText(mTextRelease);
arrowView.animate().rotation(0);
break;
case Refreshing:
mTitleText.setText(mTextRefreshing);
arrowView.setVisibility(GONE);
break;
}
}
}