Commit 96334b6b by 郑娜伟

添加 sobotwidget 控件库

parent 6a1bc21b
/build
\ No newline at end of file
plugins {
id 'com.android.library'
}
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 14
}
}
dependencies {
api fileTree(include: ['*.jar'], dir: 'libs')
compileOnly 'com.android.support:appcompat-v7:28.0.0'
compileOnly 'com.android.support:recyclerview-v7:28.0.0'
}
//添加发布到mavenCentral脚本
apply from: './sobot-widget-publish-mavencentral.gradle'
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# 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
\ No newline at end of file
apply plugin: 'maven-publish'
apply plugin: 'signing'
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.source
exclude "**/R.class"
exclude "**/BuildConfig.class"
}
ext {
PUBLISH_GROUP_ID = "com.sobot.library" //项目包名
PUBLISH_ARTIFACT_ID = 'widget' //项目名
// PUBLISH_ARTIFACT_ID = 'widget_x' //项目名
PUBLISH_VERSION = '0.1' //版本号
}
ext["signing.keyId"] = ''
ext["signing.password"] = ''
ext["signing.secretKeyRingFile"] = ''
ext["ossrhUsername"] = ''
ext["ossrhPassword"] = ''
File secretPropsFile = project.rootProject.file('local.properties')
if (secretPropsFile.exists()) {
println "Found secret props file, loading props"
Properties p = new Properties()
p.load(new FileInputStream(secretPropsFile))
p.each { name, value ->
ext[name] = value
}
} else {
println "No props file, loading env vars"
}
publishing {
publications {
release(MavenPublication) {
// The coordinates of the library, being set from variables that
// we'll set up in a moment
groupId PUBLISH_GROUP_ID
artifactId PUBLISH_ARTIFACT_ID
version PUBLISH_VERSION
// Two artifacts, the `aar` and the sources
artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
artifact androidSourcesJar
// Self-explanatory metadata for the most part
pom {
name = PUBLISH_ARTIFACT_ID
description = 'sobotcommon'
// If your project has a dedicated site, use its URL here
url = 'http://code.zhichidata.com/sobot_android/Sobot_module_Dev'
licenses {
license {
//协议类型,一般默认Apache License2.0的话不用改:
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id = 'app_dev@sobot.com'
name = 'zhichi'
email = 'app_dev@sobot.com'
}
}
// Version control info, if you're using GitHub, follow the format as seen here
scm {
//修改成你的Git地址:
connection = 'git@code.zhichidata.com:sobot_android/Sobot_module_Dev.git'
developerConnection = 'git@code.zhichidata.com:sobot_android/Sobot_module_Dev.git'
//分支地址:
url = 'http://code.zhichidata.com/sobot_android/Sobot_module_Dev/master'
}
// A slightly hacky fix so that your POM will include any transitive dependencies
// that your library builds upon
// withXml {
// def dependenciesNode = asNode().appendNode('dependencies')
//
// project.configurations.implementation.allDependencies.each {
// def dependencyNode = dependenciesNode.appendNode('dependency')
// dependencyNode.appendNode('groupId', it.group)
// dependencyNode.appendNode('artifactId', it.name)
// dependencyNode.appendNode('version', it.version)
// }
// }
withXml {
def dependenciesNode = asNode().appendNode('dependencies')
//跳过“unspecified”的依赖项
//避免出现问题:
//Execution failed for task ':sample:checkDebugAarMetadata'.
//> Could not resolve all files for configuration ':sample:debugRuntimeClasspath'.
// > Could not find :unspecified:.
// Required by:
project.configurations.implementation.allDependencies.each {
if (it.name != 'unspecified') {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
}
}
}
}
}
}
repositories {
// The repository to publish to, Sonatype/MavenCentral
maven {
// This is an arbitrary name, you may also use "mavencentral" or
// any other name that's descriptive for you
//项目名称
name = "sobotwidget"
def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
// You only need this if you want to publish snapshots, otherwise just set the URL
// to the release repo directly
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
// The username and password we've fetched earlier
credentials {
username ossrhUsername
password ossrhPassword
}
}
}
}
signing {
sign publishing.publications
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.sobot.widget">
<application>
</application>
</manifest>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
package com.sobot.widget.refresh.layout.api;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import android.view.View;
import com.sobot.widget.refresh.layout.constant.SpinnerStyle;
import com.sobot.widget.refresh.layout.listener.OnStateChangedListener;
import static android.support.annotation.RestrictTo.Scope.LIBRARY;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static android.support.annotation.RestrictTo.Scope.SUBCLASSES;
/**
* 刷新内部组件
* Created by scwang on 2017/5/26.
*/
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();
}
package com.sobot.widget.refresh.layout.api;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.support.annotation.NonNull;
import android.view.MotionEvent;
import android.view.View;
import com.sobot.widget.refresh.layout.listener.ScrollBoundaryDecider;
/**
* 刷新内容组件
* Created by scwang on 2017/5/26.
*/
public interface RefreshContent {
@NonNull
View getView();
@NonNull
View getScrollableView();
void onActionDown(MotionEvent e);
void setUpComponent(RefreshKernel kernel, View fixedHeader, View fixedFooter);
void setScrollBoundaryDecider(ScrollBoundaryDecider boundary);
void setEnableLoadMoreWhenContentNotFull(boolean enable);
void moveSpinner(int spinner, int headerTranslationViewId, int footerTranslationViewId);
boolean canRefresh();
boolean canLoadMore();
AnimatorUpdateListener scrollContentWhenFinished(int spinner);
}
package com.sobot.widget.refresh.layout.api;
import android.support.annotation.RestrictTo;
import static android.support.annotation.RestrictTo.Scope.LIBRARY;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static android.support.annotation.RestrictTo.Scope.SUBCLASSES;
/**
* 刷新底部
* Created by scwang on 2017/5/26.
*/
public interface RefreshFooter extends RefreshComponent {
/**
* 【仅限框架内调用】设置数据全部加载完成,将不能再次触发加载功能
* @param noMoreData 是否有更多数据
* @return true 支持全部加载完成的状态显示 false 不支持
*/
@RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES})
boolean setNoMoreData(boolean noMoreData);
}
package com.sobot.widget.refresh.layout.api;
/**
* 刷新头部
* Created by scwang on 2017/5/26.
*/
public interface RefreshHeader extends RefreshComponent {
}
package com.sobot.widget.refresh.layout.api;
import android.animation.ValueAnimator;
import android.support.annotation.NonNull;
import com.sobot.widget.refresh.layout.constant.RefreshState;
/**
* 刷新布局核心功能接口
* 为功能复杂的 Header 或者 Footer 开放的接口
* Created by scwang on 2017/5/26.
*/
@SuppressWarnings({"unused", "UnusedReturnValue", "SameParameterValue"})
public interface RefreshKernel {
@NonNull
RefreshLayout getRefreshLayout();
@NonNull
RefreshContent getRefreshContent();
RefreshKernel setState(@NonNull RefreshState state);
//<editor-fold desc="视图位移 Spinner">
/**
* 开始执行二极刷新
* @param open 是否展开
* @return RefreshKernel
*/
RefreshKernel startTwoLevel(boolean open);
/**
* 结束关闭二极刷新
* @return RefreshKernel
*/
RefreshKernel finishTwoLevel();
/**
* 移动视图到指定位置
* moveSpinner 的取名来自 谷歌官方的 {@link android.support.v4.widget.SwipeRefreshLayout}
* @param spinner 位置 (px)
* @param isDragging true 手指正在拖动 false 回弹动画执行
* @return RefreshKernel
*/
RefreshKernel moveSpinner(int spinner, boolean isDragging);
/**
* 执行动画使视图位移到指定的 位置
* moveSpinner 的取名来自 谷歌官方的 {@link android.support.v4.widget.SwipeRefreshLayout}
* @param endSpinner 指定的结束位置 (px)
* @return ValueAnimator 如果没有执行动画 null
*/
ValueAnimator animSpinner(int endSpinner);
//</editor-fold>
//<editor-fold desc="请求事件">
/**
* 指定在下拉时候为 Header 或 Footer 绘制背景
* @param internal Header Footer 调用时传 this
* @param backgroundColor 背景颜色
* @return RefreshKernel
*/
RefreshKernel requestDrawBackgroundFor(@NonNull RefreshComponent internal, int backgroundColor);
/**
* 请求事件
* @param internal Header Footer 调用时传 this
* @param request 请求
* @return RefreshKernel
*/
RefreshKernel requestNeedTouchEventFor(@NonNull RefreshComponent internal, boolean request);
/**
* 请求设置默认内容滚动设置
* @param internal Header Footer 调用时传 this
* @param translation 移动
* @return RefreshKernel
*/
RefreshKernel requestDefaultTranslationContentFor(@NonNull RefreshComponent internal, boolean translation);
/**
* 请求重新测量 headerHeight 或 footerHeight , 要求 height 高度为 WRAP_CONTENT
* @param internal Header Footer 调用时传 this
* @return RefreshKernel
*/
RefreshKernel requestRemeasureHeightFor(@NonNull RefreshComponent internal);
/**
* 设置二楼回弹时长
* @param duration 二楼回弹时长
* @return RefreshKernel
*/
RefreshKernel requestFloorDuration(int duration);
/**
* 设置二楼底部上划关闭所占高度的比率
* @return RefreshKernel
*/
RefreshKernel requestFloorBottomPullUpToCloseRate(float rate);
//</editor-fold>
}
package com.sobot.widget.refresh.layout.api;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.FloatRange;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import com.sobot.widget.refresh.layout.constant.RefreshState;
import com.sobot.widget.refresh.layout.listener.OnLoadMoreListener;
import com.sobot.widget.refresh.layout.listener.OnMultiListener;
import com.sobot.widget.refresh.layout.listener.OnRefreshListener;
import com.sobot.widget.refresh.layout.listener.OnRefreshLoadMoreListener;
import com.sobot.widget.refresh.layout.listener.ScrollBoundaryDecider;
/**
* 刷新布局
* interface of the refresh layout
* Created by scwang on 2017/5/26.
*/
@SuppressWarnings({"UnusedReturnValue", "SameParameterValue", "unused"})
public interface RefreshLayout {
/**
* Set the Footer's height.
* 设置 Footer 的高度
* @param dp Density-independent Pixels 虚拟像素(px需要调用px2dp转换)
* @return RefreshLayout
*/
RefreshLayout setFooterHeight(float dp);
/**
* 设置 Footer 高度
* @param px 像素
* @return RefreshLayout
*/
RefreshLayout setFooterHeightPx(int px);
/**
* Set the Header's height.
* 设置 Header 高度
* @param dp Density-independent Pixels 虚拟像素(px需要调用px2dp转换)
* @return RefreshLayout
*/
RefreshLayout setHeaderHeight(float dp);
/**
* 设置 Header 高度
* @param px 像素
* @return RefreshLayout
*/
RefreshLayout setHeaderHeightPx(int px);
/**
* Set the Header's start offset(see srlHeaderInsetStart in the RepastPracticeActivity XML in demo-app for the practical application).
* 设置 Header 的起始偏移量(使用方法参考 demo-app 中的 RepastPracticeActivity xml 中的 srlHeaderInsetStart)
* @param dp Density-independent Pixels 虚拟像素(px需要调用px2dp转换)
* @return RefreshLayout
*/
RefreshLayout setHeaderInsetStart(float dp);
/**
* Set the Header's start offset(see srlHeaderInsetStart in the RepastPracticeActivity XML in demo-app for the practical application).
* 设置 Header 起始偏移量(使用方法参考 demo-app 中的 RepastPracticeActivity xml 中的 srlHeaderInsetStart)
* @param px 像素
* @return RefreshLayout
*/
RefreshLayout setHeaderInsetStartPx(int px);
/**
* Set the Footer's start offset.
* 设置 Footer 起始偏移量(用处和 setHeaderInsetStart 一样)
* @see RefreshLayout#setHeaderInsetStart(float)
* @param dp Density-independent Pixels 虚拟像素(px需要调用px2dp转换)
* @return RefreshLayout
*/
RefreshLayout setFooterInsetStart(float dp);
/**
* Set the Footer's start offset.
* 设置 Footer 起始偏移量(用处和 setFooterInsetStartPx 一样)
* @param px 像素
* @return RefreshLayout
*/
RefreshLayout setFooterInsetStartPx(int px);
/**
* Set the damping effect.
* 显示拖动高度/真实拖动高度 比率(默认0.5,阻尼效果)
* @param rate ratio = (The drag height of the view)/(The actual drag height of the finger)
* 比率 = 视图拖动高度 / 手指拖动高度
* @return RefreshLayout
*/
RefreshLayout setDragRate(@FloatRange(from = 0, to = 1) float rate);
/**
* Set the ratio of the maximum height to drag header.
* 设置下拉最大高度和Header高度的比率(将会影响可以下拉的最大高度)
* @param rate ratio = (the maximum height to drag header)/(the height of header)
* 比率 = 下拉最大高度 / Header的高度
* @return RefreshLayout
*/
RefreshLayout setHeaderMaxDragRate(@FloatRange(from = 1, to = 10) float rate);
/**
* Set the ratio of the maximum height to drag footer.
* 设置上拉最大高度和Footer高度的比率(将会影响可以上拉的最大高度)
* @param rate ratio = (the maximum height to drag footer)/(the height of footer)
* 比率 = 下拉最大高度 / Footer的高度
* @return RefreshLayout
*/
RefreshLayout setFooterMaxDragRate(@FloatRange(from = 1, to = 10) float rate);
/**
* Set the ratio at which the refresh is triggered.
* 设置 触发刷新距离 与 HeaderHeight 的比率
* @param rate 触发刷新距离 与 HeaderHeight 的比率
* @return RefreshLayout
*/
RefreshLayout setHeaderTriggerRate(@FloatRange(from = 0, to = 1.0) float rate);
/**
* Set the ratio at which the load more is triggered.
* 设置 触发加载距离 与 FooterHeight 的比率
* @param rate 触发加载距离 与 FooterHeight 的比率
* @return RefreshLayout
*/
RefreshLayout setFooterTriggerRate(@FloatRange(from = 0, to = 1.0) float rate);
/**
* Set the rebound interpolator.
* 设置回弹显示插值器 [放手时回弹动画,结束时收缩动画]
* @param interpolator 动画插值器
* @return RefreshLayout
*/
RefreshLayout setReboundInterpolator(@NonNull Interpolator interpolator);
/**
* Set the duration of the rebound animation.
* 设置回弹动画时长 [放手时回弹动画,结束时收缩动画]
* @param duration 时长
* @return RefreshLayout
*/
RefreshLayout setReboundDuration(int duration);
/**
* Set the footer of RefreshLayout.
* 设置指定的 Footer
* @param footer RefreshFooter 刷新尾巴
* @return RefreshLayout
*/
RefreshLayout setRefreshFooter(@NonNull RefreshFooter footer);
/**
* Set the footer of RefreshLayout.
* 设置指定的 Footer
* @param footer RefreshFooter 刷新尾巴
* @param width the width in px, can use MATCH_PARENT and WRAP_CONTENT.
* 宽度 可以使用 MATCH_PARENT, WRAP_CONTENT
* @param height the height in px, can use MATCH_PARENT and WRAP_CONTENT.
* 高度 可以使用 MATCH_PARENT, WRAP_CONTENT
* @return RefreshLayout
*/
RefreshLayout setRefreshFooter(@NonNull RefreshFooter footer, int width, int height);
/**
* Set the header of RefreshLayout.
* 设置指定的 Header
* @param header RefreshHeader 刷新头
* @return RefreshLayout
*/
RefreshLayout setRefreshHeader(@NonNull RefreshHeader header);
/**
* Set the header of RefreshLayout.
* 设置指定的 Header
* @param header RefreshHeader 刷新头
* @param width the width in px, can use MATCH_PARENT and WRAP_CONTENT.
* 宽度 可以使用 MATCH_PARENT, WRAP_CONTENT
* @param height the height in px, can use MATCH_PARENT and WRAP_CONTENT.
* 高度 可以使用 MATCH_PARENT, WRAP_CONTENT
* @return RefreshLayout
*/
RefreshLayout setRefreshHeader(@NonNull RefreshHeader header, int width, int height);
/**
* Set the content of RefreshLayout(Suitable for non-XML pages, not suitable for replacing empty layouts)。
* 设置指定的 Content(适用于非XML页面,不适合用替换空布局)
* @param content View 内容视图
* @return RefreshLayout
*/
RefreshLayout setRefreshContent(@NonNull View content);
/**
* Set the content of RefreshLayout(Suitable for non-XML pages, not suitable for replacing empty layouts).
* 设置指定的 Content(适用于非XML页面,不适合用替换空布局)
* @param content View 内容视图
* @param width the width in px, can use MATCH_PARENT and WRAP_CONTENT.
* 宽度 可以使用 MATCH_PARENT, WRAP_CONTENT
* @param height the height in px, can use MATCH_PARENT and WRAP_CONTENT.
* 高度 可以使用 MATCH_PARENT, WRAP_CONTENT
* @return RefreshLayout
*/
RefreshLayout setRefreshContent(@NonNull View content, int width, int height);
/**
* Whether to enable pull-down refresh (enabled by default).
* 是否启用下拉刷新(默认启用)
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableRefresh(boolean enabled);
/**
* Set whether to enable pull-up loading more (enabled by default).
* 设置是否启用上拉加载更多(默认启用)
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableLoadMore(boolean enabled);
/**
* Sets whether to listen for the list to trigger a load event when scrolling to the bottom (default true).
* 设置是否监听列表在滚动到底部时触发加载事件(默认true)
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableAutoLoadMore(boolean enabled);
/**
* Set whether to pull down the content while pulling down the header.
* 设置是否启在下拉 Header 的同时下拉内容
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableHeaderTranslationContent(boolean enabled);
/**
* Set whether to pull up the content while pulling up the header.
* 设置是否启在上拉 Footer 的同时上拉内容
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableFooterTranslationContent(boolean enabled);
/**
* Set whether to enable cross-border rebound function.
* 设置是否启用越界回弹
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableOverScrollBounce(boolean enabled);
/**
* Set whether to enable the pure scroll mode.
* 设置是否开启纯滚动模式
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnablePureScrollMode(boolean enabled);
/**
* Set whether to scroll the content to display new data after loading more complete.
* 设置是否在加载更多完成之后滚动内容显示新数据
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableScrollContentWhenLoaded(boolean enabled);
/**
* Set whether to scroll the content to display new data after the refresh is complete.
* 是否在刷新完成之后滚动内容显示新数据
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableScrollContentWhenRefreshed(boolean enabled);
/**
* Set whether to pull up and load more when the content is not full of one page.
* 设置在内容不满一页的时候,是否可以上拉加载更多
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableLoadMoreWhenContentNotFull(boolean enabled);
/**
* Set whether to enable cross-border drag (imitation iphone effect).
* 设置是否启用越界拖动(仿苹果效果)
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableOverScrollDrag(boolean enabled);
/**
* Set whether or not Footer follows the content after there is no more data.
* 设置是否在没有更多数据之后 Footer 跟随内容
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableFooterFollowWhenNoMoreData(boolean enabled);
/**
* Set whether to clip header when the Header is in the FixedBehind state.
* 设置是否在当 Header 处于 FixedBehind 状态的时候剪裁遮挡 Header
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableClipHeaderWhenFixedBehind(boolean enabled);
/**
* Set whether to clip footer when the Footer is in the FixedBehind state.
* 设置是否在当 Footer 处于 FixedBehind 状态的时候剪裁遮挡 Footer
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableClipFooterWhenFixedBehind(boolean enabled);
/**
* Setting whether nesting scrolling is enabled (default off + smart on).
* 设置是会否启用嵌套滚动功能(默认关闭+智能开启)
* @param enabled 是否启用
* @return RefreshLayout
*/
RefreshLayout setEnableNestedScroll(boolean enabled);
/**
* 设置固定在 Header 下方的视图Id,可以在 Footer 上下滚动的时候保持不跟谁滚动
* @param id 固定在头部的视图Id
* @return RefreshLayout
*/
RefreshLayout setFixedHeaderViewId(@IdRes int id);
/**
* 设置固定在 Footer 上方的视图Id,可以在 Header 上下滚动的时候保持不跟谁滚动
* @param id 固定在底部的视图Id
* @return RefreshLayout
*/
RefreshLayout setFixedFooterViewId(@IdRes int id);
/**
* 设置在 Header 上下滚动时,需要跟随滚动的视图Id,默认整个内容视图
* @param id 固定在头部的视图Id
* @return RefreshLayout
*/
RefreshLayout setHeaderTranslationViewId(@IdRes int id);
/**
* 设置在 Footer 上下滚动时,需要跟随滚动的视图Id,默认整个内容视图
* @param id 固定在头部的视图Id
* @return RefreshLayout
*/
RefreshLayout setFooterTranslationViewId(@IdRes int id);
/**
* Set whether to enable the action content view when refreshing.
* 设置是否开启在刷新时候禁止操作内容视图
* @param disable 是否禁止
* @return RefreshLayout
*/
RefreshLayout setDisableContentWhenRefresh(boolean disable);
/**
* Set whether to enable the action content view when loading.
* 设置是否开启在加载时候禁止操作内容视图
* @param disable 是否禁止
* @return RefreshLayout
*/
RefreshLayout setDisableContentWhenLoading(boolean disable);
/**
* Set refresh listener separately.
* 单独设置刷新监听器
* @param listener OnRefreshListener 刷新监听器
* @return RefreshLayout
*/
RefreshLayout setOnRefreshListener(OnRefreshListener listener);
/**
* Set load more listener separately.
* 单独设置加载监听器
* @param listener OnLoadMoreListener 加载监听器
* @return RefreshLayout
*/
RefreshLayout setOnLoadMoreListener(OnLoadMoreListener listener);
/**
* Set refresh and load listeners at the same time.
* 同时设置刷新和加载监听器
* @param listener OnRefreshLoadMoreListener 刷新加载监听器
* @return RefreshLayout
*/
RefreshLayout setOnRefreshLoadMoreListener(OnRefreshLoadMoreListener listener);
/**
* Set up a multi-function listener.
* Recommended {@link com.sobot.widget.refresh.layout.simple.SimpleMultiListener}
* 设置多功能监听器
* 建议使用 {@link com.sobot.widget.refresh.layout.simple.SimpleMultiListener}
* @param listener OnMultiPurposeListener 多功能监听器
* @return RefreshLayout
*/
RefreshLayout setOnMultiListener(OnMultiListener listener);
/**
* Set the scroll boundary Decider, Can customize when you can refresh.
* Recommended {@link com.sobot.widget.refresh.layout.simple.SimpleBoundaryDecider}
* 设置滚动边界判断器
* 建议使用 {@link com.sobot.widget.refresh.layout.simple.SimpleBoundaryDecider}
* @param boundary ScrollBoundaryDecider 判断器
* @return RefreshLayout
*/
RefreshLayout setScrollBoundaryDecider(ScrollBoundaryDecider boundary);
/**
* Set theme color int (primaryColor and accentColor).
* 设置主题颜色
* @param primaryColors ColorInt 主题颜色
* @return RefreshLayout
*/
RefreshLayout setPrimaryColors(@ColorInt int... primaryColors);
/**
* Set theme color id (primaryColor and accentColor).
* 设置主题颜色
* @param primaryColorId ColorRes 主题颜色ID
* @return RefreshLayout
*/
RefreshLayout setPrimaryColorsId(@ColorRes int... primaryColorId);
/**
* finish refresh.
* 完成刷新
* @return RefreshLayout
*/
RefreshLayout finishRefresh();
/**
* finish refresh.
* 完成刷新
* @param delayed 开始延时
* @return RefreshLayout
*/
RefreshLayout finishRefresh(int delayed);
/**
* finish refresh.
* 完成加载
* @param success 数据是否成功刷新 (会影响到上次更新时间的改变)
* @return RefreshLayout
*/
RefreshLayout finishRefresh(boolean success);
/**
* finish refresh.
* 完成刷新
* @param delayed 开始延时
* @param success 数据是否成功刷新 (会影响到上次更新时间的改变)
* @param noMoreData 是否有更多数据
* @return RefreshLayout
*/
RefreshLayout finishRefresh(int delayed, boolean success, Boolean noMoreData);
/**
* finish load more with no more data.
* 完成刷新并标记没有更多数据
* @return RefreshLayout
*/
RefreshLayout finishRefreshWithNoMoreData();
/**
* finish load more.
* 完成加载
* @return RefreshLayout
*/
RefreshLayout finishLoadMore();
/**
* finish load more.
* 完成加载
* @param delayed 开始延时
* @return RefreshLayout
*/
RefreshLayout finishLoadMore(int delayed);
/**
* finish load more.
* 完成加载
* @param success 数据是否成功
* @return RefreshLayout
*/
RefreshLayout finishLoadMore(boolean success);
/**
* finish load more.
* 完成加载
* @param delayed 开始延时
* @param success 数据是否成功
* @param noMoreData 是否有更多数据
* @return RefreshLayout
*/
RefreshLayout finishLoadMore(int delayed, boolean success, boolean noMoreData);
/**
* finish load more with no more data.
* 完成加载并标记没有更多数据
* @return RefreshLayout
*/
RefreshLayout finishLoadMoreWithNoMoreData();
/**
* Close the Header or Footer, can't replace finishRefresh and finishLoadMore.
* 关闭 Header 或者 Footer
* 注意:
* 1.closeHeaderOrFooter 任何时候任何状态都能关闭 header 和 footer
* 2.finishRefresh 和 finishLoadMore 只能在 刷新 或者 加载 的时候关闭
* @return RefreshLayout
*/
RefreshLayout closeHeaderOrFooter();
/**
* Restore the original state after finishLoadMoreWithNoMoreData.
* 设置没有更多数据的状态
* @param noMoreData 是否有更多数据
* @return RefreshLayout
* 尽量使用下面三个方法代替,他们可以让状态切换与动画结束合拍
* use {@link RefreshLayout#resetNoMoreData()}
* use {@link RefreshLayout#finishRefreshWithNoMoreData()}
* use {@link RefreshLayout#finishLoadMoreWithNoMoreData()}
*/
RefreshLayout setNoMoreData(boolean noMoreData);
/**
* Restore the original state after finishLoadMoreWithNoMoreData.
* 恢复没有更多数据的原始状态
* @return RefreshLayout
*/
RefreshLayout resetNoMoreData();
/**
* Get header of RefreshLayout
* 获取当前 Header
* @return RefreshLayout
*/
@Nullable
RefreshHeader getRefreshHeader();
/**
* Get footer of RefreshLayout
* 获取当前 Footer
* @return RefreshLayout
*/
@Nullable
RefreshFooter getRefreshFooter();
/**
* Get the current state of RefreshLayout
* 获取当前状态
* @return RefreshLayout
*/
@NonNull
RefreshState getState();
/**
* Get the ViewGroup of RefreshLayout
* 获取实体布局视图
* @return ViewGroup
*/
@NonNull
ViewGroup getLayout();
/**
* Display refresh animation and trigger refresh event.
* 显示刷新动画并且触发刷新事件
* @return true or false, Status non-compliance will fail.
* 是否成功(状态不符合会失败)
*/
boolean autoRefresh();
/**
* Display refresh animation and trigger refresh event, Delayed start.
* 显示刷新动画并且触发刷新事件,延时启动
* @param delayed 开始延时
* @return true or false, Status non-compliance will fail.
* 是否成功(状态不符合会失败)
*/
boolean autoRefresh(int delayed);
/**
* Display refresh animation without triggering events.
* 显示刷新动画,不触发事件
* @return true or false, Status non-compliance will fail.
* 是否成功(状态不符合会失败)
*/
boolean autoRefreshAnimationOnly();
/**
* Display refresh animation, Multifunction.
* 显示刷新动画并且触发刷新事件
* @param delayed 开始延时
* @param duration 拖拽动画持续时间
* @param dragRate 拉拽的高度比率
* @param animationOnly animation only 只有动画
* @return true or false, Status non-compliance will fail.
* 是否成功(状态不符合会失败)
*/
boolean autoRefresh(int delayed, int duration, float dragRate, boolean animationOnly);
/**
* Display load more animation and trigger load more event.
* 显示加载动画并且触发刷新事件
* @return true or false, Status non-compliance will fail.
* 是否成功(状态不符合会失败)
*/
boolean autoLoadMore();
/**
* Display load more animation and trigger load more event, Delayed start.
* 显示加载动画并且触发刷新事件, 延时启动
* @param delayed 开始延时
* @return true or false, Status non-compliance will fail.
* 是否成功(状态不符合会失败)
*/
boolean autoLoadMore(int delayed);
/**
* Display load more animation without triggering events.
* 显示加载动画,不触发事件
* @return true or false, Status non-compliance will fail.
* 是否成功(状态不符合会失败)
*/
boolean autoLoadMoreAnimationOnly();
/**
* Display load more animation and trigger load more event, Delayed start.
* 显示加载动画, 多功能选项
* @param delayed 开始延时
* @param duration 拖拽动画持续时间
* @param dragRate 拉拽的高度比率
* @param animationOnly 是否只是显示动画,不回调
* @return true or false, Status non-compliance will fail.
* 是否成功(状态不符合会失败)
*/
boolean autoLoadMore(int delayed, int duration, float dragRate, boolean animationOnly);
/**
* 是否正在刷新
* @return RefreshLayout
*/
boolean isRefreshing();
/**
* 是否正在加载
* @return RefreshLayout
*/
boolean isLoading();
}
package com.sobot.widget.refresh.layout.constant;
/**
* 尺寸值的定义状态,用于在值覆盖的时候决定优先级
* 越往下优先级越高
*/
@SuppressWarnings("WeakerAccess")
public class DimensionStatus {
public static final DimensionStatus DefaultUnNotify = new DimensionStatus(0,false);//默认值,但是还没通知确认
public static final DimensionStatus Default = new DimensionStatus(1,true);//默认值
public static final DimensionStatus XmlWrapUnNotify = new DimensionStatus(2,false);//Xml计算,但是还没通知确认
public static final DimensionStatus XmlWrap = new DimensionStatus(3,true);//Xml计算
public static final DimensionStatus XmlExactUnNotify = new DimensionStatus(4,false);//Xml 的view 指定,但是还没通知确认
public static final DimensionStatus XmlExact = new DimensionStatus(5,true);//Xml 的view 指定
public static final DimensionStatus XmlLayoutUnNotify = new DimensionStatus(6,false);//Xml 的layout 中指定,但是还没通知确认
public static final DimensionStatus XmlLayout = new DimensionStatus(7,true);//Xml 的layout 中指定
public static final DimensionStatus CodeExactUnNotify = new DimensionStatus(8,false);//代码指定,但是还没通知确认
public static final DimensionStatus CodeExact = new DimensionStatus(9,true);//代码指定
public static final DimensionStatus DeadLockUnNotify = new DimensionStatus(10,false);//锁死,但是还没通知确认
public static final DimensionStatus DeadLock = new DimensionStatus(10,true);//锁死
public final int ordinal;
public final boolean notified;
public static final DimensionStatus[] values = new DimensionStatus[]{
DefaultUnNotify,
Default,
XmlWrapUnNotify,
XmlWrap,
XmlExactUnNotify,
XmlExact,
XmlLayoutUnNotify,
XmlLayout,
CodeExactUnNotify,
CodeExact,
DeadLockUnNotify,
DeadLock
};
private DimensionStatus(int ordinal,boolean notified) {
this.ordinal = ordinal;
this.notified = notified;
}
/**
* 转换为未通知状态
* @return 未通知状态
*/
public DimensionStatus unNotify() {
if (notified) {
DimensionStatus prev = values[ordinal - 1];
if (!prev.notified) {
return prev;
}
return DefaultUnNotify;
}
return this;
}
/**
* 转换为通知状态
* @return 通知状态
*/
public DimensionStatus notified() {
if (!notified) {
return values[ordinal + 1];
}
return this;
}
/**
* 是否可以被新的状态替换
* @param status 新转台
* @return 小于等于
*/
public boolean canReplaceWith(DimensionStatus status) {
return ordinal < status.ordinal || ((!notified || CodeExact == this) && ordinal == status.ordinal);
}
// /**
// * 是否没有达到新的状态
// * @param status 新转台
// * @return 大于等于 gte
// */
// public boolean gteStatusWith(DimensionStatus status) {
// return ordinal() >= status.ordinal();
// }
}
\ No newline at end of file
package com.sobot.widget.refresh.layout.constant;
/**
* 刷新状态
*/
@SuppressWarnings("unused")
public enum RefreshState {
None(0,false,false,false,false,false),
PullDownToRefresh(1,true,false,false,false,false), PullUpToLoad(2,true,false,false,false,false),
PullDownCanceled(1,false,false,false,false,false), PullUpCanceled(2,false,false,false,false,false),
ReleaseToRefresh(1,true,false,false,false,true), ReleaseToLoad(2,true,false,false,false,true),
ReleaseToTwoLevel(1,true,false,false,true,true), TwoLevelReleased(1,false,false,false,true,false),
RefreshReleased(1,false,false,false,false,false), LoadReleased(2,false,false,false,false,false),
Refreshing(1,false,true,false,false,false), Loading(2,false,true,false,false,false), TwoLevel(1, false, true,false,true,false),
RefreshFinish(1,false,false,true,false,false), LoadFinish(2,false,false,true,false,false), TwoLevelFinish(1,false,false,true,true,false);
public final boolean isHeader;
public final boolean isFooter;
public final boolean isTwoLevel;// 二级刷新 ReleaseToTwoLevel TwoLevelReleased TwoLevel
public final boolean isDragging;// 正在拖动状态:PullDownToRefresh PullUpToLoad ReleaseToRefresh ReleaseToLoad ReleaseToTwoLevel
public final boolean isOpening;// 正在刷新状态:Refreshing Loading TwoLevel
public final boolean isFinishing;//正在完成状态:RefreshFinish LoadFinish TwoLevelFinish
public final boolean isReleaseToOpening;// 释放立马打开 ReleaseToRefresh ReleaseToLoad ReleaseToTwoLevel
RefreshState(int role, boolean dragging, boolean opening, boolean finishing, boolean twoLevel, boolean releaseToOpening) {
this.isHeader = role == 1;
this.isFooter = role == 2;
this.isDragging = dragging;
this.isOpening = opening;
this.isFinishing = finishing;
this.isTwoLevel = twoLevel;
this.isReleaseToOpening = releaseToOpening;
}
public RefreshState toFooter() {
if (isHeader && !isTwoLevel) {
return values()[ordinal() + 1];
}
return this;
}
public RefreshState toHeader() {
if (isFooter && !isTwoLevel) {
return values()[ordinal()-1];
}
return this;
}
}
\ No newline at end of file
package com.sobot.widget.refresh.layout.constant;
/**
* 顶部和底部的组件在拖动时候的变换方式
* Created by scwang on 2017/5/26.
*/
@SuppressWarnings("DeprecatedIsStillUsed")
public class SpinnerStyle {
public static final SpinnerStyle Translate = new SpinnerStyle(0, true, false);
/**
* Scale 下拉过程中会动态 【测量】(header)和 【布局】(layout)降低app 性能,
* 官方自带的 Header 都已经从【Scale】转向【FixedBehind】来提高性能
* 自定义可以参考官方的 【飞机】【贝塞尔】【快递】等 Header
* @deprecated use {@link SpinnerStyle#FixedBehind}
*/
@Deprecated
public static final SpinnerStyle Scale = new SpinnerStyle(1, true, true);
public static final SpinnerStyle FixedBehind = new SpinnerStyle(2, false, false);
public static final SpinnerStyle FixedFront = new SpinnerStyle(3, true, false);
public static final SpinnerStyle MatchLayout = new SpinnerStyle(4, true, false);
public static final SpinnerStyle[] values = new SpinnerStyle[] {
Translate, //平行移动 特点: HeaderView高度不会改变,
Scale, //拉伸形变 特点:在下拉和上弹(HeaderView高度改变)时候,会自动触发OnDraw事件
FixedBehind, //固定在背后 特点:HeaderView高度不会改变,
FixedFront, //固定在前面 特点:HeaderView高度不会改变,
MatchLayout//填满布局 特点:HeaderView高度不会改变,尺寸充满 RefreshLayout
};
public final int ordinal;
public final boolean front;
public final boolean scale;
protected SpinnerStyle(int ordinal, boolean front, boolean scale) {
this.ordinal = ordinal;
this.front = front;
this.scale = scale;
}
}
package com.sobot.widget.refresh.layout.drawable;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
/**
* 画笔 Drawable
* Created by scwang on 2017/6/16.
*/
public abstract class PaintDrawable extends Drawable {
protected Paint mPaint = new Paint();
protected PaintDrawable() {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
mPaint.setColor(0xffaaaaaa);
}
public void setColor(int color) {
mPaint.setColor(color);
}
@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;
}
}
package com.sobot.widget.refresh.layout.drawable;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
/**
* 旋转动画
* Created by scwang on 2017/6/16.
*/
@SuppressWarnings("WeakerAccess")
public class ProgressDrawable extends PaintDrawable implements Animatable , ValueAnimator.AnimatorUpdateListener{
protected int mWidth = 0;
protected int mHeight = 0;
protected int mProgressDegree = 0;
protected ValueAnimator mValueAnimator;
protected Path mPath = new Path();
public ProgressDrawable() {
mValueAnimator = ValueAnimator.ofInt(30, 3600);
mValueAnimator.setDuration(10000);
mValueAnimator.setInterpolator(null);
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.setRepeatMode(ValueAnimator.RESTART);
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
mProgressDegree = 30 * (value / 30);
final Drawable drawable = ProgressDrawable.this;
drawable.invalidateSelf();
}
//<editor-fold desc="Drawable">
@Override
public void draw(@NonNull Canvas canvas) {
final Drawable drawable = ProgressDrawable.this;
final Rect bounds = drawable.getBounds();
final int width = bounds.width();
final int height = bounds.height();
final float r = Math.max(1f, width / 22f);
if (mWidth != width || mHeight != height) {
mPath.reset();
mPath.addCircle(width - r, height / 2f, r, Path.Direction.CW);
mPath.addRect(width - 5 * r, height / 2f - r, width - r, height / 2f + r, Path.Direction.CW);
mPath.addCircle(width - 5 * r, height / 2f, r, Path.Direction.CW);
mWidth = width;
mHeight = height;
}
canvas.save();
canvas.rotate(mProgressDegree, (width) / 2f, (height) / 2f);
for (int i = 0; i < 12; i++) {
mPaint.setAlpha((i+5) * 0x11);
canvas.rotate(30, (width) / 2f, (height) / 2f);
canvas.drawPath(mPath, mPaint);
}
canvas.restore();
}
//</editor-fold>
@Override
public void start() {
if (!mValueAnimator.isRunning()) {
mValueAnimator.addUpdateListener(this);
mValueAnimator.start();
}
}
@Override
public void stop() {
if (mValueAnimator.isRunning()) {
Animator animator = mValueAnimator;
animator.removeAllListeners();
mValueAnimator.removeAllUpdateListeners();
mValueAnimator.cancel();
}
}
@Override
public boolean isRunning() {
return mValueAnimator.isRunning();
}
}
package com.sobot.widget.refresh.layout.footer;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import com.sobot.widget.refresh.layout.drawable.PaintDrawable;
/**
* 箭头图像
* Created by scwang on 2018/2/5.
*/
public class ArrowDrawable extends PaintDrawable {
private int mWidth = 0;
private int mHeight = 0;
private Path mPath = new Path();
@Override
public void draw(@NonNull Canvas canvas) {
final Drawable drawable = ArrowDrawable.this;
final Rect bounds = drawable.getBounds();
final int width = bounds.width();
final int height = bounds.height();
if (mWidth != width || mHeight != height) {
int lineWidth = width * 30 / 225;
mPath.reset();
float vector1 = (lineWidth * 0.70710678118654752440084436210485f);//Math.sin(Math.PI/4));
float vector2 = (lineWidth / 0.70710678118654752440084436210485f);//Math.sin(Math.PI/4));
mPath.moveTo(width / 2f, height);
mPath.lineTo(0, height / 2f);
mPath.lineTo(vector1, height / 2f - vector1);
mPath.lineTo(width / 2f - lineWidth / 2f, height - vector2 - lineWidth / 2f);
mPath.lineTo(width / 2f - lineWidth / 2f, 0);
mPath.lineTo(width / 2f + lineWidth / 2f, 0);
mPath.lineTo(width / 2f + lineWidth / 2f, height - vector2 - lineWidth / 2f);
mPath.lineTo(width - vector1, height / 2f - vector1);
mPath.lineTo(width, height / 2f);
mPath.close();
mWidth = width;
mHeight = height;
}
canvas.drawPath(mPath, mPaint);
}
}
package com.sobot.widget.refresh.layout.footer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.sobot.widget.R;
import com.sobot.widget.refresh.layout.api.RefreshComponent;
import com.sobot.widget.refresh.layout.api.RefreshKernel;
import com.sobot.widget.refresh.layout.api.RefreshLayout;
import com.sobot.widget.refresh.layout.constant.SpinnerStyle;
import com.sobot.widget.refresh.layout.drawable.PaintDrawable;
import com.sobot.widget.refresh.layout.simple.SimpleComponent;
import com.sobot.widget.refresh.layout.util.SmartUtil;
import static android.view.View.MeasureSpec.EXACTLY;
@SuppressWarnings({"unused", "UnusedReturnValue"})
public abstract class ClassicsAbstract<T extends ClassicsAbstract> extends SimpleComponent implements RefreshComponent {
public static final int ID_TEXT_TITLE = R.id.srl_classics_title;
public static final int ID_IMAGE_ARROW = R.id.srl_classics_arrow;
public static final int ID_IMAGE_PROGRESS = R.id.srl_classics_progress;
protected TextView mTitleText;
protected ImageView mArrowView;
protected ImageView mProgressView;
protected RefreshKernel mRefreshKernel;
protected PaintDrawable mArrowDrawable;
protected PaintDrawable mProgressDrawable;
protected boolean mSetAccentColor;
protected boolean mSetPrimaryColor;
protected int mBackgroundColor;
protected int mFinishDuration = 300;
protected int mPaddingTop = 20;
protected int mPaddingBottom = 20;
protected int mMinHeightOfContent = 0;
//<editor-fold desc="RelativeLayout">
public ClassicsAbstract(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mSpinnerStyle = SpinnerStyle.Translate;
mBackgroundColor = context.getResources().getColor(R.color.sobot_refresh_bg_color);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final View thisView = this;
if (mMinHeightOfContent == 0) {
mPaddingTop = thisView.getPaddingTop();
mPaddingBottom = thisView.getPaddingBottom();
if (mPaddingTop == 0 || mPaddingBottom == 0) {
int paddingLeft = thisView.getPaddingLeft();
int paddingRight = thisView.getPaddingRight();
mPaddingTop = mPaddingTop == 0 ? SmartUtil.dp2px(20) : mPaddingTop;
mPaddingBottom = mPaddingBottom == 0 ? SmartUtil.dp2px(20) : mPaddingBottom;
thisView.setPadding(paddingLeft, mPaddingTop, paddingRight, mPaddingBottom);
}
final ViewGroup thisGroup = this;
thisGroup.setClipToPadding(false);
}
if (View.MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) {
final int parentHeight = View.MeasureSpec.getSize(heightMeasureSpec);
if (parentHeight < mMinHeightOfContent) {
final int padding = (parentHeight - mMinHeightOfContent) / 2;
thisView.setPadding(thisView.getPaddingLeft(), padding, thisView.getPaddingRight(), padding);
} else {
thisView.setPadding(thisView.getPaddingLeft(), 0, thisView.getPaddingRight(), 0);
}
} else {
thisView.setPadding(thisView.getPaddingLeft(), mPaddingTop, thisView.getPaddingRight(), mPaddingBottom);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mMinHeightOfContent == 0) {
final ViewGroup thisGroup = this;
for (int i = 0; i < thisGroup.getChildCount(); i++) {
final int height = thisGroup.getChildAt(i).getMeasuredHeight();
if (mMinHeightOfContent < height) {
mMinHeightOfContent = height;
}
}
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
final View arrowView = mArrowView;
final View progressView = mProgressView;
arrowView.animate().cancel();
progressView.animate().cancel();
final Drawable drawable = mProgressView.getDrawable();
if (drawable instanceof Animatable) {
if (((Animatable) drawable).isRunning()) {
((Animatable) drawable).stop();
}
}
}
@SuppressWarnings("unchecked")
protected T self() {
return (T) this;
}
//</editor-fold>
//<editor-fold desc="RefreshHeader">
@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;
}
}
}
//</editor-fold>
//<editor-fold desc="API">
public T setProgressBitmap(Bitmap bitmap) {
mProgressDrawable = null;
mProgressView.setImageBitmap(bitmap);
return self();
}
public T setProgressDrawable(Drawable drawable) {
mProgressDrawable = null;
mProgressView.setImageDrawable(drawable);
return self();
}
public T setProgressResource(@DrawableRes int resId) {
mProgressDrawable = null;
mProgressView.setImageResource(resId);
return self();
}
public T setArrowBitmap(Bitmap bitmap) {
mArrowDrawable = null;
mArrowView.setImageBitmap(bitmap);
return self();
}
public T setArrowDrawable(Drawable drawable) {
mArrowDrawable = null;
mArrowView.setImageDrawable(drawable);
return self();
}
public T setArrowResource(@DrawableRes int resId) {
mArrowDrawable = null;
mArrowView.setImageResource(resId);
return self();
}
public T setSpinnerStyle(SpinnerStyle style) {
this.mSpinnerStyle = style;
return self();
}
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();
}
public T setPrimaryColorId(@ColorRes int colorId) {
final View thisView = this;
setPrimaryColor(ContextCompat.getColor(thisView.getContext(), colorId));
return self();
}
public T setAccentColorId(@ColorRes int colorId) {
final View thisView = this;
setAccentColor(ContextCompat.getColor(thisView.getContext(), colorId));
return self();
}
public T setFinishDuration(int delay) {
mFinishDuration = delay;
return self();
}
public T setTextSizeTitle(float size) {
mTitleText.setTextSize(size);
if (mRefreshKernel != null) {
mRefreshKernel.requestRemeasureHeightFor(this);
}
return self();
}
public T setTextSizeTitle(int unit, float size) {
mTitleText.setTextSize(unit, size);
if (mRefreshKernel != null) {
mRefreshKernel.requestRemeasureHeightFor(this);
}
return self();
}
public T setDrawableMarginRight(float dp) {
final View arrowView = mArrowView;
final View progressView = mProgressView;
ViewGroup.MarginLayoutParams lpArrow = (ViewGroup.MarginLayoutParams) arrowView.getLayoutParams();
ViewGroup.MarginLayoutParams lpProgress = (ViewGroup.MarginLayoutParams) progressView.getLayoutParams();
lpArrow.rightMargin = lpProgress.rightMargin = SmartUtil.dp2px(dp);
arrowView.setLayoutParams(lpArrow);
progressView.setLayoutParams(lpProgress);
return self();
}
public T setDrawableMarginRightPx(int px) {
ViewGroup.MarginLayoutParams lpArrow = (ViewGroup.MarginLayoutParams) mArrowView.getLayoutParams();
ViewGroup.MarginLayoutParams lpProgress = (ViewGroup.MarginLayoutParams) mProgressView.getLayoutParams();
lpArrow.rightMargin = lpProgress.rightMargin = px;
mArrowView.setLayoutParams(lpArrow);
mProgressView.setLayoutParams(lpProgress);
return self();
}
public T setDrawableSize(float dp) {
final View arrowView = mArrowView;
final View progressView = mProgressView;
ViewGroup.LayoutParams lpArrow = arrowView.getLayoutParams();
ViewGroup.LayoutParams lpProgress = progressView.getLayoutParams();
lpArrow.width = lpProgress.width = SmartUtil.dp2px(dp);
lpArrow.height = lpProgress.height = SmartUtil.dp2px(dp);
arrowView.setLayoutParams(lpArrow);
progressView.setLayoutParams(lpProgress);
return self();
}
public T 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 self();
}
public T setDrawableArrowSize(float dp) {
final View arrowView = mArrowView;
ViewGroup.LayoutParams lpArrow = arrowView.getLayoutParams();
lpArrow.height = lpArrow.width = SmartUtil.dp2px(dp);
arrowView.setLayoutParams(lpArrow);
return self();
}
public T setDrawableArrowSizePx(int px) {
ViewGroup.LayoutParams lpArrow = mArrowView.getLayoutParams();
lpArrow.width = px;
lpArrow.height = px;
mArrowView.setLayoutParams(lpArrow);
return self();
}
public T setDrawableProgressSize(float dp) {
final View progressView = mProgressView;
ViewGroup.LayoutParams lpProgress = progressView.getLayoutParams();
lpProgress.height = lpProgress.width = SmartUtil.dp2px(dp);
progressView.setLayoutParams(lpProgress);
return self();
}
public T setDrawableProgressSizePx(int px) {
ViewGroup.LayoutParams lpProgress = mProgressView.getLayoutParams();
lpProgress.width = px;
lpProgress.height = px;
mProgressView.setLayoutParams(lpProgress);
return self();
}
// /**
// * @deprecated 使用 findViewById(ID_IMAGE_ARROW) 代替
// */
// @Deprecated
// public ImageView getArrowView() {
// return mArrowView;
// }
//
// /**
// * @deprecated 使用 findViewById(ID_IMAGE_PROGRESS) 代替
// */
// @Deprecated
// public ImageView getProgressView() {
// return mProgressView;
// }
//
// /**
// * @deprecated 使用 findViewById(ID_TEXT_TITLE) 代替
// */
// @Deprecated
// public TextView getTitleText() {
// return mTitleText;
// }
//</editor-fold>
}
package com.sobot.widget.refresh.layout.footer;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.RelativeLayout;
import com.sobot.widget.R;
import com.sobot.widget.refresh.layout.api.RefreshFooter;
import com.sobot.widget.refresh.layout.api.RefreshLayout;
import com.sobot.widget.refresh.layout.constant.RefreshState;
import com.sobot.widget.refresh.layout.constant.SpinnerStyle;
import com.sobot.widget.refresh.layout.drawable.ProgressDrawable;
import com.sobot.widget.refresh.layout.util.SmartUtil;
/**
* 经典上拉底部
* Created by scwang on 2017/5/28.
*/
@SuppressWarnings({"unused", "UnusedReturnValue"})
public class ClassicsFooter extends ClassicsAbstract<ClassicsFooter> implements RefreshFooter {
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;
//<editor-fold desc="LinearLayout">
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);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SobotClassicsFooter);
RelativeLayout.LayoutParams lpArrow = (RelativeLayout.LayoutParams) arrowView.getLayoutParams();
RelativeLayout.LayoutParams lpProgress = (RelativeLayout.LayoutParams) progressView.getLayoutParams();
lpProgress.rightMargin = ta.getDimensionPixelSize(R.styleable.SobotClassicsFooter_sobotSrlDrawableMarginRight, SmartUtil.dp2px(20));
lpArrow.rightMargin = lpProgress.rightMargin;
lpArrow.width = ta.getLayoutDimension(R.styleable.SobotClassicsFooter_sobotSrlDrawableArrowSize, lpArrow.width);
lpArrow.height = ta.getLayoutDimension(R.styleable.SobotClassicsFooter_sobotSrlDrawableArrowSize, lpArrow.height);
lpProgress.width = ta.getLayoutDimension(R.styleable.SobotClassicsFooter_sobotSrlDrawableProgressSize, lpProgress.width);
lpProgress.height = ta.getLayoutDimension(R.styleable.SobotClassicsFooter_sobotSrlDrawableProgressSize, lpProgress.height);
lpArrow.width = ta.getLayoutDimension(R.styleable.SobotClassicsFooter_sobotSrlDrawableSize, lpArrow.width);
lpArrow.height = ta.getLayoutDimension(R.styleable.SobotClassicsFooter_sobotSrlDrawableSize, lpArrow.height);
lpProgress.width = ta.getLayoutDimension(R.styleable.SobotClassicsFooter_sobotSrlDrawableSize, lpProgress.width);
lpProgress.height = ta.getLayoutDimension(R.styleable.SobotClassicsFooter_sobotSrlDrawableSize, lpProgress.height);
mFinishDuration = ta.getInt(R.styleable.SobotClassicsFooter_sobotSrlFinishDuration, mFinishDuration);
mSpinnerStyle = SpinnerStyle.values[ta.getInt(R.styleable.SobotClassicsFooter_sobotSrlClassicsSpinnerStyle, mSpinnerStyle.ordinal)];
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlDrawableArrow)) {
mArrowView.setImageDrawable(ta.getDrawable(R.styleable.SobotClassicsFooter_sobotSrlDrawableArrow));
} else if (mArrowView.getDrawable() == null) {
mArrowDrawable = new ArrowDrawable();
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlColorArrow)) {
mArrowDrawable.setColor(ta.getColor(R.styleable.SobotClassicsFooter_sobotSrlColorArrow, 0));
} else {
mArrowDrawable.setColor(this.getResources().getColor(R.color.sobot_refresh_footer_arrow_color));
}
mArrowView.setImageDrawable(mArrowDrawable);
}
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlDrawableProgress)) {
mProgressView.setImageDrawable(ta.getDrawable(R.styleable.SobotClassicsFooter_sobotSrlDrawableProgress));
} else if (mProgressView.getDrawable() == null) {
mProgressDrawable = new ProgressDrawable();
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlColorProgress)) {
mProgressDrawable.setColor(ta.getColor(R.styleable.SobotClassicsFooter_sobotSrlColorProgress, 0));
} else {
mProgressDrawable.setColor(this.getResources().getColor(R.color.sobot_refresh_footer_progress_color));
}
mProgressView.setImageDrawable(mProgressDrawable);
}
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlTextSizeTitle)) {
mTitleText.setTextSize(TypedValue.COMPLEX_UNIT_PX, ta.getDimensionPixelSize(R.styleable.SobotClassicsFooter_sobotSrlTextSizeTitle, SmartUtil.dp2px(16)));
}
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlPrimaryColor)) {
super.setPrimaryColor(ta.getColor(R.styleable.SobotClassicsFooter_sobotSrlPrimaryColor, 0));
}
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlAccentColor)) {
super.setAccentColor(ta.getColor(R.styleable.SobotClassicsFooter_sobotSrlAccentColor, 0));
}
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlTextPulling)) {
mTextPulling = ta.getString(R.styleable.SobotClassicsFooter_sobotSrlTextPulling);
} else if (REFRESH_FOOTER_PULLING != null) {
mTextPulling = REFRESH_FOOTER_PULLING;
} else {
mTextPulling = context.getString(R.string.sobot_srl_footer_pulling);
}
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlTextRelease)) {
mTextRelease = ta.getString(R.styleable.SobotClassicsFooter_sobotSrlTextRelease);
} else if (REFRESH_FOOTER_RELEASE != null) {
mTextRelease = REFRESH_FOOTER_RELEASE;
} else {
mTextRelease = context.getString(R.string.sobot_srl_footer_release);
}
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlTextLoading)) {
mTextLoading = ta.getString(R.styleable.SobotClassicsFooter_sobotSrlTextLoading);
} else if (REFRESH_FOOTER_LOADING != null) {
mTextLoading = REFRESH_FOOTER_LOADING;
} else {
mTextLoading = context.getString(R.string.sobot_srl_footer_loading);
}
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlTextRefreshing)) {
mTextRefreshing = ta.getString(R.styleable.SobotClassicsFooter_sobotSrlTextRefreshing);
} else if (REFRESH_FOOTER_REFRESHING != null) {
mTextRefreshing = REFRESH_FOOTER_REFRESHING;
} else {
mTextRefreshing = context.getString(R.string.sobot_srl_footer_refreshing);
}
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlTextFinish)) {
mTextFinish = ta.getString(R.styleable.SobotClassicsFooter_sobotSrlTextFinish);
} else if (REFRESH_FOOTER_FINISH != null) {
mTextFinish = REFRESH_FOOTER_FINISH;
} else {
mTextFinish = context.getString(R.string.sobot_srl_footer_finish);
}
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlTextFailed)) {
mTextFailed = ta.getString(R.styleable.SobotClassicsFooter_sobotSrlTextFailed);
} else if (REFRESH_FOOTER_FAILED != null) {
mTextFailed = REFRESH_FOOTER_FAILED;
} else {
mTextFailed = context.getString(R.string.sobot_srl_footer_failed);
}
if (ta.hasValue(R.styleable.SobotClassicsFooter_sobotSrlTextNothing)) {
mTextNothing = ta.getString(R.styleable.SobotClassicsFooter_sobotSrlTextNothing);
} else if (REFRESH_FOOTER_NOTHING != null) {
mTextNothing = REFRESH_FOOTER_NOTHING;
} else {
mTextNothing = context.getString(R.string.sobot_srl_footer_nothing);
}
ta.recycle();
progressView.animate().setInterpolator(null);
mTitleText.setText(thisView.isInEditMode() ? mTextLoading : mTextPulling);
if (thisView.isInEditMode()) {
arrowView.setVisibility(GONE);
} else {
progressView.setVisibility(GONE);
}
}
//</editor-fold>
//<editor-fold desc="RefreshFooter">
// @Override
// public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
// if (!mNoMoreData) {
// super.onStartAnimator(refreshLayout, height, maxDragHeight);
// }
// }
@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;
}
}
}
//</editor-fold>
}
package com.sobot.widget.refresh.layout.header;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
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.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.sobot.widget.R;
import com.sobot.widget.refresh.layout.api.RefreshHeader;
import com.sobot.widget.refresh.layout.api.RefreshLayout;
import com.sobot.widget.refresh.layout.constant.RefreshState;
import com.sobot.widget.refresh.layout.constant.SpinnerStyle;
import com.sobot.widget.refresh.layout.drawable.ProgressDrawable;
import com.sobot.widget.refresh.layout.footer.ArrowDrawable;
import com.sobot.widget.refresh.layout.footer.ClassicsAbstract;
import com.sobot.widget.refresh.layout.util.SmartUtil;
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", "UnusedReturnValue"})
public class ClassicsHeader extends ClassicsAbstract<ClassicsHeader> implements RefreshHeader {
public static final int ID_TEXT_UPDATE = R.id.srl_classics_update;
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;//"释放进入二楼";
//<editor-fold desc="RelativeLayout">
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);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SobotClassicsHeader);
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.SobotClassicsHeader_sobotSrlTextTimeMarginTop, SmartUtil.dp2px(0));
lpProgress.rightMargin = ta.getDimensionPixelSize(R.styleable.SobotClassicsHeader_sobotSrlDrawableMarginRight, SmartUtil.dp2px(20));
lpArrow.rightMargin = lpProgress.rightMargin;
lpArrow.width = ta.getLayoutDimension(R.styleable.SobotClassicsHeader_sobotSrlDrawableArrowSize, lpArrow.width);
lpArrow.height = ta.getLayoutDimension(R.styleable.SobotClassicsHeader_sobotSrlDrawableArrowSize, lpArrow.height);
lpProgress.width = ta.getLayoutDimension(R.styleable.SobotClassicsHeader_sobotSrlDrawableProgressSize, lpProgress.width);
lpProgress.height = ta.getLayoutDimension(R.styleable.SobotClassicsHeader_sobotSrlDrawableProgressSize, lpProgress.height);
lpArrow.width = ta.getLayoutDimension(R.styleable.SobotClassicsHeader_sobotSrlDrawableSize, lpArrow.width);
lpArrow.height = ta.getLayoutDimension(R.styleable.SobotClassicsHeader_sobotSrlDrawableSize, lpArrow.height);
lpProgress.width = ta.getLayoutDimension(R.styleable.SobotClassicsHeader_sobotSrlDrawableSize, lpProgress.width);
lpProgress.height = ta.getLayoutDimension(R.styleable.SobotClassicsHeader_sobotSrlDrawableSize, lpProgress.height);
mFinishDuration = ta.getInt(R.styleable.SobotClassicsHeader_sobotSrlFinishDuration, mFinishDuration);
mEnableLastTime = ta.getBoolean(R.styleable.SobotClassicsHeader_sobotSrlEnableLastTime, mEnableLastTime);
mSpinnerStyle = SpinnerStyle.values[ta.getInt(R.styleable.SobotClassicsHeader_sobotSrlClassicsSpinnerStyle, mSpinnerStyle.ordinal)];
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlDrawableArrow)) {
mArrowView.setImageDrawable(ta.getDrawable(R.styleable.SobotClassicsHeader_sobotSrlDrawableArrow));
} else if (mArrowView.getDrawable() == null) {
mArrowDrawable = new ArrowDrawable();
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlColorArrow)) {
mArrowDrawable.setColor(ta.getColor(R.styleable.SobotClassicsHeader_sobotSrlColorArrow, 0));
} else {
mArrowDrawable.setColor(this.getResources().getColor(R.color.sobot_refresh_header_arrow_color));
}
mArrowView.setImageDrawable(mArrowDrawable);
}
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlDrawableProgress)) {
mProgressView.setImageDrawable(ta.getDrawable(R.styleable.SobotClassicsHeader_sobotSrlDrawableProgress));
} else if (mProgressView.getDrawable() == null) {
mProgressDrawable = new ProgressDrawable();
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlColorProgress)) {
mProgressDrawable.setColor(ta.getColor(R.styleable.SobotClassicsHeader_sobotSrlColorProgress, 0));
} else {
mProgressDrawable.setColor(this.getResources().getColor(R.color.sobot_refresh_header_progress_color));
}
mProgressView.setImageDrawable(mProgressDrawable);
}
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlTextSizeTitle)) {
mTitleText.setTextSize(TypedValue.COMPLEX_UNIT_PX, ta.getDimensionPixelSize(R.styleable.SobotClassicsHeader_sobotSrlTextSizeTitle, SmartUtil.dp2px(16)));
// } else {
// mTitleText.setTextSize(16);
}
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlTextSizeTime)) {
mLastUpdateText.setTextSize(TypedValue.COMPLEX_UNIT_PX, ta.getDimensionPixelSize(R.styleable.SobotClassicsHeader_sobotSrlTextSizeTime, SmartUtil.dp2px(12)));
// } else {
// mLastUpdateText.setTextSize(12);
}
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlPrimaryColor)) {
super.setPrimaryColor(ta.getColor(R.styleable.SobotClassicsHeader_sobotSrlPrimaryColor, 0));
}
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlAccentColor)) {
setAccentColor(ta.getColor(R.styleable.SobotClassicsHeader_sobotSrlAccentColor, 0));
}
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlTextPulling)) {
mTextPulling = ta.getString(R.styleable.SobotClassicsHeader_sobotSrlTextPulling);
} else if (REFRESH_HEADER_PULLING != null) {
mTextPulling = REFRESH_HEADER_PULLING;
} else {
mTextPulling = context.getString(R.string.sobot_srl_header_pulling);
}
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlTextLoading)) {
mTextLoading = ta.getString(R.styleable.SobotClassicsHeader_sobotSrlTextLoading);
} else if (REFRESH_HEADER_LOADING != null) {
mTextLoading = REFRESH_HEADER_LOADING;
} else {
mTextLoading = context.getString(R.string.sobot_srl_header_loading);
}
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlTextRelease)) {
mTextRelease = ta.getString(R.styleable.SobotClassicsHeader_sobotSrlTextRelease);
} else if (REFRESH_HEADER_RELEASE != null) {
mTextRelease = REFRESH_HEADER_RELEASE;
} else {
mTextRelease = context.getString(R.string.sobot_srl_header_release);
}
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlTextFinish)) {
mTextFinish = ta.getString(R.styleable.SobotClassicsHeader_sobotSrlTextFinish);
} else if (REFRESH_HEADER_FINISH != null) {
mTextFinish = REFRESH_HEADER_FINISH;
} else {
mTextFinish = context.getString(R.string.sobot_srl_header_finish);
}
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlTextFailed)) {
mTextFailed = ta.getString(R.styleable.SobotClassicsHeader_sobotSrlTextFailed);
} else if (REFRESH_HEADER_FAILED != null) {
mTextFailed = REFRESH_HEADER_FAILED;
} else {
mTextFailed = context.getString(R.string.sobot_srl_header_failed);
}
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlTextSecondary)) {
mTextSecondary = ta.getString(R.styleable.SobotClassicsHeader_sobotSrlTextSecondary);
} else if (REFRESH_HEADER_SECONDARY != null) {
mTextSecondary = REFRESH_HEADER_SECONDARY;
} else {
mTextSecondary = context.getString(R.string.sobot_srl_header_secondary);
}
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlTextRefreshing)) {
mTextRefreshing = ta.getString(R.styleable.SobotClassicsHeader_sobotSrlTextRefreshing);
} else if (REFRESH_HEADER_REFRESHING != null) {
mTextRefreshing = REFRESH_HEADER_REFRESHING;
} else {
mTextRefreshing = context.getString(R.string.sobot_srl_header_refreshing);
}
if (ta.hasValue(R.styleable.SobotClassicsHeader_sobotSrlTextUpdate)) {
mTextUpdate = ta.getString(R.styleable.SobotClassicsHeader_sobotSrlTextUpdate);
} else if (REFRESH_HEADER_UPDATE != null) {
mTextUpdate = REFRESH_HEADER_UPDATE;
} else {
mTextUpdate = context.getString(R.string.sobot_srl_header_update);
}
mLastUpdateFormat = new SimpleDateFormat(mTextUpdate, Locale.getDefault());
ta.recycle();
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())));
}
//</editor-fold>
//<editor-fold desc="RefreshHeader">
@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;
}
}
//</editor-fold>
//<editor-fold desc="API">
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;
}
public ClassicsHeader setTimeFormat(DateFormat format) {
mLastUpdateFormat = format;
if (mLastTime != null) {
mLastUpdateText.setText(mLastUpdateFormat.format(mLastTime));
}
return this;
}
public ClassicsHeader setLastUpdateText(CharSequence text) {
mLastTime = null;
mLastUpdateText.setText(text);
return this;
}
public ClassicsHeader setAccentColor(@ColorInt int accentColor) {
mLastUpdateText.setTextColor(accentColor & 0x00ffffff | 0xcc000000);
return super.setAccentColor(accentColor);
}
public ClassicsHeader setEnableLastTime(boolean enable) {
final View updateView = mLastUpdateText;
mEnableLastTime = enable;
updateView.setVisibility(enable ? VISIBLE : GONE);
if (mRefreshKernel != null) {
mRefreshKernel.requestRemeasureHeightFor(this);
}
return this;
}
public ClassicsHeader setTextSizeTime(float size) {
mLastUpdateText.setTextSize(size);
if (mRefreshKernel != null) {
mRefreshKernel.requestRemeasureHeightFor(this);
}
return this;
}
public ClassicsHeader setTextSizeTime(int unit, float size) {
mLastUpdateText.setTextSize(unit, size);
if (mRefreshKernel != null) {
mRefreshKernel.requestRemeasureHeightFor(this);
}
return this;
}
public ClassicsHeader setTextTimeMarginTop(float dp) {
final View updateView = mLastUpdateText;
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) updateView.getLayoutParams();
lp.topMargin = SmartUtil.dp2px(dp);
updateView.setLayoutParams(lp);
return this;
}
public ClassicsHeader setTextTimeMarginTopPx(int px) {
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mLastUpdateText.getLayoutParams();
lp.topMargin = px;
mLastUpdateText.setLayoutParams(lp);
return this;
}
// /**
// * @deprecated 使用 findViewById(ID_TEXT_UPDATE) 代替
// */
// @Deprecated
// public TextView getLastUpdateText() {
// return mLastUpdateText;
// }
//</editor-fold>
}
package com.sobot.widget.refresh.layout.listener;
public interface CoordinatorLayoutListener {
void onCoordinatorUpdate(boolean enableRefresh, boolean enableLoadMore);
}
\ No newline at end of file
package com.sobot.widget.refresh.layout.listener;
import android.content.Context;
import android.support.annotation.NonNull;
import com.sobot.widget.refresh.layout.api.RefreshFooter;
import com.sobot.widget.refresh.layout.api.RefreshLayout;
/**
* 默认Footer创建器
* Created by scwang on 2018/1/26.
*/
public interface DefaultRefreshFooterCreator {
@NonNull
RefreshFooter createRefreshFooter(@NonNull Context context, @NonNull RefreshLayout layout);
}
package com.sobot.widget.refresh.layout.listener;
import android.content.Context;
import android.support.annotation.NonNull;
import com.sobot.widget.refresh.layout.api.RefreshHeader;
import com.sobot.widget.refresh.layout.api.RefreshLayout;
/**
* 默认Header创建器
* Created by scwang on 2018/1/26.
*/
public interface DefaultRefreshHeaderCreator {
@NonNull
RefreshHeader createRefreshHeader(@NonNull Context context, @NonNull RefreshLayout layout);
}
package com.sobot.widget.refresh.layout.listener;
import android.content.Context;
import android.support.annotation.NonNull;
import com.sobot.widget.refresh.layout.api.RefreshLayout;
/**
* 默认全局初始化器
* Created by scwang on 2018/5/29 0029.
*/
public interface DefaultRefreshInitializer {
void initialize(@NonNull Context context, @NonNull RefreshLayout layout);
}
package com.sobot.widget.refresh.layout.listener;
import android.support.annotation.NonNull;
import com.sobot.widget.refresh.layout.api.RefreshLayout;
/**
* 加载更多监听器
* Created by scwang on 2017/5/26.
*/
public interface OnLoadMoreListener {
void onLoadMore(@NonNull RefreshLayout refreshLayout);
}
package com.sobot.widget.refresh.layout.listener;
import com.sobot.widget.refresh.layout.api.RefreshFooter;
import com.sobot.widget.refresh.layout.api.RefreshHeader;
/**
* 多功能监听器
* Created by scwang on 2017/5/26.
*/
public interface OnMultiListener extends OnRefreshLoadMoreListener, OnStateChangedListener {
/**
* 手指拖动下拉(会连续多次调用,添加isDragging并取代之前的onPulling、onReleasing)
* @param header 头部
* @param isDragging true 手指正在拖动 false 回弹动画
* @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+maxDragHeight) / footerHeight )
* @param offset 下拉的像素偏移量 0 - offset - (footerHeight+maxDragHeight)
* @param headerHeight 高度 HeaderHeight or FooterHeight
* @param maxDragHeight 最大拖动高度
*/
void onHeaderMoving(RefreshHeader header, boolean isDragging, float percent, int offset, int headerHeight, int maxDragHeight);
void onHeaderReleased(RefreshHeader header, int headerHeight, int maxDragHeight);
void onHeaderStartAnimator(RefreshHeader header, int headerHeight, int maxDragHeight);
void onHeaderFinish(RefreshHeader header, boolean success);
/**
* 手指拖动上拉(会连续多次调用,添加isDragging并取代之前的onPulling、onReleasing)
* @param footer 尾部
* @param isDragging true 手指正在拖动 false 回弹动画
* @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+maxDragHeight) / footerHeight )
* @param offset 下拉的像素偏移量 0 - offset - (footerHeight+maxDragHeight)
* @param footerHeight 高度 HeaderHeight or FooterHeight
* @param maxDragHeight 最大拖动高度
*/
void onFooterMoving(RefreshFooter footer, boolean isDragging, float percent, int offset, int footerHeight, int maxDragHeight);
void onFooterReleased(RefreshFooter footer, int footerHeight, int maxDragHeight);
void onFooterStartAnimator(RefreshFooter footer, int footerHeight, int maxDragHeight);
void onFooterFinish(RefreshFooter footer, boolean success);
}
package com.sobot.widget.refresh.layout.listener;
import android.support.annotation.NonNull;
import com.sobot.widget.refresh.layout.api.RefreshLayout;
/**
* 刷新监听器
* Created by scwang on 2017/5/26.
*/
public interface OnRefreshListener {
void onRefresh(@NonNull RefreshLayout refreshLayout);
}
package com.sobot.widget.refresh.layout.listener;
/**
* 刷新加载组合监听器
* Created by scwang on 2017/5/26.
*/
public interface OnRefreshLoadMoreListener extends OnRefreshListener, OnLoadMoreListener {
}
package com.sobot.widget.refresh.layout.listener;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import com.sobot.widget.refresh.layout.api.RefreshLayout;
import com.sobot.widget.refresh.layout.constant.RefreshState;
import static android.support.annotation.RestrictTo.Scope.LIBRARY;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static android.support.annotation.RestrictTo.Scope.SUBCLASSES;
/**
* 刷新状态改变监听器
* Created by scwang on 2017/5/26.
*/
public interface OnStateChangedListener {
/**
* 【仅限框架内调用】状态改变事件 {@link RefreshState}
* @param refreshLayout RefreshLayout
* @param oldState 改变之前的状态
* @param newState 改变之后的状态
*/
@RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES})
void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState);
}
package com.sobot.widget.refresh.layout.listener;
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);
}
package com.sobot.widget.refresh.layout.simple;
import android.graphics.PointF;
import android.view.View;
import com.sobot.widget.refresh.layout.listener.ScrollBoundaryDecider;
import com.sobot.widget.refresh.layout.util.SmartUtil;
/**
* 滚动边界
* Created by scwang on 2017/7/8.
*/
public class SimpleBoundaryDecider implements ScrollBoundaryDecider {
//<editor-fold desc="Internal">
public PointF mActionEvent;
public ScrollBoundaryDecider boundary;
public boolean mEnableLoadMoreWhenContentNotFull = true;
//</editor-fold>
//<editor-fold desc="ScrollBoundaryDecider">
@Override
public boolean canRefresh(View content) {
if (boundary != null) {
return boundary.canRefresh(content);
}
//mActionEvent == null 时 canRefresh 不会动态递归搜索
return SmartUtil.canRefresh(content, mActionEvent);
}
@Override
public boolean canLoadMore(View content) {
if (boundary != null) {
return boundary.canLoadMore(content);
}
//mActionEvent == null 时 canLoadMore 不会动态递归搜索
return SmartUtil.canLoadMore(content, mActionEvent, mEnableLoadMoreWhenContentNotFull);
}
//</editor-fold>
}
package com.sobot.widget.refresh.layout.simple;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import com.sobot.widget.refresh.layout.SobotRefreshLayout;
import com.sobot.widget.refresh.layout.api.RefreshComponent;
import com.sobot.widget.refresh.layout.api.RefreshFooter;
import com.sobot.widget.refresh.layout.api.RefreshHeader;
import com.sobot.widget.refresh.layout.api.RefreshKernel;
import com.sobot.widget.refresh.layout.api.RefreshLayout;
import com.sobot.widget.refresh.layout.constant.RefreshState;
import com.sobot.widget.refresh.layout.constant.SpinnerStyle;
import com.sobot.widget.refresh.layout.listener.OnStateChangedListener;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
/**
* Component 初步实现
* 实现 Header 和 Footer 时,继承 ComponentAbstract 的话可以少写很多接口方法
* Created by scwang on 2018/2/6.
*/
public abstract class SimpleComponent extends RelativeLayout implements RefreshComponent {
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);
}
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) {
if (obj instanceof RefreshComponent) {
final RefreshComponent thisView = this;
return thisView.getView() == ((RefreshComponent)obj).getView();
}
return false;
}
return true;
}
@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 SobotRefreshLayout.LayoutParams) {
mSpinnerStyle = ((SobotRefreshLayout.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 SobotRefreshLayout.LayoutParams) {
kernel.requestDrawBackgroundFor(this, ((SobotRefreshLayout.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);
}
}
package com.sobot.widget.refresh.layout.simple;
import android.support.annotation.NonNull;
import com.sobot.widget.refresh.layout.api.RefreshFooter;
import com.sobot.widget.refresh.layout.api.RefreshHeader;
import com.sobot.widget.refresh.layout.api.RefreshLayout;
import com.sobot.widget.refresh.layout.constant.RefreshState;
import com.sobot.widget.refresh.layout.listener.OnMultiListener;
/**
* 多功能监听器
* Created by scwang on 2017/5/26.
*/
public class SimpleMultiListener implements OnMultiListener {
@Override
public void onHeaderMoving(RefreshHeader header, boolean isDragging, float percent, int offset, int headerHeight, int maxDragHeight) {
}
@Override
public void onHeaderReleased(RefreshHeader header, int headerHeight, int maxDragHeight) {
}
@Override
public void onHeaderStartAnimator(RefreshHeader header, int footerHeight, int maxDragHeight) {
}
@Override
public void onHeaderFinish(RefreshHeader header, boolean success) {
}
@Override
public void onFooterMoving(RefreshFooter footer, boolean isDragging, float percent, int offset, int footerHeight, int maxDragHeight) {
}
@Override
public void onFooterReleased(RefreshFooter footer, int footerHeight, int maxDragHeight) {
}
@Override
public void onFooterStartAnimator(RefreshFooter footer, int headerHeight, int maxDragHeight) {
}
@Override
public void onFooterFinish(RefreshFooter footer, boolean success) {
}
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
}
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
}
@Override
public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
}
}
package com.sobot.widget.refresh.layout.util;
import android.content.res.Resources;
import android.graphics.PointF;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.ScrollingView;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.NestedScrollView;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.webkit.WebView;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.ScrollView;
import com.sobot.widget.R;
import com.sobot.widget.refresh.layout.api.RefreshComponent;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
/**
* SmartUtil
* Created by scwang on 2018/3/5.
*/
public class SmartUtil implements Interpolator {
public static int INTERPOLATOR_VISCOUS_FLUID = 0;
public static int INTERPOLATOR_DECELERATE = 1;
private int type;
public SmartUtil(int type) {
this.type = type;
}
//<editor-fold desc="内容工具">
public 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 = View.MeasureSpec.makeMeasureSpec(p.height, View.MeasureSpec.EXACTLY);
} else {
childHeightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
}
view.measure(childWidthSpec, childHeightSpec);
return view.getMeasuredHeight();
}
public static void scrollListBy(@NonNull AbsListView listView, int y) {
if (Build.VERSION.SDK_INT >= 19) {
// Call the framework version directly
listView.scrollListBy(y);
} else if (listView instanceof ListView) {
// provide backport on earlier versions
final int firstPosition = listView.getFirstVisiblePosition();
if (firstPosition == ListView.INVALID_POSITION) {
return;
}
//noinspection UnnecessaryLocalVariable
final ViewGroup listGroup = listView;
final View firstView = listGroup.getChildAt(0);
if (firstView == null) {
return;
}
final int newTop = firstView.getTop() - y;
((ListView) listView).setSelectionFromTop(firstPosition, newTop);
} else {
listView.smoothScrollBy(y, 0);
}
}
public static boolean isScrollableView(View view) {
if (view instanceof RefreshComponent) {
return false;
}
return view instanceof AbsListView
|| view instanceof ScrollView
|| view instanceof ScrollingView
|| view instanceof WebView
|| view instanceof NestedScrollingChild;
}
public static boolean isContentView(View view) {
if (view instanceof RefreshComponent) {
return false;
}
return isScrollableView(view)
|| view instanceof ViewPager
|| view instanceof NestedScrollingParent;
}
public static void fling(View scrollableView, int velocity) {
if (scrollableView instanceof ScrollView) {
((ScrollView) scrollableView).fling(velocity);
} else if (scrollableView instanceof AbsListView) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
((AbsListView) scrollableView).fling(velocity);
}
} else if (scrollableView instanceof WebView) {
((WebView) scrollableView).flingScroll(0, velocity);
} else if (scrollableView instanceof NestedScrollView) {
((NestedScrollView) scrollableView).fling(velocity);
} else if (scrollableView instanceof RecyclerView) {
((RecyclerView) scrollableView).fling(0, velocity);
}
}
//</editor-fold>
//<editor-fold desc="滚动判断">
/**
* 判断内容是否可以刷新
* @param targetView 内容视图
* @param touch 按压事件位置
* @return 是否可以刷新
*/
public static boolean canRefresh(@NonNull View targetView, PointF touch) {
if (targetView.canScrollVertically(-1) && targetView.getVisibility() == View.VISIBLE) {
return false;
}
//touch == null 时 canRefresh 不会动态递归搜索
if (targetView instanceof ViewGroup && touch != 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, touch.x, touch.y, point)) {
Object tag = child.getTag(R.id.sobot_srl_tag);
if ("fixed".equals(tag) || "fixed-bottom".equals(tag)) {
return false;
}
touch.offset(point.x, point.y);
boolean can = canRefresh(child, touch);
touch.offset(-point.x, -point.y);
return can;
}
}
}
return true;
}
/**
* 判断内容视图是否可以加载更多
* @param targetView 内容视图
* @param touch 按压事件位置
* @param contentFull 内容是否填满页面 (未填满时,会通过canScrollUp自动判断)
* @return 是否可以刷新
*/
public static boolean canLoadMore(@NonNull View targetView, PointF touch, boolean contentFull) {
if (targetView.canScrollVertically(1) && targetView.getVisibility() == View.VISIBLE) {
return false;
}
//touch == null 时 canLoadMore 不会动态递归搜索
if (targetView instanceof ViewGroup && touch != null && !SmartUtil.isScrollableView(targetView)) {
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, touch.x, touch.y, point)) {
Object tag = child.getTag(R.id.sobot_srl_tag);
if ("fixed".equals(tag) || "fixed-top".equals(tag)) {
return false;
}
touch.offset(point.x, point.y);
boolean can = canLoadMore(child, touch, contentFull);
touch.offset(-point.x, -point.y);
return can;
}
}
}
return (contentFull || targetView.canScrollVertically(-1));
}
//</editor-fold>
//<editor-fold desc="transform Point">
public static boolean isTransformedTouchPointInView(@NonNull View group,@NonNull View child, float x, float y,PointF outLocalPoint) {
if (child.getVisibility() != View.VISIBLE) {
return false;
}
final float[] point = new float[2];
point[0] = x;
point[1] = y;
// transformPointToViewLocal(group, child, point);
point[0] += group.getScrollX() - child.getLeft();
point[1] += group.getScrollY() - child.getTop();
// final boolean isInView = pointInView(child, point[0], point[1], 0);
final boolean isInView = point[0] >= 0 && point[1] >= 0
&& point[0] < (child.getWidth())
&& point[1] < ((child.getHeight()));
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0]-x, point[1]-y);
}
return isInView;
}
//</editor-fold>
//<editor-fold desc="像素密度">
private static float density = Resources.getSystem().getDisplayMetrics().density;
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
* @param dpValue 虚拟像素
* @return 像素
*/
public static int dp2px(float dpValue) {
return (int) (0.5f + dpValue * density);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
* @param pxValue 像素
* @return 虚拟像素
*/
public static float px2dp(int pxValue) {
return (pxValue / density);
}
//</editor-fold>
//<editor-fold desc="ViscousFluidInterpolator">
/** 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) {
if (type == INTERPOLATOR_DECELERATE) {
return (1.0f - (1.0f - input) * (1.0f - input));
}
final float interpolated = VISCOUS_FLUID_NORMALIZE * viscousFluid(input);
if (interpolated > 0) {
return interpolated + VISCOUS_FLUID_OFFSET;
}
return interpolated;
}
//</editor-fold>
}
package com.sobot.widget.refresh.layout.wrapper;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.graphics.PointF;
import android.support.annotation.NonNull;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.ViewPager;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.Space;
import com.sobot.widget.R;
import com.sobot.widget.refresh.layout.api.RefreshContent;
import com.sobot.widget.refresh.layout.api.RefreshKernel;
import com.sobot.widget.refresh.layout.listener.CoordinatorLayoutListener;
import com.sobot.widget.refresh.layout.listener.ScrollBoundaryDecider;
import com.sobot.widget.refresh.layout.simple.SimpleBoundaryDecider;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.sobot.widget.refresh.layout.util.SmartUtil.isContentView;
import static com.sobot.widget.refresh.layout.util.SmartUtil.isTransformedTouchPointInView;
import static com.sobot.widget.refresh.layout.util.SmartUtil.measureViewHeight;
import static com.sobot.widget.refresh.layout.util.SmartUtil.scrollListBy;
/**
* 刷新内容包装
* Created by scwang on 2017/5/26.
*/
@SuppressWarnings("WeakerAccess")
public class RefreshContentWrapper implements RefreshContent, CoordinatorLayoutListener, AnimatorUpdateListener {
protected View mContentView;//直接内容视图
protected View mOriginalContentView;//被包裹的原真实视图
protected View mScrollableView;
protected View mFixedHeader;
protected View mFixedFooter;
protected int mLastSpinner = 0;
protected boolean mEnableRefresh = true;
protected boolean mEnableLoadMore = true;
protected SimpleBoundaryDecider mBoundaryAdapter = new SimpleBoundaryDecider();
public RefreshContentWrapper(@NonNull View view) {
this.mContentView = mOriginalContentView = mScrollableView = view;
}
//<editor-fold desc="findScrollableView">
protected void findScrollableView(View content, RefreshKernel kernel) {
View scrollableView = null;
boolean isInEditMode = mContentView.isInEditMode();
while (scrollableView == null || (scrollableView instanceof NestedScrollingParent
&& !(scrollableView instanceof NestedScrollingChild))) {
content = findScrollableViewInternal(content, scrollableView == null);
if (content == scrollableView) {
break;
}
scrollableView = content;
}
if (scrollableView != null) {
mScrollableView = scrollableView;
}
}
@Override
public void onCoordinatorUpdate(boolean enableRefresh, boolean enableLoadMore) {
mEnableRefresh = enableRefresh;
mEnableLoadMore = enableLoadMore;
}
protected View findScrollableViewInternal(View content, boolean selfAble) {
View scrollableView = null;
Queue<View> views = new LinkedList<>();
//noinspection unchecked
List<View> list = (List<View>)views;
list.add(content);
while (list.size() > 0 && scrollableView == null) {
View view = views.poll();
if (view != null) {
if ((selfAble || view != content) && isContentView(view)) {
scrollableView = view;
} else if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
for (int j = 0; j < group.getChildCount(); j++) {
list.add(group.getChildAt(j));
}
}
}
}
return scrollableView == null ? content : scrollableView;
}
protected View findScrollableViewByPoint(View content, PointF event, View orgScrollableView) {
if (content instanceof ViewGroup && event != null) {
ViewGroup viewGroup = (ViewGroup) content;
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.x, event.y, point)) {
if (child instanceof ViewPager || !isContentView(child)) {
event.offset(point.x, point.y);
child = findScrollableViewByPoint(child, event, orgScrollableView);
event.offset(-point.x, -point.y);
}
return child;
}
}
}
return orgScrollableView;
}
//</editor-fold>
//<editor-fold desc="implements">
@NonNull
public View getView() {
return mContentView;
}
@Override
@NonNull
public View getScrollableView() {
return mScrollableView;
}
@Override
public void moveSpinner(int spinner, int headerTranslationViewId, int footerTranslationViewId) {
boolean translated = false;
if (headerTranslationViewId != View.NO_ID) {
View headerTranslationView = mOriginalContentView.findViewById(headerTranslationViewId);
if (headerTranslationView != null) {
if (spinner > 0) {
translated = true;
headerTranslationView.setTranslationY(spinner);
} else if (headerTranslationView.getTranslationY() > 0) {
headerTranslationView.setTranslationY(0);
}
}
}
if (footerTranslationViewId != View.NO_ID) {
View footerTranslationView = mOriginalContentView.findViewById(footerTranslationViewId);
if (footerTranslationView != null) {
if (spinner < 0) {
translated = true;
footerTranslationView.setTranslationY(spinner);
} else if (footerTranslationView.getTranslationY() < 0) {
footerTranslationView.setTranslationY(0);
}
}
}
if (!translated) {
mOriginalContentView.setTranslationY(spinner);
} else {
mOriginalContentView.setTranslationY(0);
}
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 onActionDown(MotionEvent e) {
PointF point = new PointF(e.getX(), e.getY());
point.offset(-mContentView.getLeft(), -mContentView.getTop());
if (mScrollableView != mContentView) {
//如果内容视图不是 ScrollableView 说明使用了Layout嵌套内容,需要动态搜索 ScrollableView
mScrollableView = findScrollableViewByPoint(mContentView, point, mScrollableView);
}
if (mScrollableView == mContentView) {
//如果内容视图就是 ScrollableView 就不需要使用事件来动态搜索 而浪费CPU时间和性能了
// mBoundaryAdapter.setActionEvent(null);
mBoundaryAdapter.mActionEvent = null;
} else {
mBoundaryAdapter.mActionEvent = point;
// mBoundaryAdapter.setActionEvent(mMotionEvent);
}
}
@Override
public void setUpComponent(RefreshKernel kernel, View fixedHeader, View fixedFooter) {
findScrollableView(mContentView, kernel);
if (fixedHeader != null || fixedFooter != null) {
mFixedHeader = fixedHeader;
mFixedFooter = fixedFooter;
ViewGroup frameLayout = new FrameLayout(mContentView.getContext());
int index = kernel.getRefreshLayout().getLayout().indexOfChild(mContentView);
kernel.getRefreshLayout().getLayout().removeView(mContentView);
frameLayout.addView(mContentView, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
kernel.getRefreshLayout().getLayout().addView(frameLayout, index, layoutParams);
mContentView = frameLayout;
if (fixedHeader != null) {
fixedHeader.setTag(R.id.sobot_srl_tag, "fixed-top");
ViewGroup.LayoutParams lp = fixedHeader.getLayoutParams();
ViewGroup parent = (ViewGroup) fixedHeader.getParent();
index = parent.indexOfChild(fixedHeader);
parent.removeView(fixedHeader);
lp.height = measureViewHeight(fixedHeader);
parent.addView(new Space(mContentView.getContext()), index, lp);
frameLayout.addView(fixedHeader, 1, lp);
}
if (fixedFooter != null) {
fixedFooter.setTag(R.id.sobot_srl_tag,"fixed-bottom");
ViewGroup.LayoutParams lp = fixedFooter.getLayoutParams();
ViewGroup parent = (ViewGroup) fixedFooter.getParent();
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, 1, flp);
}
}
}
@Override
public void setScrollBoundaryDecider(ScrollBoundaryDecider boundary) {
if (boundary instanceof SimpleBoundaryDecider) {
mBoundaryAdapter = ((SimpleBoundaryDecider) boundary);
} else {
mBoundaryAdapter.boundary = (boundary);
}
}
@Override
public void setEnableLoadMoreWhenContentNotFull(boolean enable) {
mBoundaryAdapter.mEnableLoadMoreWhenContentNotFull = enable;
}
@Override
public AnimatorUpdateListener scrollContentWhenFinished(final int spinner) {
if (mScrollableView != null && spinner != 0) {
if ((spinner < 0 && mScrollableView.canScrollVertically(1)) || (spinner > 0 && mScrollableView.canScrollVertically(-1))) {
mLastSpinner = spinner;
return this;
}
}
return null;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
try {
float dy = (value - mLastSpinner) * mScrollableView.getScaleY();
if (mScrollableView instanceof AbsListView) {
scrollListBy((AbsListView) mScrollableView, (int)dy);
} else {
mScrollableView.scrollBy(0, (int)dy);
}
} catch (Throwable e) {
//根据用户反馈,此处可能会有BUG
e.printStackTrace();
}
mLastSpinner = value;
}
//</editor-fold>
}
package com.sobot.widget.refresh.layout.wrapper;
import android.annotation.SuppressLint;
import android.view.View;
import com.sobot.widget.refresh.layout.api.RefreshFooter;
import com.sobot.widget.refresh.layout.simple.SimpleComponent;
/**
* 刷新底部包装
* Created by scwang on 2017/5/26.
*/
@SuppressLint("ViewConstructor")
public class RefreshFooterWrapper extends SimpleComponent implements RefreshFooter {
public RefreshFooterWrapper(View wrapper) {
super(wrapper);
}
}
package com.sobot.widget.refresh.layout.wrapper;
import android.annotation.SuppressLint;
import android.view.View;
import com.sobot.widget.refresh.layout.api.RefreshHeader;
import com.sobot.widget.refresh.layout.simple.SimpleComponent;
/**
* 刷新头部包装
* Created by scwang on 2017/5/26.
*/
@SuppressLint("ViewConstructor")
public class RefreshHeaderWrapper extends SimpleComponent implements RefreshHeader {
public RefreshHeaderWrapper(View wrapper) {
super(wrapper);
}
}
<?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"
android:background="@color/sobot_refresh_bg_color"
tools:background="@color/sobot_refresh_bg_color"
tools:paddingBottom="20dp"
tools:paddingTop="20dp"
tools:parentTag="android.widget.RelativeLayout">
<ImageView
android:id="@+id/srl_classics_arrow"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_centerVertical="true"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_toStartOf="@+id/srl_classics_title"
android:layout_toLeftOf="@+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_centerVertical="true"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_toStartOf="@+id/srl_classics_title"
android:layout_toLeftOf="@+id/srl_classics_title"
android:contentDescription="@android:string/untitled"
tools:src="@android:drawable/stat_notify_sync"
tools:tint="#666" />
<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:text="@string/sobot_srl_footer_pulling"
android:textColor="@color/sobot_refresh_text_color"
android:textSize="15sp" />
</merge>
\ No newline at end of file
<?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:background="@color/sobot_refresh_bg_color"
tools:paddingBottom="20dp"
tools:paddingTop="20dp"
tools:parentTag="android.widget.RelativeLayout">
<ImageView
android:id="@+id/srl_classics_arrow"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_centerVertical="true"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_toStartOf="@+id/srl_classics_center"
android:layout_toLeftOf="@+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_centerVertical="true"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_toStartOf="@+id/srl_classics_center"
android:layout_toLeftOf="@+id/srl_classics_center"
android:contentDescription="@android:string/untitled"
tools:src="@android:drawable/stat_notify_sync"
tools:tint="#666666" />
<LinearLayout
android:id="@+id/srl_classics_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/srl_classics_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="@string/sobot_srl_header_pulling"
android:textColor="@color/sobot_refresh_text_color"
android:textSize="15sp" />
<TextView
android:id="@+id/srl_classics_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="@string/sobot_srl_header_update"
android:textColor="@color/sobot_refresh_text2_color"
android:textSize="12sp" />
</LinearLayout>
</merge>
\ No newline at end of file
<resources>
<string name="sobot_srl_content_empty">The content view in SmartRefreshLayout is empty. Do you forget to add it in xml layout file?</string>
<string name="sobot_srl_footer_pulling">Pull Up To Load More</string>
<string name="sobot_srl_footer_release">Release To Load More</string>
<string name="sobot_srl_footer_loading">Loading…</string>
<string name="sobot_srl_footer_refreshing">Wait For Refreshing…</string>
<string name="sobot_srl_footer_finish">Load Success</string>
<string name="sobot_srl_footer_failed">Load Failed</string>
<string name="sobot_srl_footer_nothing">No More Data</string>
<string name="sobot_srl_header_pulling">Pull Down To Refresh</string>
<string name="sobot_srl_header_release">Release To Refresh</string>
<string name="sobot_srl_header_refreshing">Refreshing…</string>
<string name="sobot_srl_header_loading">Wait For Loading…</string>
<string name="sobot_srl_header_finish">Refresh Success</string>
<string name="sobot_srl_header_failed">Refresh Failed</string>
<string name="sobot_srl_header_update">\'Last Update\' M-d HH:mm</string>
<string name="sobot_srl_header_secondary">Release To Second Floor</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--下拉刷新 背景颜色-->
<color name="sobot_refresh_bg_color">#1B1B1C</color>
<!--下拉刷新 标题文字颜色-->
<color name="sobot_refresh_text_color">#D1D1D6</color>
<!--下拉刷新 更新时间文字颜色-->
<color name="sobot_refresh_text2_color">#D1D1D6</color>
<!--下拉刷新 箭头控件颜色 -->
<color name="sobot_refresh_arrow_color">#D1D1D6</color>
<!--下拉刷新 加载中 旋转控件颜色-->
<color name="sobot_refresh_progress_color">#D1D1D6</color>
<!--下拉刷新 头部 箭头控件颜色 -->
<color name="sobot_refresh_header_arrow_color">@color/sobot_refresh_arrow_color</color>
<!--下拉刷新 头部 加载中 旋转控件颜色-->
<color name="sobot_refresh_header_progress_color">@color/sobot_refresh_progress_color</color>
<!--下拉刷新 尾部 箭头控件颜色 -->
<color name="sobot_refresh_footer_arrow_color">@color/sobot_refresh_arrow_color</color>
<!--下拉刷新 尾部 加载中 旋转控件颜色-->
<color name="sobot_refresh_footer_progress_color">@color/sobot_refresh_progress_color</color>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="sobotSrlStyle" format="reference" /><!--样式-->
<attr name="sobotSrlDrawableSize" format="dimension" /><!--图片尺寸-->
<attr name="sobotSrlDrawableArrowSize" format="dimension" /><!--箭头图片尺寸-->
<attr name="sobotSrlDrawableProgressSize" format="dimension" /><!--箭头图片尺寸-->
<attr name="sobotSrlDrawableMarginRight" format="dimension" /><!--图片和文字的间距-->
<attr name="sobotSrlTextSizeTitle" format="dimension" /><!--标题字体-->
<attr name="sobotSrlTextSizeTime" format="dimension" /><!--时间字体-->
<attr name="sobotSrlFinishDuration" format="integer" /><!--完成时停留时间-->
<attr name="sobotSrlPrimaryColor" format="color" /><!--主要颜色-->
<attr name="sobotSrlAccentColor" format="color" /><!--强调颜色-->
<attr name="sobotSrlDrawableArrow" format="reference" /><!--箭头图片-->
<attr name="sobotSrlColorArrow" format="color" /><!--强调颜色-->
<attr name="sobotSrlDrawableProgress" format="reference" /><!--转动图片-->
<attr name="sobotSrlColorProgress" format="color" /><!--强调颜色-->
<attr name="sobotSrlEnableHorizontalDrag" format="boolean" /><!--支持水平拖动-->
<attr name="sobotSrlTextPulling" format="string" />
<attr name="sobotSrlTextLoading" format="string" />
<attr name="sobotSrlTextRelease" format="string" />
<attr name="sobotSrlTextFinish" format="string" />
<attr name="sobotSrlTextFailed" format="string" />
<attr name="sobotSrlTextUpdate" format="string" />
<attr name="sobotSrlTextSecondary" format="string" />
<attr name="sobotSrlTextRefreshing" format="string" />
<attr name="sobotSrlTextNothing" format="string" />
<attr name="sobotSrlClassicsSpinnerStyle" format="enum">
<enum name="Translate" value="0" /><!--平行移动-->
<enum name="Scale" value="1" /><!--拉伸形变-->
<enum name="FixedBehind" value="2" /><!--固定在背后-->
</attr>
<attr name="sobot_layout_srlSpinnerStyle" format="enum">
<enum name="Translate" value="0" /><!--平行移动-->
<enum name="Scale" value="1" /><!--拉伸形变-->
<enum name="FixedBehind" value="2" /><!--固定在背后-->
<enum name="FixedFront" value="3" /><!--固定在前面-->
<enum name="MatchLayout" value="4" /><!--填满布局-->
</attr>
<declare-styleable name="SobotRefreshLayout">
<attr name="android:clipChildren" />
<attr name="android:clipToPadding" />
<attr name="sobotSrlPrimaryColor" />
<attr name="sobotSrlAccentColor" />
<attr name="srlReboundDuration" format="integer" />
<attr name="srlHeaderHeight" format="dimension" />
<attr name="srlFooterHeight" format="dimension" />
<attr name="srlHeaderInsetStart" format="dimension" />
<attr name="srlFooterInsetStart" format="dimension" />
<attr name="srlDragRate" format="float" />
<attr name="srlHeaderMaxDragRate" format="float" />
<attr name="srlFooterMaxDragRate" format="float" />
<attr name="srlHeaderTriggerRate" format="float" />
<attr name="srlFooterTriggerRate" format="float" />
<attr name="srlEnableRefresh" format="boolean" />
<attr name="srlEnableLoadMore" format="boolean" />
<attr name="srlEnableHeaderTranslationContent" format="boolean" />
<attr name="srlEnableFooterTranslationContent" format="boolean" />
<attr name="srlHeaderTranslationViewId" format="reference" />
<attr name="srlFooterTranslationViewId" format="reference" />
<attr name="srlEnablePreviewInEditMode" format="boolean" />
<attr name="srlEnableAutoLoadMore" format="boolean" />
<attr name="srlEnableOverScrollBounce" format="boolean" />
<attr name="srlEnablePureScrollMode" format="boolean" />
<attr name="srlEnableNestedScrolling" format="boolean" />
<attr name="srlEnableScrollContentWhenLoaded" format="boolean" />
<attr name="srlEnableScrollContentWhenRefreshed" format="boolean" />
<attr name="srlEnableLoadMoreWhenContentNotFull" format="boolean" />
<attr name="srlEnableFooterFollowWhenLoadFinished" format="boolean" />
<attr name="srlEnableFooterFollowWhenNoMoreData" format="boolean" />
<attr name="srlEnableClipHeaderWhenFixedBehind" format="boolean" />
<attr name="srlEnableClipFooterWhenFixedBehind" format="boolean" />
<attr name="srlEnableOverScrollDrag" format="boolean" />
<attr name="srlDisableContentWhenRefresh" format="boolean" />
<attr name="srlDisableContentWhenLoading" format="boolean" />
<attr name="srlFixedHeaderViewId" format="reference" />
<attr name="srlFixedFooterViewId" format="reference" />
</declare-styleable>
<declare-styleable name="SobotRefreshLayout_Layout">
<attr name="sobot_layout_srlSpinnerStyle" />
<attr name="layout_srlBackgroundColor" format="color" />
</declare-styleable>
<style name="SobotRefreshStyle">
<item name="sobotSrlPrimaryColor">@android:color/holo_blue_dark</item>
<item name="sobotSrlAccentColor">@android:color/white</item>
<item name="srlReboundDuration">300</item>
<item name="srlHeaderHeight">100dp</item>
<item name="srlFooterHeight">60dp</item>
<item name="srlHeaderInsetStart">0dp</item>
<item name="srlFooterInsetStart">0dp</item>
<item name="srlDragRate">0.5</item>
<item name="srlHeaderMaxDragRate">2.5</item>
<item name="srlFooterMaxDragRate">2.5</item>
<item name="srlHeaderTriggerRate">1</item>
<item name="srlFooterTriggerRate">1</item>
<!--<item name="srlEnableRefresh">true</item>-->
<!--<item name="srlEnableLoadMore">true</item>-->
<!--<item name="srlEnableHeaderTranslationContent">true</item>-->
<!--<item name="srlEnableFooterTranslationContent">true</item>-->
<!--<item name="srlHeaderTranslationViewId">-1</item>-->
<!--<item name="srlFooterTranslationViewId">-1</item>-->
<item name="srlEnablePreviewInEditMode">true</item>
<item name="srlEnableAutoLoadMore">true</item>
<item name="srlEnableOverScrollDrag">false</item>
<item name="srlEnableOverScrollBounce">true</item>
<item name="srlEnablePureScrollMode">false</item>
<item name="srlEnableNestedScrolling">true</item>
<item name="srlEnableScrollContentWhenLoaded">true</item>
<item name="srlEnableScrollContentWhenRefreshed">true</item>
<item name="srlEnableLoadMoreWhenContentNotFull">true</item>
<!--<item name="srlEnableFooterFollowWhenLoadFinished" format="boolean"/>-->
<item name="srlEnableFooterFollowWhenNoMoreData">false</item>
<item name="srlEnableClipHeaderWhenFixedBehind">true</item>
<item name="srlEnableClipFooterWhenFixedBehind">true</item>
<item name="srlDisableContentWhenRefresh">false</item>
<item name="srlDisableContentWhenLoading">false</item>
<!--<item name="srlFixedHeaderViewId" format="reference"/>-->
<!--<item name="srlFixedFooterViewId" format="reference"/>-->
</style>
<declare-styleable name="SobotClassicsFooter">
<attr name="sobotSrlClassicsSpinnerStyle" />
<attr name="sobotSrlPrimaryColor" />
<attr name="sobotSrlAccentColor" />
<attr name="sobotSrlFinishDuration" />
<attr name="sobotSrlTextSizeTitle" />
<attr name="sobotSrlColorArrow" />
<attr name="sobotSrlDrawableArrow" />
<attr name="sobotSrlDrawableProgress" />
<attr name="sobotSrlColorProgress" />
<attr name="sobotSrlDrawableMarginRight" />
<attr name="sobotSrlDrawableSize" />
<attr name="sobotSrlDrawableArrowSize" />
<attr name="sobotSrlDrawableProgressSize" />
<attr name="sobotSrlTextPulling" />
<attr name="sobotSrlTextRelease" />
<attr name="sobotSrlTextLoading" />
<attr name="sobotSrlTextRefreshing" />
<attr name="sobotSrlTextFinish" />
<attr name="sobotSrlTextFailed" />
<attr name="sobotSrlTextNothing" />
</declare-styleable>
<declare-styleable name="SobotClassicsHeader">
<attr name="sobotSrlClassicsSpinnerStyle" />
<attr name="sobotSrlPrimaryColor" />
<attr name="sobotSrlAccentColor" />
<attr name="sobotSrlFinishDuration" />
<attr name="sobotSrlDrawableArrow" />
<attr name="sobotSrlColorArrow" />
<attr name="sobotSrlColorProgress" />
<attr name="sobotSrlDrawableProgress" />
<attr name="sobotSrlDrawableMarginRight" />
<attr name="sobotSrlDrawableSize" />
<attr name="sobotSrlDrawableArrowSize" />
<attr name="sobotSrlDrawableProgressSize" />
<attr name="sobotSrlTextSizeTitle" />
<attr name="sobotSrlTextSizeTime" />
<attr name="sobotSrlTextTimeMarginTop" format="dimension" />
<attr name="sobotSrlEnableLastTime" format="boolean" />
<attr name="sobotSrlTextPulling" />
<attr name="sobotSrlTextLoading" />
<attr name="sobotSrlTextRelease" />
<attr name="sobotSrlTextFinish" />
<attr name="sobotSrlTextFailed" />
<attr name="sobotSrlTextUpdate" />
<attr name="sobotSrlTextSecondary" />
<attr name="sobotSrlTextRefreshing" />
</declare-styleable>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--下拉刷新 背景颜色-->
<color name="sobot_refresh_bg_color">#FFFFFF</color>
<!--下拉刷新 标题文字颜色-->
<color name="sobot_refresh_text_color">#ACB5C4</color>
<!--下拉刷新 更新时间文字颜色-->
<color name="sobot_refresh_text2_color">#ACB5C4</color>
<!--下拉刷新 箭头控件颜色 -->
<color name="sobot_refresh_arrow_color">#ACB5C4</color>
<!--下拉刷新 加载中 旋转控件颜色-->
<color name="sobot_refresh_progress_color">#ACB5C4</color>
<!--下拉刷新 头部 箭头控件颜色 -->
<color name="sobot_refresh_header_arrow_color">@color/sobot_refresh_arrow_color</color>
<!--下拉刷新 头部 加载中 旋转控件颜色-->
<color name="sobot_refresh_header_progress_color">@color/sobot_refresh_progress_color</color>
<!--下拉刷新 尾部 箭头控件颜色 -->
<color name="sobot_refresh_footer_arrow_color">@color/sobot_refresh_arrow_color</color>
<!--下拉刷新 尾部 加载中 旋转控件颜色-->
<color name="sobot_refresh_footer_progress_color">@color/sobot_refresh_progress_color</color>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="sobot_srl_tag" type="id" />
</resources>
\ No newline at end of file
<resources>
<string name="sobot_srl_content_empty">SmartRefreshLayout中没有找到内容视图。您是否忘记在xml布局文件中添加?</string>
<string name="sobot_srl_header_pulling">下拉可以刷新</string>
<string name="sobot_srl_header_refreshing">正在刷新…</string>
<string name="sobot_srl_header_loading">等待底部加载完成…</string>
<string name="sobot_srl_header_release">释放立即刷新</string>
<string name="sobot_srl_header_finish">刷新完成</string>
<string name="sobot_srl_header_failed">刷新失败</string>
<string name="sobot_srl_header_update">上次更新 M-d HH:mm</string>
<string name="sobot_srl_header_secondary">释放进入二楼</string>
<string name="sobot_srl_footer_pulling">上拉加载更多</string>
<string name="sobot_srl_footer_release">释放立即加载</string>
<string name="sobot_srl_footer_loading">正在加载…</string>
<string name="sobot_srl_footer_refreshing">等待头部刷新完成…</string>
<string name="sobot_srl_footer_finish">加载完成</string>
<string name="sobot_srl_footer_failed">加载失败</string>
<string name="sobot_srl_footer_nothing">没有更多数据了</string>
</resources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment