Repository: linuxjava/HorizontalRefreshLayout
Branch: master
Commit: 903c9a547c9f
Files: 41
Total size: 87.2 KB
Directory structure:
gitextract_0us6p602/
├── README.md
├── apk/
│ └── app-debug.apk
├── app/
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── robincxiao/
│ │ └── horizontalrefreshlayout/
│ │ ├── LayoutAdapter.java
│ │ └── MainActivity.java
│ └── res/
│ ├── drawable/
│ │ └── item_background.xml
│ ├── layout/
│ │ ├── activity_main.xml
│ │ └── item.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── values-w820dp/
│ └── dimens.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── jcenter-push.gradle
├── lib/
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── xiao/
│ │ └── free/
│ │ └── horizontalrefreshlayout/
│ │ ├── HorizontalRefreshLayout.java
│ │ ├── RefreshCallBack.java
│ │ ├── RefreshHeader.java
│ │ ├── refreshhead/
│ │ │ ├── LoadingRefreshHeader.java
│ │ │ ├── MaterialRefreshHeader.java
│ │ │ └── NiceRefreshHeader.java
│ │ └── widget/
│ │ ├── CircleImageView.java
│ │ └── MaterialProgressDrawable.java
│ └── res/
│ ├── drawable/
│ │ ├── animated_rotate.xml
│ │ ├── animated_rotate_1.xml
│ │ └── spinner.xml
│ ├── layout/
│ │ ├── common_loading_refresh_header.xml
│ │ ├── material_refresh_header.xml
│ │ └── nice_refresh_header.xml
│ └── values/
│ └── strings.xml
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: README.md
================================================
:running:HorizontalRefreshLayout-Android:running:
============
开发者使用 HorizontalRefreshLayout-Android 可以对RecycView、Listview、ScrollView等控件实现左右刷新
## APK下载
[Download](https://github.com/linuxjava/HorizontalRefreshLayout/raw/master/apk/app-debug.apk)
## Demo使用
运行demo需删除gradle.properties中的代理
```xml
systemProp.http.proxyHost=dev-proxy.oa.com
systemProp.http.proxyPort=8080
systemProp.https.proxyHost=dev-proxy.oa.com
systemProp.https.proxyPort=8080
```
## Gradle配置
compile 'xiao.free.horizontalrefreshlayout:lib:v0.1.2'
## XML配置
```xml
```
## Java代码
```java
refreshLayout = (HorizontalRefreshLayout) findViewById(R.id.refresh);
refreshLayout.setRefreshCallback(this);
refreshLayout.setRefreshHeader(new LoadingRefreshHeader(this), HorizontalRefreshLayout.LEFT);
refreshLayout.setRefreshHeader(new LoadingRefreshHeader(this), HorizontalRefreshLayout.RIGHT);
```
通过setRefreshHeader方法可以设置左右刷新头部,库中已支持三种刷新效果,如下图所示:



## 自定义Header
可通过实现如下接口实现自定义header
```java
public interface RefreshHeader {
/**
* @param dragPosition HorizontalRefreshLayout.START or HorizontalRefreshLayout.END
*/
void onStart(int dragPosition, View refreshHead);
/**
* @param distance
*/
void onDragging(float distance, float percent, View refreshHead);
void onReadyToRelease(View refreshHead);
@NonNull View getView(ViewGroup container);
void onRefreshing(View refreshHead);
}
```
具体可参考lib库中refreshhead目录中的实现
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion ANDROID_BUILD_SDK_VERSION as int
buildToolsVersion ANDROID_BUILD_TOOLS_VERSION
defaultConfig {
applicationId "com.example.robincxiao.horizontalrefreshlayout"
minSdkVersion 14
targetSdkVersion ANDROID_BUILD_SDK_VERSION
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.github.lsjwzh.RecyclerViewPager:lib:v1.1.2'
//compile project(':lib')
compile 'xiao.free.horizontalrefreshlayout:lib:v0.1.2'
//compile 'xiao.free.horizontalrefreshlayout:lib:v1.0.0'
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
================================================
FILE: app/src/main/java/com/example/robincxiao/horizontalrefreshlayout/LayoutAdapter.java
================================================
/*
* Copyright (C) 2014 Lucas Rocha
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.robincxiao.horizontalrefreshlayout;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class LayoutAdapter extends RecyclerView.Adapter {
private static final int DEFAULT_ITEM_COUNT = 5;
private final Context mContext;
private final RecyclerView mRecyclerView;
private final List mItems;
private int mCurrentItemId = 0;
private int[] imageIds = {R.mipmap.card_cover1, R.mipmap.card_cover2, R.mipmap.card_cover3,
R.mipmap.card_cover4, R.mipmap.card_cover5, R.mipmap.card_cover6,
R.mipmap.card_cover7, R.mipmap.card_cover8};
public static class SimpleViewHolder extends RecyclerView.ViewHolder {
public final TextView title;
public final ImageView myImage;
public SimpleViewHolder(View view) {
super(view);
title = (TextView) view.findViewById(R.id.title);
myImage = (ImageView) view.findViewById(R.id.image);
}
}
public LayoutAdapter(Context context, RecyclerView recyclerView) {
this(context, recyclerView, DEFAULT_ITEM_COUNT);
}
public LayoutAdapter(Context context, RecyclerView recyclerView, int itemCount) {
mContext = context;
mItems = new ArrayList<>(itemCount);
for (int i = 0; i < itemCount; i++) {
addItem(i);
}
mRecyclerView = recyclerView;
}
public void addItem(int position) {
final int id = mCurrentItemId++;
mItems.add(position, id);
notifyItemInserted(position);
}
public void removeItem(int position) {
mItems.remove(position);
notifyItemRemoved(position);
}
public void getMore() {
int size = getItemCount();
for (int i = size; i < size + 5; i++) {
addItem(i);
}
}
@Override
public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(mContext).inflate(R.layout.item, parent, false);
return new SimpleViewHolder(view);
}
@Override
public void onBindViewHolder(SimpleViewHolder holder, int position) {
holder.title.setText("Index " + mItems.get(position));
holder.myImage.setImageResource(imageIds[position % imageIds.length]);
final View itemView = holder.itemView;
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext, "", Toast.LENGTH_SHORT).show();
}
});
final int itemId = mItems.get(position);
}
@Override
public int getItemCount() {
return mItems.size();
}
}
================================================
FILE: app/src/main/java/com/example/robincxiao/horizontalrefreshlayout/MainActivity.java
================================================
package com.example.robincxiao.horizontalrefreshlayout;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import com.lsjwzh.widget.recyclerviewpager.RecyclerViewPager;
import xiao.free.horizontalrefreshlayout.HorizontalRefreshLayout;
import xiao.free.horizontalrefreshlayout.RefreshCallBack;
import xiao.free.horizontalrefreshlayout.refreshhead.LoadingRefreshHeader;
import xiao.free.horizontalrefreshlayout.refreshhead.MaterialRefreshHeader;
import xiao.free.horizontalrefreshlayout.refreshhead.NiceRefreshHeader;
public class MainActivity extends AppCompatActivity implements RefreshCallBack {
private HorizontalRefreshLayout refreshLayout;
protected RecyclerViewPager mRecyclerView;
private LayoutAdapter mLayoutAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
refreshLayout = (HorizontalRefreshLayout) findViewById(R.id.refresh);
refreshLayout.setRefreshCallback(this);
refreshLayout.setRefreshHeader(new NiceRefreshHeader(this), HorizontalRefreshLayout.LEFT);
refreshLayout.setRefreshHeader(new NiceRefreshHeader(this), HorizontalRefreshLayout.RIGHT);
mRecyclerView = (RecyclerViewPager) findViewById(R.id.viewpager);
LinearLayoutManager layout = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
mRecyclerView.setLayoutManager(layout);
mLayoutAdapter = new LayoutAdapter(this, mRecyclerView);
mRecyclerView.setAdapter(mLayoutAdapter);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLongClickable(true);
mRecyclerView.addOnPageChangedListener(new RecyclerViewPager.OnPageChangedListener() {
@Override
public void OnPageChanged(int oldPosition, int newPosition) {
int size = mLayoutAdapter.getItemCount();
if (size > 1 && newPosition == size - 1) {
//mLayoutAdapter.getMore();
//refreshLayout.startAutoRefresh(HorizontalRefreshLayout.RIGHT);
}
}
});
//refreshLayout.startAutoRefresh(HorizontalRefreshLayout.LEFT);
}
@Override
public void onLeftRefreshing() {
refreshLayout.postDelayed(new Runnable() {
@Override
public void run() {
refreshLayout.onRefreshComplete();
}
}, 2000);
}
@Override
public void onRightRefreshing() {
refreshLayout.postDelayed(new Runnable() {
@Override
public void run() {
mLayoutAdapter.getMore();
refreshLayout.onRefreshComplete();
}
}, 2000);
}
}
================================================
FILE: app/src/main/res/drawable/item_background.xml
================================================
-
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
================================================
FILE: app/src/main/res/layout/item.xml
================================================
================================================
FILE: app/src/main/res/values/colors.xml
================================================
#3F51B5
#303F9F
#FF4081
#CCCCCC
#77CEEE
#ff33b5e5
================================================
FILE: app/src/main/res/values/dimens.xml
================================================
16dp
16dp
================================================
FILE: app/src/main/res/values/strings.xml
================================================
HorizontalRefreshLayout
================================================
FILE: app/src/main/res/values/styles.xml
================================================
================================================
FILE: app/src/main/res/values-w820dp/dimens.xml
================================================
64dp
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:$GRADLE_VERSION"
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
maven { url "https://jitpack.io" }
jcenter()
}
tasks.withType(Javadoc).all {
enabled = false
options.setEncoding('UTF-8')
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Mon Dec 28 10:00:20 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#gradle版本
GRADLE_VERSION=2.1.0
#目标SDK版本
ANDROID_BUILD_TARGET_SDK_VERSION=19
#编译SDK版本
ANDROID_BUILD_SDK_VERSION=23
#编译工具版本
ANDROID_BUILD_TOOLS_VERSION=23.0.2
#V7包版本
SUPPORT_LIBRARY_VERSION=23.4.0
#代理
systemProp.http.proxyHost=dev-proxy.oa.com
systemProp.http.proxyPort=8080
systemProp.https.proxyHost=dev-proxy.oa.com
systemProp.https.proxyPort=8080
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: jcenter-push.gradle
================================================
apply plugin: 'com.jfrog.bintray'
apply plugin: 'com.github.dcendents.android-maven'
def siteUrl = "https://github.com/linuxjava/HorizontalRefreshLayout"
def gitUrl = "https://github.com/linuxjava/HorizontalRefreshLayout.git"
group = 'xiao.free.horizontalrefreshlayout'// library name
version = 'v0.1.2'// version
install {
repositories.mavenInstaller {
// This generates POM.xml with proper paramters
pom {
project {
packaging 'aar'
name 'HorizontalRefreshLayout for Android'//添加项目描述
url siteUrl
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id 'guochangxiao'
name 'guochangxiao'
email 'guochangxiao@gmail.com'
}
}
scm {
connection gitUrl
developerConnection gitUrl
url siteUrl
}
}
}
}
}
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
task javadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
archives sourcesJar
}
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
bintray {
user = properties.getProperty("bintray.user")
key = properties.getProperty("bintray.apikey")
configurations = ['archives']
pkg {
repo = "maven" //JCenter上仓储名
name = "horizontalrefreshlayout" //JCenter上的项目中的packageName
websiteUrl = siteUrl
vcsUrl = gitUrl
licenses = ["Apache-2.0"]
publish = true
}
}
================================================
FILE: lib/build.gradle
================================================
apply plugin: 'com.android.library'
android {
compileSdkVersion ANDROID_BUILD_SDK_VERSION as int
buildToolsVersion ANDROID_BUILD_TOOLS_VERSION
resourcePrefix "horizontalrefreshlayout_"
defaultConfig {
minSdkVersion 14
targetSdkVersion ANDROID_BUILD_SDK_VERSION
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.4.0'
}
apply from: '../jcenter-push.gradle'
================================================
FILE: lib/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: lib/src/main/AndroidManifest.xml
================================================
================================================
FILE: lib/src/main/java/xiao/free/horizontalrefreshlayout/HorizontalRefreshLayout.java
================================================
package xiao.free.horizontalrefreshlayout;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.FrameLayout;
/**
* Created by robincxiao on 2017/2/6.
* 1.View布局问题,onMeasure、onLayout
* 2.滑动冲突问题
* 2.1事件拦截
* 2.2拖动效果
* 3.释放时自动归位问题
* 值得注意的问题:
* 1.onlayout中header初始化
* 2.onTouchEvent的返回值
*/
public class HorizontalRefreshLayout extends FrameLayout {
private static final int DURATION = 150;
private Context context;
private RefreshHeader leftRefreshHeader;
private RefreshHeader rightRefreshHeader;
private View mTargetView;
private View leftHeaderView;
private View rightHeaderView;
private RefreshCallBack refreshCallback;
private int touchSlop;
private int dragMarginPx;
private int leftHeaderWidth;
private int rightHeaderWidth;
//最大拖动距离
private int dragMaxHeaderWidth;
private int mLastInterceptX;
private int mLastInterceptY;
private int mLastX;
private int mLastY;
private float mTargetTranslationX = 0;
//header状态,当前显示是左边header、右边header
private int headerState = -1;
public static final int LEFT = 0;
public static final int RIGHT = 1;
//刷新状态
private static final int REFRESH_STATE_IDLE = 0;
private static final int REFRESH_STATE_START = 1;
private static final int REFRESH_STATE_DRAGGING = 2;
private static final int REFRESH_STATE_READY_TO_RELEASE = 3;
private static final int REFRESH_STATE_REFRESHING = 4;
private int refreshState = REFRESH_STATE_IDLE;
public HorizontalRefreshLayout(Context context) {
super(context);
init();
}
public HorizontalRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HorizontalRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void setRefreshCallback(RefreshCallBack callback) {
refreshCallback = callback;
}
private void init() {
context = getContext();
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(leftHeaderView != null) {
leftHeaderWidth = leftHeaderView.getMeasuredWidth();
dragMarginPx = (int) (leftHeaderWidth * 0.6);
dragMaxHeaderWidth = leftHeaderWidth + dragMarginPx;
}
if(rightHeaderView != null) {
rightHeaderWidth = rightHeaderView.getMeasuredWidth();
if(dragMarginPx == 0){
dragMarginPx = (int) (rightHeaderWidth * 0.6);
dragMaxHeaderWidth = rightHeaderWidth + dragMarginPx;
}
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (getChildCount() == 0) {
return;
}
if (mTargetView == null) {
findTargetView();
if (mTargetView == null) {
return;
}
}
/**
* 注意:只有状态是IDLE时才初始化刷新header的TranslationX;因为在滑动mTargetView时onLayout会被重新调用,
* 如果不是在REFRESH_STATE_IDLE状态下设置setTranslationX,则会产生问题
*/
if (refreshState == REFRESH_STATE_IDLE) {
if (leftHeaderView != null) {
leftHeaderView.setTranslationX(-leftHeaderWidth);
}
if (rightHeaderView != null) {
rightHeaderView.setTranslationX(rightHeaderWidth);
}
}
super.onLayout(changed, left, top, right, bottom);
}
private void findTargetView() {
if (mTargetView == null) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (!child.equals(leftHeaderView) && !child.equals(rightHeaderView)) {
mTargetView = child;
break;
}
}
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = mLastInterceptX = x;
mLastY = mLastInterceptY = y;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastInterceptX;
int deltaY = y - mLastInterceptY;
mLastX = mLastInterceptX = x;
mLastY = mLastInterceptY = y;
/**
* 注意:需要判断refreshState != REFRESH_STATE_REFRESHING,否则当处于REFRESH_STATE_REFRESHING状态
* 再次拖动滑动时,会有些许小瑕疵
*/
if (Math.abs(deltaX) > Math.abs(deltaY)) {//判断是否是水平滑动
if (leftHeaderView != null && deltaX > 0 && !canChildScrollRight() && refreshState != REFRESH_STATE_REFRESHING) {//手指向右滑动
headerState = LEFT;
refreshState = REFRESH_STATE_START;
leftRefreshHeader.onStart(LEFT, leftHeaderView);
return true;
} else if (rightHeaderView != null && deltaX < 0 && !canChildScrollLeft() && refreshState != REFRESH_STATE_REFRESHING) {//手指向左滑动
headerState = RIGHT;
refreshState = REFRESH_STATE_START;
rightRefreshHeader.onStart(RIGHT, rightHeaderView);
return true;
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mLastInterceptX = 0;
mLastInterceptY = 0;
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
/**
* 为什么不在onTouchEvent中直接返回true,而是在ACTION_MOVE/ACTION_UP/ACTION_CANCEL返回true,ACTION_DOWN不返回true?
* 在如下场景下需要这样设计:header正在刷新的时候,用户点击在header上开始drag,但是当header正在刷新的时候,我们并不希望
* headerview和mTargetView被拖动,因此需要这样去处理。
*/
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
mLastX = x;
mLastY = y;
float dampingDX = deltaX * (1 - Math.abs((mTargetTranslationX / dragMaxHeaderWidth))); //let drag action has resistance
mTargetTranslationX += dampingDX;
if (headerState == LEFT) {
if (mTargetTranslationX <= 0) {
Log.d("xiao1", "test1");
mTargetTranslationX = 0;
mTargetView.setTranslationX(0);
} else if (mTargetTranslationX >= dragMaxHeaderWidth) {
Log.d("xiao1", "test2");
mTargetTranslationX = dragMaxHeaderWidth;
mTargetView.setTranslationX(mTargetTranslationX);
} else {
Log.d("xiao1", "test3");
mTargetView.setTranslationX(mTargetTranslationX);
if (refreshState != REFRESH_STATE_READY_TO_RELEASE && mTargetTranslationX >= leftHeaderWidth) {
refreshState = REFRESH_STATE_READY_TO_RELEASE;
leftRefreshHeader.onReadyToRelease(leftHeaderView);
} else {
refreshState = REFRESH_STATE_DRAGGING;
//计算出拖动的比率
float percent = Math.abs(mTargetTranslationX / leftHeaderWidth);
leftRefreshHeader.onDragging(mTargetTranslationX, percent, leftHeaderView);
}
}
leftHeaderView.setTranslationX(-leftHeaderWidth + mTargetTranslationX);
} else if ((headerState == RIGHT)) {
if (mTargetTranslationX >= 0) {
Log.d("xiao1", "test4");
mTargetTranslationX = 0;
mTargetView.setTranslationX(0);
} else if (mTargetTranslationX <= -dragMaxHeaderWidth) {
Log.d("xiao1", "test5");
mTargetTranslationX = -dragMaxHeaderWidth;
mTargetView.setTranslationX(mTargetTranslationX);
} else {
Log.d("xiao1", "test6");
mTargetView.setTranslationX(mTargetTranslationX);
if (refreshState != REFRESH_STATE_READY_TO_RELEASE && mTargetTranslationX <= -rightHeaderWidth) {
refreshState = REFRESH_STATE_READY_TO_RELEASE;
rightRefreshHeader.onReadyToRelease(rightHeaderView);
} else {
refreshState = REFRESH_STATE_DRAGGING;
//计算出拖动的比率
float percent = Math.abs(mTargetTranslationX / rightHeaderWidth);
rightRefreshHeader.onDragging(mTargetTranslationX, percent, rightHeaderView);
}
}
rightHeaderView.setTranslationX(rightHeaderWidth + mTargetTranslationX);
}
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mLastX = mLastInterceptX = 0;
mLastY = mLastInterceptY = 0;
if (headerState == LEFT) {
if (mTargetTranslationX < leftHeaderWidth) {
Log.d("xiao1", "test7");
smoothRelease();
} else {
Log.d("xiao1", "test8");
smoothLocateToRefresh();
}
} else if (headerState == RIGHT) {
if (mTargetTranslationX > -rightHeaderWidth) {
Log.d("xiao1", "test9");
smoothRelease();
} else {
Log.d("xiao1", "test10");
smoothLocateToRefresh();
}
}
return true;
}
return super.onTouchEvent(event);
}
/**
* 释放滑动
*/
private void smoothRelease() {
mTargetView.animate().translationX(0).setDuration(DURATION)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//动画结束后reset状态
refreshState = REFRESH_STATE_IDLE;
headerState = -1;
mTargetTranslationX = 0;
}
})
.start();
if (headerState == LEFT) {
if (leftHeaderView != null) {
leftRefreshHeader.onStart(LEFT, leftHeaderView);//恢复到开始状态
leftHeaderView.animate().translationX(-leftHeaderWidth).setDuration(DURATION).start();
}
} else if (headerState == RIGHT) {
if (rightHeaderView != null) {
rightRefreshHeader.onStart(LEFT, rightHeaderView);//恢复到开始状态
rightHeaderView.animate().translationX(rightHeaderWidth).setDuration(DURATION).start();
}
}
}
/**
* 滑动到刷新位置
*/
private void smoothLocateToRefresh() {
if (headerState == LEFT && leftHeaderView != null) {
refreshState = REFRESH_STATE_REFRESHING;
leftHeaderView.animate().translationX(0).setDuration(DURATION).start();
leftRefreshHeader.onRefreshing(leftHeaderView);//正在刷新
mTargetView.animate().translationX(leftHeaderWidth).setDuration(DURATION)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mTargetTranslationX = leftHeaderWidth;
if (refreshCallback != null) {
if (headerState == LEFT) {
refreshCallback.onLeftRefreshing();
} else {
refreshCallback.onRightRefreshing();
}
}
}
})
.start();
} else if (headerState == RIGHT && rightHeaderView != null) {
refreshState = REFRESH_STATE_REFRESHING;
//注意,这里使用的translationXBy
rightHeaderView.animate().translationXBy(-mTargetTranslationX - rightHeaderWidth).setDuration(DURATION).start();
rightRefreshHeader.onRefreshing(rightHeaderView);//正在刷新
mTargetView.animate().translationX(-rightHeaderWidth).setDuration(DURATION)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (refreshCallback != null) {
if (headerState == LEFT) {
refreshCallback.onLeftRefreshing();
} else {
refreshCallback.onRightRefreshing();
}
}
mTargetTranslationX = -rightHeaderWidth;
}
})
.start();
}
}
/**
* 刷新完成
*/
public void onRefreshComplete() {
smoothRelease();
}
/**
* 自动刷新
*
* @param leftOrRight HorizontalRefreshLayout.LEFT or HorizontalRefreshLayout.RIGHT
*/
public void startAutoRefresh(final int leftOrRight) {
// delay to let the animation smoothly
//此处需要采用postDelayed将消息方式view的消息队列中,如果直接调用smoothLocateToRefresh可能view还没能完全初始化好,导致mTarget为null
postDelayed(new Runnable() {
@Override
public void run() {
headerState = leftOrRight;
smoothLocateToRefresh();
}
}, 100);
}
private void setLeftHeadView(View view) {
leftHeaderView = view;
((LayoutParams) leftHeaderView.getLayoutParams()).gravity = Gravity.START;
addView(leftHeaderView, 0);
}
private void setRightHeadView(View view) {
rightHeaderView = view;
((LayoutParams) rightHeaderView.getLayoutParams()).gravity = Gravity.END;
addView(rightHeaderView, 0);
}
/**
* 设置刷新header
* @param header
* @param startOrEnd
*/
public void setRefreshHeader(RefreshHeader header, int startOrEnd) {
if (startOrEnd == LEFT) {
leftRefreshHeader = header;
setLeftHeadView(leftRefreshHeader.getView(this));
} else if (startOrEnd == RIGHT) {
rightRefreshHeader = header;
setRightHeadView(rightRefreshHeader.getView(this));
}
}
/**
* mTargetView是否还能向右滑动
*
* @return
*/
public boolean canChildScrollRight() {
return ViewCompat.canScrollHorizontally(mTargetView, -1);
}
/**
* mTargetView是否还能向左滑动
*
* @return
*/
public boolean canChildScrollLeft() {
return ViewCompat.canScrollHorizontally(mTargetView, 1);
}
public static int dp2px(Context context, float dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources().getDisplayMetrics());
}
}
================================================
FILE: lib/src/main/java/xiao/free/horizontalrefreshlayout/RefreshCallBack.java
================================================
package xiao.free.horizontalrefreshlayout;
/**
* Created by wangqi on 2015/12/24.
*/
public interface RefreshCallBack {
void onLeftRefreshing();
void onRightRefreshing();
}
================================================
FILE: lib/src/main/java/xiao/free/horizontalrefreshlayout/RefreshHeader.java
================================================
package xiao.free.horizontalrefreshlayout;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by wangqi on 2015/12/24.
*/
public interface RefreshHeader {
/**
* @param dragPosition HorizontalRefreshLayout.START or HorizontalRefreshLayout.END
*/
void onStart(int dragPosition, View refreshHead);
/**
* @param distance
*/
void onDragging(float distance, float percent, View refreshHead);
void onReadyToRelease(View refreshHead);
@NonNull View getView(ViewGroup container);
void onRefreshing(View refreshHead);
}
================================================
FILE: lib/src/main/java/xiao/free/horizontalrefreshlayout/refreshhead/LoadingRefreshHeader.java
================================================
package xiao.free.horizontalrefreshlayout.refreshhead;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import xiao.free.horizontalrefreshlayout.R;
import xiao.free.horizontalrefreshlayout.RefreshHeader;
/**
* Created by xiaoguochang on 2015/12/24.
*/
public class LoadingRefreshHeader implements RefreshHeader {
private final Context context;
private ProgressBar progressBar;
private ImageView staticLoading;
public LoadingRefreshHeader(Context context) {
this.context = context;
}
@NonNull
@Override
public View getView(ViewGroup container) {
View view = LayoutInflater.from(context).inflate(R.layout.common_loading_refresh_header, container, false);
progressBar = (ProgressBar) view.findViewById(R.id.progressbar);
staticLoading = (ImageView) view.findViewById(R.id.static_loading);
progressBar.setVisibility(View.INVISIBLE);
return view;
}
@Override
public void onStart(int dragPosition, View refreshHead) {
staticLoading.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.INVISIBLE);
}
@Override
public void onDragging(float distance, float percent, View refreshHead) {
staticLoading.setRotation(percent * 360);
}
@Override
public void onReadyToRelease(View refreshHead) {
}
@Override
public void onRefreshing(View refreshHead) {
staticLoading.setVisibility(View.INVISIBLE);
progressBar.setVisibility(View.VISIBLE);
}
}
================================================
FILE: lib/src/main/java/xiao/free/horizontalrefreshlayout/refreshhead/MaterialRefreshHeader.java
================================================
package xiao.free.horizontalrefreshlayout.refreshhead;
import android.support.annotation.NonNull;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import xiao.free.horizontalrefreshlayout.HorizontalRefreshLayout;
import xiao.free.horizontalrefreshlayout.R;
import xiao.free.horizontalrefreshlayout.RefreshHeader;
import xiao.free.horizontalrefreshlayout.widget.CircleImageView;
import xiao.free.horizontalrefreshlayout.widget.MaterialProgressDrawable;
/**
* Created by wangqi on 2016/7/21.
*/
public class MaterialRefreshHeader implements RefreshHeader {
private final int startOrEnd;
private CircleImageView mCircleView;
private MaterialProgressDrawable mProgress;
private ViewGroup parent;
public MaterialRefreshHeader(int startOrEnd) {
this.startOrEnd = startOrEnd;
}
@Override
public void onStart(int dragPosition, View refreshHead) {
mProgress.stop();
mProgress.showArrow(false);
mProgress.setAlpha(0);
mProgress.setStartEndTrim(0f, 0f);
}
@Override
public void onDragging(float distance, float percent, View refreshHead) {
mProgress.showArrow(true);
mProgress.setAlpha((int) (percent * 255));
mProgress.setProgressRotation(percent);
mProgress.setStartEndTrim(0f, Math.min(.8f, percent));
}
@Override
public void onReadyToRelease(View refreshHead) {
}
@NonNull
@Override
public View getView(ViewGroup container) {
this.parent = container;
ViewGroup view = (ViewGroup) LayoutInflater.from(container.getContext()).inflate(R.layout.material_refresh_header, container, false);
mCircleView = new CircleImageView(container.getContext(), 0xFFFAFAFA, 40 / 2);
mProgress = new MaterialProgressDrawable(container.getContext(), container);
mProgress.setBackgroundColor(0xFFFAFAFA);
mCircleView.setImageDrawable(mProgress);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
if (startOrEnd == HorizontalRefreshLayout.LEFT) {
layoutParams.gravity = Gravity.CENTER;
} else {
layoutParams.gravity = Gravity.CENTER;
}
mCircleView.setLayoutParams(layoutParams);
view.addView(mCircleView);
return view;
}
@Override
public void onRefreshing(View refreshHead) {
mProgress.showArrow(true);
mProgress.setAlpha(255);
mProgress.setProgressRotation(1);
mProgress.setStartEndTrim(0f, Math.min(.8f, 1));
mProgress.start();
}
}
================================================
FILE: lib/src/main/java/xiao/free/horizontalrefreshlayout/refreshhead/NiceRefreshHeader.java
================================================
package xiao.free.horizontalrefreshlayout.refreshhead;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import xiao.free.horizontalrefreshlayout.R;
import xiao.free.horizontalrefreshlayout.RefreshHeader;
/**
* Created by xiaoguochang on 2015/12/24.
*/
public class NiceRefreshHeader implements RefreshHeader {
private final Context context;
private ProgressBar progressBar;
private ImageView staticLoading;
public NiceRefreshHeader(Context context) {
this.context = context;
}
@NonNull
@Override
public View getView(ViewGroup container) {
View view = LayoutInflater.from(context).inflate(R.layout.nice_refresh_header, container, false);
progressBar = (ProgressBar) view.findViewById(R.id.progressbar);
staticLoading = (ImageView) view.findViewById(R.id.static_loading);
progressBar.setVisibility(View.INVISIBLE);
return view;
}
@Override
public void onStart(int dragPosition, View refreshHead) {
staticLoading.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.INVISIBLE);
}
@Override
public void onDragging(float distance, float percent, View refreshHead) {
int num = (int) (percent * 10);
switch (num){
case 1:
case 4:
case 7:
staticLoading.setBackgroundResource(R.drawable.ic_loading_1);
break;
case 2:
case 5:
case 8:
staticLoading.setBackgroundResource(R.drawable.ic_loading_2);
break;
case 3:
case 6:
case 9:
staticLoading.setBackgroundResource(R.drawable.ic_loading_3);
break;
}
}
@Override
public void onReadyToRelease(View refreshHead) {
}
@Override
public void onRefreshing(View refreshHead) {
staticLoading.setVisibility(View.INVISIBLE);
progressBar.setVisibility(View.VISIBLE);
}
}
================================================
FILE: lib/src/main/java/xiao/free/horizontalrefreshlayout/widget/CircleImageView.java
================================================
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xiao.free.horizontalrefreshlayout.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.support.v4.view.ViewCompat;
import android.view.animation.Animation;
import android.widget.ImageView;
/**
* Private class created to work around issues with AnimationListeners being
* called before the animation is actually complete and support shadows on older
* platforms.
*/
public class CircleImageView extends ImageView {
private static final int KEY_SHADOW_COLOR = 0x1E000000;
private static final int FILL_SHADOW_COLOR = 0x3D000000;
// PX
private static final float X_OFFSET = 0f;
private static final float Y_OFFSET = 1.75f;
private static final float SHADOW_RADIUS = 3.5f;
private static final int SHADOW_ELEVATION = 4;
private Animation.AnimationListener mListener;
private int mShadowRadius;
public CircleImageView(Context context, int color, final float radius) {
super(context);
final float density = getContext().getResources().getDisplayMetrics().density;
final int diameter = (int) (radius * density * 2);
final int shadowYOffset = (int) (density * Y_OFFSET);
final int shadowXOffset = (int) (density * X_OFFSET);
mShadowRadius = (int) (density * SHADOW_RADIUS);
ShapeDrawable circle;
if (elevationSupported()) {
circle = new ShapeDrawable(new OvalShape());
ViewCompat.setElevation(this, SHADOW_ELEVATION * density);
} else {
OvalShape oval = new OvalShadow(mShadowRadius, diameter);
circle = new ShapeDrawable(oval);
ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint());
circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,
KEY_SHADOW_COLOR);
final int padding = mShadowRadius;
// set padding so the inner image sits correctly within the shadow.
setPadding(padding, padding, padding, padding);
}
circle.getPaint().setColor(color);
setBackgroundDrawable(circle);
}
private boolean elevationSupported() {
return android.os.Build.VERSION.SDK_INT >= 21;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!elevationSupported()) {
setMeasuredDimension(getMeasuredWidth() + mShadowRadius*2, getMeasuredHeight()
+ mShadowRadius*2);
}
}
public void setAnimationListener(Animation.AnimationListener listener) {
mListener = listener;
}
@Override
public void onAnimationStart() {
super.onAnimationStart();
if (mListener != null) {
mListener.onAnimationStart(getAnimation());
}
}
@Override
public void onAnimationEnd() {
super.onAnimationEnd();
if (mListener != null) {
mListener.onAnimationEnd(getAnimation());
}
}
/**
* Update the background color of the circle image view.
*
* @param colorRes Id of a color resource.
*/
public void setBackgroundColorRes(int colorRes) {
setBackgroundColor(getContext().getResources().getColor(colorRes));
}
@Override
public void setBackgroundColor(int color) {
if (getBackground() instanceof ShapeDrawable) {
((ShapeDrawable) getBackground()).getPaint().setColor(color);
}
}
private class OvalShadow extends OvalShape {
private RadialGradient mRadialGradient;
private Paint mShadowPaint;
private int mCircleDiameter;
public OvalShadow(int shadowRadius, int circleDiameter) {
super();
mShadowPaint = new Paint();
mShadowRadius = shadowRadius;
mCircleDiameter = circleDiameter;
mRadialGradient = new RadialGradient(mCircleDiameter / 2, mCircleDiameter / 2,
mShadowRadius, new int[] {
FILL_SHADOW_COLOR, Color.TRANSPARENT
}, null, Shader.TileMode.CLAMP);
mShadowPaint.setShader(mRadialGradient);
}
@Override
public void draw(Canvas canvas, Paint paint) {
final int viewWidth = CircleImageView.this.getWidth();
final int viewHeight = CircleImageView.this.getHeight();
canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2 + mShadowRadius),
mShadowPaint);
canvas.drawCircle(viewWidth / 2, viewHeight / 2, (mCircleDiameter / 2), paint);
}
}
}
================================================
FILE: lib/src/main/java/xiao/free/horizontalrefreshlayout/widget/MaterialProgressDrawable.java
================================================
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xiao.free.horizontalrefreshlayout.widget;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
* Fancy progress indicator for Material theme.
*/
public class MaterialProgressDrawable extends Drawable implements Animatable {
private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
private static final float FULL_ROTATION = 1080.0f;
@Retention(RetentionPolicy.CLASS)
@IntDef({LARGE, DEFAULT})
public @interface ProgressDrawableSize {}
// Maps to ProgressBar.Large style
static final int LARGE = 0;
// Maps to ProgressBar default style
static final int DEFAULT = 1;
// Maps to ProgressBar default style
private static final int CIRCLE_DIAMETER = 40;
private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width
private static final float STROKE_WIDTH = 2.5f;
// Maps to ProgressBar.Large style
private static final int CIRCLE_DIAMETER_LARGE = 56;
private static final float CENTER_RADIUS_LARGE = 12.5f;
private static final float STROKE_WIDTH_LARGE = 3f;
private final int[] COLORS = new int[] {
Color.BLACK
};
/**
* The value in the linear interpolator for animating the drawable at which
* the color transition should start
*/
private static final float COLOR_START_DELAY_OFFSET = 0.75f;
private static final float END_TRIM_START_DELAY_OFFSET = 0.5f;
private static final float START_TRIM_DURATION_OFFSET = 0.5f;
/** The duration of a single progress spin in milliseconds. */
private static final int ANIMATION_DURATION = 1332;
/** The number of points in the progress "star". */
private static final float NUM_POINTS = 5f;
/** The list of animators operating on this drawable. */
private final ArrayList mAnimators = new ArrayList();
/** The indicator ring, used to manage animation state. */
private final Ring mRing;
/** Canvas rotation in degrees. */
private float mRotation;
/** Layout info for the arrowhead in dp */
private static final int ARROW_WIDTH = 10;
private static final int ARROW_HEIGHT = 5;
private static final float ARROW_OFFSET_ANGLE = 5;
/** Layout info for the arrowhead for the large spinner in dp */
private static final int ARROW_WIDTH_LARGE = 12;
private static final int ARROW_HEIGHT_LARGE = 6;
private static final float MAX_PROGRESS_ARC = .8f;
private Resources mResources;
private View mParent;
private Animation mAnimation;
private float mRotationCount;
private double mWidth;
private double mHeight;
boolean mFinishing;
public MaterialProgressDrawable(Context context, View parent) {
mParent = parent;
mResources = context.getResources();
mRing = new Ring(mCallback);
mRing.setColors(COLORS);
updateSizes(DEFAULT);
setupAnimators();
}
private void setSizeParameters(double progressCircleWidth, double progressCircleHeight,
double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) {
final Ring ring = mRing;
final DisplayMetrics metrics = mResources.getDisplayMetrics();
final float screenDensity = metrics.density;
mWidth = progressCircleWidth * screenDensity;
mHeight = progressCircleHeight * screenDensity;
ring.setStrokeWidth((float) strokeWidth * screenDensity);
ring.setCenterRadius(centerRadius * screenDensity);
ring.setColorIndex(0);
ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity);
ring.setInsets((int) mWidth, (int) mHeight);
}
/**
* Set the overall size for the progress spinner. This updates the radius
* and stroke width of the ring.
*
* @param size One of {@link MaterialProgressDrawable.LARGE} or
* {@link MaterialProgressDrawable.DEFAULT}
*/
public void updateSizes(@ProgressDrawableSize int size) {
if (size == LARGE) {
setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE,
STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE);
} else {
setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH,
ARROW_WIDTH, ARROW_HEIGHT);
}
}
/**
* @param show Set to true to display the arrowhead on the progress spinner.
*/
public void showArrow(boolean show) {
mRing.setShowArrow(show);
}
/**
* @param scale Set the scale of the arrowhead for the spinner.
*/
public void setArrowScale(float scale) {
mRing.setArrowScale(scale);
}
/**
* Set the start and end trim for the progress spinner arc.
*
* @param startAngle start angle
* @param endAngle end angle
*/
public void setStartEndTrim(float startAngle, float endAngle) {
mRing.setStartTrim(startAngle);
mRing.setEndTrim(endAngle);
}
/**
* Set the amount of rotation to apply to the progress spinner.
*
* @param rotation Rotation is from [0..1]
*/
public void setProgressRotation(float rotation) {
mRing.setRotation(rotation);
}
/**
* Update the background color of the circle image view.
*/
public void setBackgroundColor(int color) {
mRing.setBackgroundColor(color);
}
/**
* Set the colors used in the progress animation from color resources.
* The first color will also be the color of the bar that grows in response
* to a user swipe gesture.
*
* @param colors
*/
public void setColorSchemeColors(int... colors) {
mRing.setColors(colors);
mRing.setColorIndex(0);
}
@Override
public int getIntrinsicHeight() {
return (int) mHeight;
}
@Override
public int getIntrinsicWidth() {
return (int) mWidth;
}
@Override
public void draw(Canvas c) {
final Rect bounds = getBounds();
final int saveCount = c.save();
c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
mRing.draw(c, bounds);
c.restoreToCount(saveCount);
}
@Override
public void setAlpha(int alpha) {
mRing.setAlpha(alpha);
}
public int getAlpha() {
return mRing.getAlpha();
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mRing.setColorFilter(colorFilter);
}
@SuppressWarnings("unused")
void setRotation(float rotation) {
mRotation = rotation;
invalidateSelf();
}
@SuppressWarnings("unused")
private float getRotation() {
return mRotation;
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public boolean isRunning() {
final ArrayList animators = mAnimators;
final int N = animators.size();
for (int i = 0; i < N; i++) {
final Animation animator = animators.get(i);
if (animator.hasStarted() && !animator.hasEnded()) {
return true;
}
}
return false;
}
@Override
public void start() {
mAnimation.reset();
mRing.storeOriginals();
// Already showing some part of the ring
if (mRing.getEndTrim() != mRing.getStartTrim()) {
mFinishing = true;
mAnimation.setDuration(ANIMATION_DURATION/2);
mParent.startAnimation(mAnimation);
} else {
mRing.setColorIndex(0);
mRing.resetOriginals();
mAnimation.setDuration(ANIMATION_DURATION);
mParent.startAnimation(mAnimation);
}
}
@Override
public void stop() {
mParent.clearAnimation();
setRotation(0);
mRing.setShowArrow(false);
mRing.setColorIndex(0);
mRing.resetOriginals();
}
private float getMinProgressArc(Ring ring) {
return (float) Math.toRadians(
ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius()));
}
// Adapted from ArgbEvaluator.java
private int evaluateColorChange(float fraction, int startValue, int endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;
return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
(int)((startR + (int)(fraction * (endR - startR))) << 16) |
(int)((startG + (int)(fraction * (endG - startG))) << 8) |
(int)((startB + (int)(fraction * (endB - startB))));
}
/**
* Update the ring color if this is within the last 25% of the animation.
* The new ring color will be a translation from the starting ring color to
* the next color.
*/
private void updateRingColor(float interpolatedTime, Ring ring) {
if (interpolatedTime > COLOR_START_DELAY_OFFSET) {
// scale the interpolatedTime so that the full
// transformation from 0 - 1 takes place in the
// remaining time
ring.setColor(evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET)
/ (1.0f - COLOR_START_DELAY_OFFSET), ring.getStartingColor(),
ring.getNextColor()));
}
}
private void applyFinishTranslation(float interpolatedTime, Ring ring) {
// shrink back down and complete a full rotation before
// starting other circles
// Rotation goes between [0..1].
updateRingColor(interpolatedTime, ring);
float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC)
+ 1f);
final float minProgressArc = getMinProgressArc(ring);
final float startTrim = ring.getStartingStartTrim()
+ (ring.getStartingEndTrim() - minProgressArc - ring.getStartingStartTrim())
* interpolatedTime;
ring.setStartTrim(startTrim);
ring.setEndTrim(ring.getStartingEndTrim());
final float rotation = ring.getStartingRotation()
+ ((targetRotation - ring.getStartingRotation()) * interpolatedTime);
ring.setRotation(rotation);
}
private void setupAnimators() {
final Ring ring = mRing;
final Animation animation = new Animation() {
@Override
public void applyTransformation(float interpolatedTime, Transformation t) {
if (mFinishing) {
applyFinishTranslation(interpolatedTime, ring);
} else {
// The minProgressArc is calculated from 0 to create an
// angle that matches the stroke width.
final float minProgressArc = getMinProgressArc(ring);
final float startingEndTrim = ring.getStartingEndTrim();
final float startingTrim = ring.getStartingStartTrim();
final float startingRotation = ring.getStartingRotation();
updateRingColor(interpolatedTime, ring);
// Moving the start trim only occurs in the first 50% of a
// single ring animation
if (interpolatedTime <= START_TRIM_DURATION_OFFSET) {
// scale the interpolatedTime so that the full
// transformation from 0 - 1 takes place in the
// remaining time
final float scaledTime = (interpolatedTime)
/ (1.0f - START_TRIM_DURATION_OFFSET);
final float startTrim = startingTrim
+ ((MAX_PROGRESS_ARC - minProgressArc) * MATERIAL_INTERPOLATOR
.getInterpolation(scaledTime));
ring.setStartTrim(startTrim);
}
// Moving the end trim starts after 50% of a single ring
// animation completes
if (interpolatedTime > END_TRIM_START_DELAY_OFFSET) {
// scale the interpolatedTime so that the full
// transformation from 0 - 1 takes place in the
// remaining time
final float minArc = MAX_PROGRESS_ARC - minProgressArc;
float scaledTime = (interpolatedTime - START_TRIM_DURATION_OFFSET)
/ (1.0f - START_TRIM_DURATION_OFFSET);
final float endTrim = startingEndTrim
+ (minArc * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime));
ring.setEndTrim(endTrim);
}
final float rotation = startingRotation + (0.25f * interpolatedTime);
ring.setRotation(rotation);
float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime)
+ (FULL_ROTATION * (mRotationCount / NUM_POINTS));
setRotation(groupRotation);
}
}
};
animation.setRepeatCount(Animation.INFINITE);
animation.setRepeatMode(Animation.RESTART);
animation.setInterpolator(LINEAR_INTERPOLATOR);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mRotationCount = 0;
}
@Override
public void onAnimationEnd(Animation animation) {
// do nothing
}
@Override
public void onAnimationRepeat(Animation animation) {
ring.storeOriginals();
ring.goToNextColor();
ring.setStartTrim(ring.getEndTrim());
if (mFinishing) {
// finished closing the last ring from the swipe gesture; go
// into progress mode
mFinishing = false;
animation.setDuration(ANIMATION_DURATION);
ring.setShowArrow(false);
} else {
mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
}
}
});
mAnimation = animation;
}
private final Callback mCallback = new Callback() {
@Override
public void invalidateDrawable(Drawable d) {
invalidateSelf();
}
@Override
public void scheduleDrawable(Drawable d, Runnable what, long when) {
scheduleSelf(what, when);
}
@Override
public void unscheduleDrawable(Drawable d, Runnable what) {
unscheduleSelf(what);
}
};
private static class Ring {
private final RectF mTempBounds = new RectF();
private final Paint mPaint = new Paint();
private final Paint mArrowPaint = new Paint();
private final Callback mCallback;
private float mStartTrim = 0.0f;
private float mEndTrim = 0.0f;
private float mRotation = 0.0f;
private float mStrokeWidth = 5.0f;
private float mStrokeInset = 2.5f;
private int[] mColors;
// mColorIndex represents the offset into the available mColors that the
// progress circle should currently display. As the progress circle is
// animating, the mColorIndex moves by one to the next available color.
private int mColorIndex;
private float mStartingStartTrim;
private float mStartingEndTrim;
private float mStartingRotation;
private boolean mShowArrow;
private Path mArrow;
private float mArrowScale;
private double mRingCenterRadius;
private int mArrowWidth;
private int mArrowHeight;
private int mAlpha;
private final Paint mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int mBackgroundColor;
private int mCurrentColor;
public Ring(Callback callback) {
mCallback = callback;
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mPaint.setAntiAlias(true);
mPaint.setStyle(Style.STROKE);
mArrowPaint.setStyle(Style.FILL);
mArrowPaint.setAntiAlias(true);
}
public void setBackgroundColor(int color) {
mBackgroundColor = color;
}
/**
* Set the dimensions of the arrowhead.
*
* @param width Width of the hypotenuse of the arrow head
* @param height Height of the arrow point
*/
public void setArrowDimensions(float width, float height) {
mArrowWidth = (int) width;
mArrowHeight = (int) height;
}
/**
* Draw the progress spinner
*/
public void draw(Canvas c, Rect bounds) {
final RectF arcBounds = mTempBounds;
arcBounds.set(bounds);
arcBounds.inset(mStrokeInset, mStrokeInset);
final float startAngle = (mStartTrim + mRotation) * 360;
final float endAngle = (mEndTrim + mRotation) * 360;
float sweepAngle = endAngle - startAngle;
mPaint.setColor(mCurrentColor);
c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
drawTriangle(c, startAngle, sweepAngle, bounds);
if (mAlpha < 255) {
mCirclePaint.setColor(mBackgroundColor);
mCirclePaint.setAlpha(255 - mAlpha);
c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2,
mCirclePaint);
}
}
private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) {
if (mShowArrow) {
if (mArrow == null) {
mArrow = new Path();
mArrow.setFillType(Path.FillType.EVEN_ODD);
} else {
mArrow.reset();
}
// Adjust the position of the triangle so that it is inset as
// much as the arc, but also centered on the arc.
float inset = (int) mStrokeInset / 2 * mArrowScale;
float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX());
float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY());
// Update the path each time. This works around an issue in SKIA
// where concatenating a rotation matrix to a scale matrix
// ignored a starting negative rotation. This appears to have
// been fixed as of API 21.
mArrow.moveTo(0, 0);
mArrow.lineTo(mArrowWidth * mArrowScale, 0);
mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight
* mArrowScale));
mArrow.offset(x - inset, y);
mArrow.close();
// draw a triangle
mArrowPaint.setColor(mCurrentColor);
c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(),
bounds.exactCenterY());
c.drawPath(mArrow, mArrowPaint);
}
}
/**
* Set the colors the progress spinner alternates between.
*
* @param colors Array of integers describing the colors. Must be non-null.
*/
public void setColors(@NonNull int[] colors) {
mColors = colors;
// if colors are reset, make sure to reset the color index as well
setColorIndex(0);
}
/**
* Set the absolute color of the progress spinner. This is should only
* be used when animating between current and next color when the
* spinner is rotating.
*
* @param color int describing the color.
*/
public void setColor(int color) {
mCurrentColor = color;
}
/**
* @param index Index into the color array of the color to display in
* the progress spinner.
*/
public void setColorIndex(int index) {
mColorIndex = index;
mCurrentColor = mColors[mColorIndex];
}
/**
* @return int describing the next color the progress spinner should use when drawing.
*/
public int getNextColor() {
return mColors[getNextColorIndex()];
}
private int getNextColorIndex() {
return (mColorIndex + 1) % (mColors.length);
}
/**
* Proceed to the next available ring color. This will automatically
* wrap back to the beginning of colors.
*/
public void goToNextColor() {
setColorIndex(getNextColorIndex());
}
public void setColorFilter(ColorFilter filter) {
mPaint.setColorFilter(filter);
invalidateSelf();
}
/**
* @param alpha Set the alpha of the progress spinner and associated arrowhead.
*/
public void setAlpha(int alpha) {
mAlpha = alpha;
}
/**
* @return Current alpha of the progress spinner and arrowhead.
*/
public int getAlpha() {
return mAlpha;
}
/**
* @param strokeWidth Set the stroke width of the progress spinner in pixels.
*/
public void setStrokeWidth(float strokeWidth) {
mStrokeWidth = strokeWidth;
mPaint.setStrokeWidth(strokeWidth);
invalidateSelf();
}
@SuppressWarnings("unused")
public float getStrokeWidth() {
return mStrokeWidth;
}
@SuppressWarnings("unused")
public void setStartTrim(float startTrim) {
mStartTrim = startTrim;
invalidateSelf();
}
@SuppressWarnings("unused")
public float getStartTrim() {
return mStartTrim;
}
public float getStartingStartTrim() {
return mStartingStartTrim;
}
public float getStartingEndTrim() {
return mStartingEndTrim;
}
public int getStartingColor() {
return mColors[mColorIndex];
}
@SuppressWarnings("unused")
public void setEndTrim(float endTrim) {
mEndTrim = endTrim;
invalidateSelf();
}
@SuppressWarnings("unused")
public float getEndTrim() {
return mEndTrim;
}
@SuppressWarnings("unused")
public void setRotation(float rotation) {
mRotation = rotation;
invalidateSelf();
}
@SuppressWarnings("unused")
public float getRotation() {
return mRotation;
}
public void setInsets(int width, int height) {
final float minEdge = (float) Math.min(width, height);
float insets;
if (mRingCenterRadius <= 0 || minEdge < 0) {
insets = (float) Math.ceil(mStrokeWidth / 2.0f);
} else {
insets = (float) (minEdge / 2.0f - mRingCenterRadius);
}
mStrokeInset = insets;
}
@SuppressWarnings("unused")
public float getInsets() {
return mStrokeInset;
}
/**
* @param centerRadius Inner radius in px of the circle the progress
* spinner arc traces.
*/
public void setCenterRadius(double centerRadius) {
mRingCenterRadius = centerRadius;
}
public double getCenterRadius() {
return mRingCenterRadius;
}
/**
* @param show Set to true to show the arrow head on the progress spinner.
*/
public void setShowArrow(boolean show) {
if (mShowArrow != show) {
mShowArrow = show;
invalidateSelf();
}
}
/**
* @param scale Set the scale of the arrowhead for the spinner.
*/
public void setArrowScale(float scale) {
if (scale != mArrowScale) {
mArrowScale = scale;
invalidateSelf();
}
}
/**
* @return The amount the progress spinner is currently rotated, between [0..1].
*/
public float getStartingRotation() {
return mStartingRotation;
}
/**
* If the start / end trim are offset to begin with, store them so that
* animation starts from that offset.
*/
public void storeOriginals() {
mStartingStartTrim = mStartTrim;
mStartingEndTrim = mEndTrim;
mStartingRotation = mRotation;
}
/**
* Reset the progress spinner to default rotation, start and end angles.
*/
public void resetOriginals() {
mStartingStartTrim = 0;
mStartingEndTrim = 0;
mStartingRotation = 0;
setStartTrim(0);
setEndTrim(0);
setRotation(0);
}
private void invalidateSelf() {
mCallback.invalidateDrawable(null);
}
}
}
================================================
FILE: lib/src/main/res/drawable/animated_rotate.xml
================================================
================================================
FILE: lib/src/main/res/drawable/animated_rotate_1.xml
================================================
================================================
FILE: lib/src/main/res/drawable/spinner.xml
================================================
================================================
FILE: lib/src/main/res/layout/common_loading_refresh_header.xml
================================================
================================================
FILE: lib/src/main/res/layout/material_refresh_header.xml
================================================
================================================
FILE: lib/src/main/res/layout/nice_refresh_header.xml
================================================
================================================
FILE: lib/src/main/res/values/strings.xml
================================================
lib
================================================
FILE: settings.gradle
================================================
include ':app', ':lib'